eeac40956b3dd6611f58aeb847ff5c404d3ce883 angie Fri Dec 1 09:05:41 2023 -0800 Support trees whose reference/root is not from a db or hub, but rather a custom .2bit file. This means that the selected pathogen may or may not also be a db/hub, so the selection interacts with the db cart variable but does not always match it. Also, in phyloPlace.c, update RSV metadata column headers (RGCC lineages replace Ramaekers 2020 clades). diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c index 5b12724..116b270 100644 --- src/hg/hgPhyloPlace/phyloPlace.c +++ src/hg/hgPhyloPlace/phyloPlace.c @@ -26,59 +26,61 @@ #include "pipeline.h" #include "psl.h" #include "pthreadWrap.h" #include "ra.h" #include "regexHelper.h" #include "trackHub.h" #include "trashDir.h" #include "vcf.h" // Globals: static boolean measureTiming = FALSE; // Parameter constants: int maxGenotypes = 1000; // Upper limit on number of samples user can upload at once. boolean showParsimonyScore = FALSE; +int minSamplesForOwnTree = 3; // If user uploads at least this many samples, show tree for them. struct slName *phyloPlaceDbList(struct cart *cart) -/* Each subdirectory of PHYLOPLACE_DATA_DIR that contains a config.ra file is a supported db - * or track hub name (without the hub_number_ prefix). Return a list of them, or NULL if none - * are found. */ +/* Each subdirectory of PHYLOPLACE_DATA_DIR that contains a config.ra file is a supported data + * source and might also be a db or track hub name (without the hub_number_ prefix). Return a + * list of them, or NULL if none are found. */ { struct slName *dbList = NULL; // I was hoping the pattern would be wildMatch'd only against filenames so I could use "config.ra", // but both directories and files must match the pattern so must use "*". struct slName *dataDirPaths = pathsInDirAndSubdirs(PHYLOPLACE_DATA_DIR, "*"); struct slName *path; for (path = dataDirPaths; path != NULL; path = path->next) { if (endsWith(path->name, "/config.ra")) { char dir[PATH_LEN], name[FILENAME_LEN], extension[FILEEXT_LEN]; splitPath(path->name, dir, name, extension); if (endsWith(dir, "/")) dir[strlen(dir)-1] = '\0'; char *db = strrchr(dir, '/'); if (db == NULL) db = dir; else db++; if (hDbExists(db)) slNameAddHead(&dbList, db); else { + // Not a db -- see if it's a hub that is already connected: struct trackHubGenome *hubGenome = trackHubGetGenomeUndecorated(db); if (hubGenome != NULL) slNameAddHead(&dbList, hubGenome->name); else { // Not connected to session currently. If the name looks like an NCBI Assembly ID // then try connecting to the corresponding UCSC assembly hub. regmatch_t substrs[5]; if (regexMatchSubstr(db, "^(GC[AF])_([0-9]{3})([0-9]{3})([0-9]{3})\\.[0-9]$", substrs, ArraySize(substrs))) { char gcPrefix[4], first3[4], mid3[4], last3[4]; regexSubstringCopy(db, substrs[1], gcPrefix, sizeof gcPrefix); regexSubstringCopy(db, substrs[2], first3, sizeof first3); regexSubstringCopy(db, substrs[3], mid3, sizeof mid3); @@ -87,30 +89,35 @@ "%s/%s/hub.txt", gcPrefix, first3, mid3, last3, db); // Use cart variables to pretend user clicked to connect to this hub. cartSetString(cart, hgHubDataText, dy->string); cartSetString(cart, hgHubGenome, db); struct errCatch *errCatch = errCatchNew(); char *hubDb = NULL; if (errCatchStart(errCatch)) { hubDb = hubConnectLoadHubs(cart); } errCatchEnd(errCatch); if (hubDb != NULL) slNameAddHead(&dbList, hubDb); } + else + { + // Doesn't appear to be a hub; count on its config to specify a .2bit file. + slNameAddHead(&dbList, db); + } } } } } // Reverse alphabetical sort to put wuhCor1/SARS-CoV-2 first slNameSort(&dbList); slReverse(&dbList); return dbList; } char *phyloPlaceDbSetting(char *db, char *settingName) /* Return a setting from hgPhyloPlaceData/<db>/config.ra or NULL if not found. */ { static struct hash *configHash = NULL; static char *configDb = NULL; @@ -775,34 +782,34 @@ lineageIx = stringArrayIx("pango_lineage", headerWords, headerWordCount); int countryIx = stringArrayIx("country", headerWords, headerWordCount); int divisionIx = stringArrayIx("division", headerWords, headerWordCount); int locationIx = stringArrayIx("location", headerWords, headerWordCount); int countryExpIx = stringArrayIx("country_exposure", headerWords, headerWordCount); int divExpIx = stringArrayIx("division_exposure", headerWords, headerWordCount); int origLabIx = stringArrayIx("originating_lab", headerWords, headerWordCount); int subLabIx = stringArrayIx("submitting_lab", headerWords, headerWordCount); int regionIx = stringArrayIx("region", headerWords, headerWordCount); int nCladeUsherIx = stringArrayIx("Nextstrain_clade_usher", headerWords, headerWordCount); int lineageUsherIx = stringArrayIx("pango_lineage_usher", headerWords, headerWordCount); int authorsIx = stringArrayIx("authors", headerWords, headerWordCount); int pubsIx = stringArrayIx("publications", headerWords, headerWordCount); int nLineageIx = stringArrayIx("Nextstrain_lineage", headerWords, headerWordCount); int gnCladeIx = stringArrayIx("goya_nextclade", headerWords, headerWordCount); - int rnCladeIx = stringArrayIx("ramaekers_nextclade", headerWords, headerWordCount); + int rnCladeIx = stringArrayIx("GCC_nextclade", headerWords, headerWordCount); int guCladeIx = stringArrayIx("goya_usher", headerWords, headerWordCount); - int ruCladeIx = stringArrayIx("ramaekers_usher", headerWords, headerWordCount); - int rtCladeIx = stringArrayIx("ramaekers_tableS1", headerWords, headerWordCount); + int ruCladeIx = stringArrayIx("GCC_usher", headerWords, headerWordCount); + int rtCladeIx = stringArrayIx("GCC_assigned_2023-11", headerWords, headerWordCount); while (lineFileNext(lf, &line, NULL)) { char *words[headerWordCount]; int wordCount = chopTabs(line, words); lineFileExpectWords(lf, headerWordCount, wordCount); struct sampleMetadata *met; AllocVar(met); if (strainIx >= 0) met->strain = cloneString(words[strainIx]); if (epiIdIx >= 0) met->epiId = cloneString(words[epiIdIx]); if (genbankIx >= 0 && !sameString("?", words[genbankIx])) met->gbAcc = cloneString(words[genbankIx]); if (dateIx >= 0) met->date = cloneString(words[dateIx]); @@ -828,41 +835,41 @@ met->origLab = cloneString(words[origLabIx]); if (subLabIx >= 0) met->subLab = cloneString(words[subLabIx]); if (regionIx >= 0) met->region = cloneString(words[regionIx]); if (nCladeUsherIx >= 0) met->nCladeUsher = cloneString(words[nCladeUsherIx]); if (lineageUsherIx >= 0) met->lineageUsher = cloneString(words[lineageUsherIx]); if (authorsIx >= 0) met->authors = cloneString(words[authorsIx]); if (pubsIx >= 0) met->pubs = cloneString(words[pubsIx]); if (nLineageIx >= 0) met->nLineage = cloneString(words[nLineageIx]); - // For RSV, use lineage for Ramaekers clades and nClade for Goya clades. + // For RSV, use lineage for GCC clades and nClade for Goya clades. // This is getting ugly and we really should specify metadata columns in config.ra files. if (gnCladeIx >= 0) met->nClade = cloneString(words[gnCladeIx]); if (rnCladeIx >= 0) met->lineage = cloneString(words[rnCladeIx]); if (guCladeIx >= 0) met->nCladeUsher = cloneString(words[guCladeIx]); if (ruCladeIx >= 0) met->lineageUsher = cloneString(words[ruCladeIx]); - // Uglier still, use gClade to store Ramaekers Table S1 designations because it's left over. + // Uglier still, use gClade to store GCC designations because it's left over. if (rtCladeIx >= 0) met->gClade = cloneString(words[rtCladeIx]); // If epiId and/or genbank ID is included, we'll probably be using that to look up items. if (epiIdIx >= 0 && !isEmpty(words[epiIdIx])) hashAdd(sampleMetadata, words[epiIdIx], met); if (genbankIx >= 0 && !isEmpty(words[genbankIx]) && !sameString("?", words[genbankIx])) { if (strchr(words[genbankIx], '.')) { // Index by versionless accession char copy[strlen(words[genbankIx])+1]; safecpy(copy, sizeof copy, words[genbankIx]); char *dot = strchr(copy, '.'); *dot = '\0'; hashAdd(sampleMetadata, copy, met); @@ -1565,36 +1572,32 @@ "Goya 2020</a> clade" TOOLTIP("The clade described in " "<a href='https://doi.org/10.1111/irv.12715' target=_blank>Goya et al. 2020, " ""Toward unified molecular surveillance of RSV: A proposal for " "genotype definition"</a> " "assigned by placement in the tree")); else puts("</th>\n<th>Nextstrain lineage" TOOLTIP("The Nextstrain lineage assigned by " "placement in the tree")); } if (gotLineages) { if (isRsv) puts("</th>\n<th><a href='https://doi.org/10.1093/ve/veaa052' target=_blank>" - "Ramaekers 2020</a> clade" - TOOLTIP("The clade described in " - "<a href='https://doi.org/10.1093/ve/veaa052' target=_blank>" - "Ramaekers et al. 2020, " - ""Towards a unified classification for human respiratory syncytial virus " - "genotypes"</a> " + "RGCC 2023</a> clade" + TOOLTIP("The RSV Genotyping Consensus Consortium clade (manuscript in preparation)" "assigned by placement in the tree")); else puts("</th>\n<th>Pango lineage" TOOLTIP("The <a href='https://cov-lineages.org/' " "target=_blank>Pango lineage</a> assigned to the sample by UShER")); } puts("</th>\n<th>Neighboring sample in tree" TOOLTIP("A sample already in the tree that is a child of the node at which the uploaded " "sample was placed, to give an example of a closely related sample") "</th>\n<th>Lineage of neighbor"); if (sameString(db, "wuhCor1")) puts(TOOLTIP("The <a href='https://cov-lineages.org/' target=_blank>" "Pango lineage</a> assigned by pangolin " "to the nearest neighboring sample already in the tree")); else @@ -3034,89 +3037,126 @@ struct hash *sampleMetadata = getSampleMetadata(metadataFile); reportTiming(&startTime, "read sample metadata (in a pthread)"); return sampleMetadata; } static pthread_t *mayStartLoaderPthread(char *filename, void *(*workerFunction)(void *)) /* Fork off a child process that parses a file and returns the resulting data structure. */ { pthread_t *pt; AllocVar(pt); if (! pthreadMayCreate(pt, NULL, workerFunction, filename)) pt = NULL; return pt; } -char *phyloPlaceSamples(struct lineFile *lf, char *db, char *defaultProtobuf, +static struct dnaSeq *getChromSeq(char *db, char *refName) +/* Get the reference sequence for refName, using a .2bit file if configured, + * otherwise hdb lib functions (requires refName happens to be a real db or hub). */ +{ +char *twoBitName = phyloPlaceDbSettingPath(refName, "twoBitFile"); +char *chrom = phyloPlaceDbSetting(refName, "chrom"); +struct dnaSeq *seq = NULL; +if (isNotEmpty(twoBitName) && fileExists(twoBitName)) + { + struct slName *seqNames = twoBitSeqNames(twoBitName); + if (isEmpty(chrom)) + chrom = cloneString(seqNames->name); + struct twoBitFile *tbf = twoBitOpen(twoBitName); + seq = twoBitReadSeqFrag(tbf, chrom, 0, 0); + // Convert to lower case so genoFind doesn't index it as containing no tiles. + tolowers(seq->dna); + twoBitClose(&tbf); + slNameFreeList(&seqNames); + } +else if (sameString(db, refName)) + { + if (isEmpty(chrom)) + chrom = hDefaultChrom(db); + seq = hChromSeq(db, chrom, 0, hChromSize(db, chrom)); + } +else + errAbort("No twoBitFile or db/hub found for %s", refName); +return seq; +} + +static struct phyloTree *uploadedSamplesTree(char *singleSubtreeFile, struct slName *sampleIds) +/* If the user uploaded enough samples to make a meaningful tree, then read in singleSubtreeFile + * and prune all nodes that have no leaf descendants in sampleIds to get the tree of only the + * uploaded samples. */ +{ +struct phyloTree *tree = NULL; +if (slCount(sampleIds) >= minSamplesForOwnTree) + { + tree = phyloOpenTree(singleSubtreeFile); + tree = phyloPruneToIds(tree, sampleIds); + } +return tree; +} + +char *phyloPlaceSamples(struct lineFile *lf, char *db, char *refName, char *defaultProtobuf, boolean doMeasureTiming, int subtreeSize, int fontHeight, boolean *retSuccess) /* Given a lineFile that contains either FASTA, VCF, or a list of sequence names/ids: * If FASTA/VCF, then prepare VCF for usher; if that goes well then run usher, report results, * make custom track files and return the top-level custom track file. * If list of seq names/ids, then attempt to find their full names in the protobuf, run matUtils * to make subtrees, show subtree results, and return NULL. Set retSuccess to TRUE if we were * able to get at least some results for the user's input. */ { char *ctFile = NULL; if (retSuccess) *retSuccess = FALSE; measureTiming = doMeasureTiming; int startTime = clock1000(); struct tempName *vcfTn = NULL; struct slName *sampleIds = NULL; char *usherPath = getUsherPath(TRUE); char *protobufPath = NULL; char *source = NULL; char *metadataFile = NULL; char *aliasFile = NULL; char *sampleNameFile = NULL; -struct treeChoices *treeChoices = loadTreeChoices(db); -getProtobufMetadataSource(db, treeChoices, defaultProtobuf, +struct treeChoices *treeChoices = loadTreeChoices(refName); +getProtobufMetadataSource(refName, treeChoices, defaultProtobuf, &protobufPath, &metadataFile, &source, &aliasFile, &sampleNameFile); reportTiming(&startTime, "start up and find the tree etc. files"); struct mutationAnnotatedTree *bigTree = NULL; lineFileCarefulNewlines(lf); -char *chrom = hDefaultChrom(db); -//#*** Hack for influenza -if (stringIn("GCF_000865085.1", db)) - chrom = "NC_007366.1"; -else if (stringIn("GCF_001343785.1", db)) - chrom = "NC_026433.1"; -int chromSize = hChromSize(db, chrom); -struct slName **maskSites = getProblematicSites(db, chrom, chromSize); +struct dnaSeq *refGenome = getChromSeq(db, refName); +struct slName **maskSites = getProblematicSites(refName, refGenome->name, refGenome->size); //#*** TODO: add CGI param option for this almost-never-needed tweak: if (0) { bigTree = parseParsimonyProtobuf(protobufPath); reportTiming(&startTime, "parse protobuf file (at startup, for excluding informativeBases " "from maskSites)"); - informativeBasesFromTree(bigTree->tree, maskSites, chromSize); + informativeBasesFromTree(bigTree->tree, maskSites, refGenome->size); reportTiming(&startTime, "remove any informative bases in tree from maskSites"); } -struct dnaSeq *refGenome = hChromSeq(db, chrom, 0, chromSize); boolean isFasta = FALSE; boolean subtreesOnly = FALSE; struct seqInfo *seqInfoList = NULL; if (lfLooksLikeFasta(lf)) { struct slPair *failedSeqs; struct slPair *failedPsls; struct hash *treeNames = NULL; // We need to check uploaded names in fasta only for original usher, not usher-sampled(-server). - if (!serverIsConfigured(db) && !endsWith(usherPath, "-sampled")) + if (!serverIsConfigured(refName) && !endsWith(usherPath, "-sampled")) treeNames = getTreeNames(sampleNameFile, protobufPath, &bigTree, FALSE, &startTime); - vcfTn = vcfFromFasta(lf, db, refGenome, maskSites, treeNames, + vcfTn = vcfFromFasta(lf, refName, refGenome, maskSites, treeNames, &sampleIds, &seqInfoList, &failedSeqs, &failedPsls, &startTime); if (failedSeqs) { puts("<p>"); struct slPair *fail; for (fail = failedSeqs; fail != NULL; fail = fail->next) printf("%s<br>\n", fail->name); puts("</p>"); } if (failedPsls) { puts("<p>"); struct slPair *fail; for (fail = failedPsls; fail != NULL; fail = fail->next) printf("%s<br>\n", fail->name); @@ -3142,166 +3182,171 @@ reportTiming(&startTime, "look up uploaded sample names"); } lineFileClose(&lf); if (sampleIds == NULL) { return ctFile; } // Kick off child thread to load metadata simultaneously with running usher or matUtils. pthread_t *metadataPthread = mayStartLoaderPthread(metadataFile, loadMetadataWorker); struct usherResults *results = NULL; if (vcfTn) { fflush(stdout); - results = runUsher(db, usherPath, protobufPath, vcfTn->forCgi, subtreeSize, &sampleIds, + results = runUsher(refName, usherPath, protobufPath, vcfTn->forCgi, subtreeSize, &sampleIds, treeChoices, &startTime); } else if (subtreesOnly) { char *matUtilsPath = getMatUtilsPath(TRUE); - results = runMatUtilsExtractSubtrees(db, matUtilsPath, protobufPath, subtreeSize, + results = runMatUtilsExtractSubtrees(refName, matUtilsPath, protobufPath, subtreeSize, sampleIds, treeChoices, &startTime); } struct hash *sampleMetadata = NULL; if (metadataPthread) { pthreadJoin(metadataPthread, (void **)(&sampleMetadata)); reportTiming(&startTime, "wait for sample metadata loading thread to finish"); } else { // We really need metadata -- load it the slow way. sampleMetadata = getSampleMetadata(metadataFile); reportTiming(&startTime, "load sample metadata without pthread"); } if (results && results->singleSubtreeInfo) { if (retSuccess) *retSuccess = TRUE; puts("<p></p>"); - readQcThresholds(db); + readQcThresholds(refName); int subtreeCount = slCount(results->subtreeInfoList); // Sort subtrees by number of user samples (largest first). slSort(&results->subtreeInfoList, subTreeInfoUserSampleCmp); // Make Nextstrain/auspice JSON file for each subtree. - char *bigGenePredFile = phyloPlaceDbSettingPath(db, "bigGenePredFile"); + char *bigGenePredFile = phyloPlaceDbSettingPath(refName, "bigGenePredFile"); struct geneInfo *geneInfoList = getGeneInfoList(bigGenePredFile, refGenome); - struct seqWindow *gSeqWin = memSeqWindowNew(chrom, refGenome->dna); + struct seqWindow *gSeqWin = memSeqWindowNew(refGenome->name, refGenome->dna); struct hash *sampleUrls = hashNew(0); struct tempName *jsonTns[subtreeCount]; struct subtreeInfo *ti; int ix; for (ix = 0, ti = results->subtreeInfoList; ti != NULL; ti = ti->next, ix++) { AllocVar(jsonTns[ix]); char subtreeName[512]; safef(subtreeName, sizeof(subtreeName), "subtreeAuspice%d", ix+1); trashDirFile(jsonTns[ix], "ct", subtreeName, ".json"); - treeToAuspiceJson(ti, db, geneInfoList, gSeqWin, sampleMetadata, NULL, + treeToAuspiceJson(ti, refName, geneInfoList, gSeqWin, sampleMetadata, NULL, results->samplePlacements, jsonTns[ix]->forCgi, source); // Add a link for every sample to this subtree, so the single-subtree JSON can // link to subtree JSONs char *subtreeUrl = nextstrainUrlFromTn(jsonTns[ix]); struct slName *sample; for (sample = ti->subtreeUserSampleIds; sample != NULL; sample = sample->next) hashAdd(sampleUrls, sample->name, subtreeUrl); } struct tempName *singleSubtreeJsonTn; AllocVar(singleSubtreeJsonTn); trashDirFile(singleSubtreeJsonTn, "ct", "singleSubtreeAuspice", ".json"); - treeToAuspiceJson(results->singleSubtreeInfo, db, geneInfoList, gSeqWin, sampleMetadata, + treeToAuspiceJson(results->singleSubtreeInfo, refName, geneInfoList, gSeqWin, sampleMetadata, sampleUrls, results->samplePlacements, singleSubtreeJsonTn->forCgi, source); reportTiming(&startTime, "make Auspice JSON"); struct subtreeInfo *subtreeInfoForButtons = results->subtreeInfoList; if (subtreeCount > MAX_SUBTREE_BUTTONS) subtreeInfoForButtons = NULL; + boolean canDoCustomTracks = (!subtreesOnly && sameString(db, refName)); makeButtonRow(singleSubtreeJsonTn, jsonTns, subtreeInfoForButtons, subtreeSize, isFasta, - !subtreesOnly); + canDoCustomTracks); printf("<p>If you have metadata you wish to display, click a 'view subtree in " "Nextstrain' button, and then you can drag on a CSV file to " "<a href='"NEXTSTRAIN_DRAG_DROP_DOC"' target=_blank>add it to the tree view</a>." "</p>\n"); puts("<p><em>Note: " "The Nextstrain subtree views, and Download files below, are temporary files and will " "expire within two days. " "Please download the Nextstrain subtree JSON files if you will want to view them " "again in the future. The JSON files can be drag-dropped onto " "<a href='https://auspice.us/' target=_blank>https://auspice.us/</a>." "</em></p>"); struct tempName *tsvTn = NULL, *sTsvTn = NULL; struct tempName *zipTn = makeSubtreeZipFile(results, jsonTns, singleSubtreeJsonTn, &startTime); struct tempName *ctTn = NULL; if (subtreesOnly) { - summarizeSubtrees(sampleIds, results, sampleMetadata, jsonTns, db, subtreeSize); + summarizeSubtrees(sampleIds, results, sampleMetadata, jsonTns, refName, subtreeSize); reportTiming(&startTime, "describe subtrees"); } else { findNearestNeighbors(results, sampleMetadata); reportTiming(&startTime, "find nearest neighbors"); + char *singleSubtreeFile = results->singleSubtreeInfo->subtreeTn->forCgi; + struct phyloTree *sampleTree = uploadedSamplesTree(singleSubtreeFile, sampleIds); + if (sameString(db, refName)) + { // Make custom tracks for uploaded samples and subtree(s). - struct phyloTree *sampleTree = NULL; ctTn = writeCustomTracks(db, vcfTn, results, sampleIds, source, fontHeight, - &sampleTree, &startTime); + sampleTree, &startTime); + } // Make a sample summary TSV file and accumulate S gene changes struct hash *seqInfoHash = hashFromSeqInfoListAndIds(seqInfoList, sampleIds); addSampleMutsFromSeqInfo(results->samplePlacements, seqInfoHash); struct hash *spikeChanges = hashNew(0); tsvTn = writeTsvSummary(results, sampleTree, sampleIds, seqInfoHash, geneInfoList, gSeqWin, spikeChanges, &startTime); sTsvTn = writeSpikeChangeSummary(spikeChanges, slCount(sampleIds)); downloadsRow(results->bigTreePlusTn->forHtml, tsvTn->forHtml, sTsvTn->forHtml, zipTn->forHtml); int seqCount = slCount(seqInfoList); if (seqCount <= MAX_SEQ_DETAILS) { - char *refAcc = cloneString(chrom); + char *refAcc = cloneString(refGenome->name); if (regexMatch(refAcc, "v[0-9]+$")) { char *v = strrchr(refAcc, 'v'); assert(v != NULL); *v = '.'; } - summarizeSequences(seqInfoList, isFasta, results, jsonTns, refAcc, db, subtreeSize); + summarizeSequences(seqInfoList, isFasta, results, jsonTns, refAcc, refName, subtreeSize); reportTiming(&startTime, "write summary table (including reading in lineages)"); for (ix = 0, ti = results->subtreeInfoList; ti != NULL; ti = ti->next, ix++) { int subtreeUserSampleCount = slCount(ti->subtreeUserSampleIds); printf("<h3>Subtree %d: ", ix+1); if (subtreeUserSampleCount > 1) printf("%d related samples", subtreeUserSampleCount); else if (subtreeCount > 1) printf("Unrelated sample"); printf("</h3>\n"); makeNextstrainButtonN("viewNextstrainSub", ix, subtreeUserSampleCount, subtreeSize, jsonTns); puts("<br>"); // Make a sub-subtree with only user samples for display: struct phyloTree *subtree = phyloOpenTree(ti->subtreeTn->forCgi); subtree = phyloPruneToIds(subtree, ti->subtreeUserSampleIds); describeSamplePlacements(ti->subtreeUserSampleIds, results->samplePlacements, - subtree, sampleMetadata, source, refAcc, db); + subtree, sampleMetadata, source, refAcc, refName); } reportTiming(&startTime, "describe placements"); } else printf("<p>(Skipping details; " "you uploaded %d sequences, and details are shown only when " "you upload at most %d sequences.)</p>\n", seqCount, MAX_SEQ_DETAILS); } puts("<h3>Downloads</h3>"); if (! subtreesOnly) { puts("<ul>"); // Offer big tree w/new samples for download @@ -3332,28 +3377,28 @@ puts(" (Newick file)</a>"); printf("<li><a href='%s' download>Auspice JSON for subtree with %s", jsonTns[ix]->forHtml, ti->subtreeUserSampleIds->name); if (subtreeUserSampleCount > 10) printf(" and %d other samples", subtreeUserSampleCount - 1); else { struct slName *sln; for (sln = ti->subtreeUserSampleIds->next; sln != NULL; sln = sln->next) printf(", %s", sln->name); } puts(" (JSON file)</a>"); } puts("</ul>"); - if (!subtreesOnly) + if (ctTn != NULL) { // Notify in opposite order of custom track creation. puts("<h3>Custom tracks for viewing in the Genome Browser</h3>"); printf("<p>Added custom track of uploaded samples.</p>\n"); if (subtreeCount > 0 && subtreeCount <= MAX_SUBTREE_CTS) printf("<p>Added %d subtree custom track%s.</p>\n", subtreeCount, (subtreeCount > 1 ? "s" : "")); ctFile = urlFromTn(ctTn); } } return ctFile; }