e70c7f68f61a3477631bd364c31f99c157528fc8 braney Fri Sep 4 12:48:02 2026 -0700 quickLift: lift chains, and let chain and bigChain tracks into the hub A chain is an alignment between the assembly the track came from and some other species, so lifting one composes two alignments and leaves the user with chains between the assembly on screen and that species. quickLiftChain does it by the same route the psl work uses: chainToPsl, then quickLiftPsl, then chainFromPsl, which is the inverse chainToPsl never had. It does not modify the chain handed to it. That matters more than it sounds: every chain loader leaves the header describing the whole chain while loading only the blocks in range, so the header has to be corrected for the conversion, but the callers still want the original. The details page reports it, and the track sorts on it, which is what puts a lifted chain in the same row as the native one. quickLiftSourceRanges hands back the ranges in the other assembly that the window maps to, for callers whose items cannot be had from a query quickLiftSql knows how to make. It works those ranges out by intersecting the window with each chain block rather than taking the whole block, which matters here because one block can be enormous: hg19 and hg38 run identical for 12.8Mb on chr7, and asking for all of it turned a 39 chain window into a 1892 chain one. quickLiftIsOwnChainTrack keeps quickLift's own chain track out of all this. That stanza carries quickLiftUrl and quickLiftDb like any lifted track and is loaded by bigChainLoadItems like any bigChain, but its data is already in reference coordinates. The giveaway is that its bigDataUrl is the quickLift chain file. validateOneTdb now accepts chain and bigChain. netAlign is still refused: a net is a hierarchy of gaps rather than a plain alignment and wants its own thought. refs #38249 diff --git src/hg/lib/quickLift.c src/hg/lib/quickLift.c index 0275143cf1b..57d99647552 100644 --- src/hg/lib/quickLift.c +++ src/hg/lib/quickLift.c @@ -290,41 +290,44 @@ unsigned ret = 0; struct sqlConnection *conn = hConnectCentral(); char query[2048]; // this needs to use the hg.conf setting sqlSafef(query, sizeof(query), "select q.id from quickLiftChain q where q.fromDb='%s' and q.toDb='%s'", fromDb, toDb); char *geneId = sqlQuickString(conn, query); hDisconnectCentral(&conn); if (geneId) ret = atoi(geneId); return ret; } +#define QUICKLIFT_RANGE_PAD 100000 + static struct chain *quickLiftLoadChains(char *quickLiftFile, char *chrom, int start, int end) /* Load the chains from quickLiftFile that overlap a padded window around the * destination range. */ { // need to add some padding to these coordinates -int padStart = start - 100000; +int padStart = start - QUICKLIFT_RANGE_PAD; if (padStart < 0) padStart = 0; char *linkFileName = bigChainGetLinkFile(quickLiftFile); -return chainLoadIdRangeHub(NULL, quickLiftFile, linkFileName, chrom, padStart, end+100000, -1); +return chainLoadIdRangeHub(NULL, quickLiftFile, linkFileName, chrom, padStart, + end + QUICKLIFT_RANGE_PAD, -1); } static void quickLiftChainQueryRange(struct chain *chain, int *retQStart, int *retQEnd) /* Return the query-side ("other" species) coordinate range spanned by the * aligned blocks of chain, corrected for query strand. chain->blockList must * not be NULL. */ { struct cBlock *cb = chain->blockList; int qStart = cb->qStart; int qEnd = cb->qEnd; for(; cb; cb = cb->next) { if (cb->qStart < qStart) qStart = cb->qStart; @@ -332,48 +335,119 @@ qEnd = cb->qEnd; } // correct for strand if (chain->qStrand == '-') { int saveStart = qStart; qStart = chain->qSize - qEnd; qEnd = chain->qSize - saveStart; } *retQStart = qStart; *retQEnd = qEnd; } -struct hash *quickLiftChainHash(char *quickLiftFile, char *chrom, int start, int end) -// Load the quickLift chains covering chrom:start-end on the reference and return them in a -// hash keyed on the other assembly's sequence names, which is the shape the lift functions -// want. Use this when the items were fetched some other way, so quickLiftSql was not the -// thing that collected the chains. +static boolean quickLiftChainRangeIn(struct chain *chain, int tStart, int tEnd, + int *retQStart, int *retQEnd) +/* The query side range matching tStart..tEnd on the target, rather than the whole extent + * of the chain's blocks. One block can be enormous: hg19 and hg38 run identical for + * 12.8Mb on chr7, so the whole-block answer would ask the other assembly for millions of + * bases either side of the window. Within a block the two sides are colinear, so the + * part that matters can be worked out exactly. Returns FALSE if no block overlaps. */ +{ +struct cBlock *cb; +int qStart = 0, qEnd = 0; +boolean any = FALSE; + +for (cb = chain->blockList; cb != NULL; cb = cb->next) + { + int s = max(cb->tStart, tStart); + int e = min(cb->tEnd, tEnd); + if (s >= e) + continue; + + int qLo = cb->qStart + (s - cb->tStart); + int qHi = cb->qStart + (e - cb->tStart); + if (!any || (qLo < qStart)) + qStart = qLo; + if (!any || (qHi > qEnd)) + qEnd = qHi; + any = TRUE; + } +if (!any) + return FALSE; + +// correct for strand +if (chain->qStrand == '-') + { + int saveStart = qStart; + qStart = chain->qSize - qEnd; + qEnd = chain->qSize - saveStart; + } +*retQStart = qStart; +*retQEnd = qEnd; +return TRUE; +} + +struct quickLiftRange *quickLiftSourceRanges(char *quickLiftFile, char *chrom, int start, int end, + struct hash *chainHash) +// The ranges in the other assembly that map into chrom:start-end on the reference. The +// chains that do the mapping are added to chainHash, which is the form the lift functions +// read. Use this when the items cannot be had from a query quickLiftSql knows how to make. { -struct hash *chainHash = newHash(8); struct chain *chain, *chainList = quickLiftLoadChains(quickLiftFile, chrom, start, end); +struct quickLiftRange *rangeList = NULL; for(chain = chainList; chain; chain = chain->next) { if (chain->blockList == NULL) continue; + // pad the window the same way quickLiftLoadChains does, so an item that reaches into + // the window from just outside it is still found + int qStart, qEnd; + int padStart = start - QUICKLIFT_RANGE_PAD; + if (padStart < 0) + padStart = 0; + if (quickLiftChainRangeIn(chain, padStart, end + QUICKLIFT_RANGE_PAD, &qStart, &qEnd)) + { + struct quickLiftRange *range; + AllocVar(range); + range->chrom = cloneString(chain->qName); + range->start = qStart; + range->end = qEnd; + slAddHead(&rangeList, range); + } + + // the query range was read off the chain as it came, so swap only afterwards chainSwap(chain); liftOverAddChainHash(chainHash, chain); } +slReverse(&rangeList); +return rangeList; +} +struct hash *quickLiftChainHash(char *quickLiftFile, char *chrom, int start, int end) +// Load the quickLift chains covering chrom:start-end on the reference and return them in a +// hash keyed on the other assembly's sequence names, which is the shape the lift functions +// want. Use this when the items were fetched some other way, so quickLiftSql was not the +// thing that collected the chains. +{ +struct hash *chainHash = newHash(8); + +quickLiftSourceRanges(quickLiftFile, chrom, start, end, chainHash); return chainHash; } struct slList *quickLiftSql(struct sqlConnection *conn, char *quickLiftFile, char *table, char *chrom, int start, int end, char *query, char *extraWhere, ItemLoader2 loader, int numFields,struct hash *chainHash) // retrieve items for which we have a loader from a SQL database for which we have a set quickLift chains. // Save the chains we used to map the item back to the current reference. { struct chain *chain, *chainList = quickLiftLoadChains(quickLiftFile, chrom, start, end); struct slList *item, *itemList = NULL; int rowOffset = 0; struct sqlResult *sr = NULL; char **row = NULL; for(chain = chainList; chain; chain = chain->next) @@ -582,30 +656,142 @@ if (psl->tSize != mapPsl->qSize) return NULL; struct psl *lifted = pslTransMap(pslTransMapNoOpts, psl, pslTypeUnspecified, mapPsl, pslTypeUnspecified); if (lifted != NULL) { // before counting, so quickLiftPslCounts sees both sides in the same units if (pslIsProtein(psl)) quickLiftPslBackToProtein(lifted); quickLiftPslCounts(psl, lifted); } return lifted; } +static struct chain *chainFromPsl(struct psl *psl) +/* The inverse of chainToPsl. Score and id are the caller's to fill in, since an alignment + * does not carry them. */ +{ +struct chain *chain; +struct cBlock *blockList = NULL, *b; +int i; + +AllocVar(chain); +chain->tName = cloneString(psl->tName); +chain->tSize = psl->tSize; +chain->tStart = psl->tStart; +chain->tEnd = psl->tEnd; +chain->qName = cloneString(psl->qName); +chain->qSize = psl->qSize; +chain->qStrand = psl->strand[0]; + +// chainToPsl turns the chain's query bounds the right way up for a psl, so turn them back +if (chain->qStrand == '-') + { + chain->qStart = psl->qSize - psl->qEnd; + chain->qEnd = psl->qSize - psl->qStart; + } +else + { + chain->qStart = psl->qStart; + chain->qEnd = psl->qEnd; + } + +for (i = psl->blockCount - 1; i >= 0; i--) + { + AllocVar(b); + b->tStart = psl->tStarts[i]; + b->tEnd = b->tStart + psl->blockSizes[i]; + b->qStart = psl->qStarts[i]; + b->qEnd = b->qStart + psl->blockSizes[i]; + slAddHead(&blockList, b); + } +chain->blockList = blockList; +return chain; +} + +boolean quickLiftIsOwnChainTrack(struct trackDb *tdb) +// TRUE when this is the chain track quickLift builds to show the lift itself. That stanza +// carries quickLiftUrl and quickLiftDb like any lifted track, but its data is already in +// reference coordinates and must not be lifted a second time. The giveaway is that its +// bigDataUrl IS the quickLift chain file. +{ +char *quickLiftFile = trackDbSetting(tdb, "quickLiftUrl"); + +if (quickLiftFile == NULL) + return FALSE; +if (startsWithNoCase("bigQuickLiftChain", tdb->type)) + return TRUE; + +char *bigDataUrl = trackDbSetting(tdb, "bigDataUrl"); +return (bigDataUrl != NULL) && sameString(bigDataUrl, quickLiftFile); +} + +struct chain *quickLiftChain(struct hash *chainHash, struct hash **pMapPsls, struct chain *chain) +// Map a chain's target side from the other assembly onto our current reference. A chain is +// an alignment between that assembly and some other species, so this composes the two and +// leaves a chain between the reference and that species. The query side is left alone. +// Returns NULL if the chain doesn't map. The chain handed in is not modified. +{ +// chainToPsl copies the header, and every chain loader leaves the header describing the +// whole chain while loading only the blocks that overlap the range asked for. Correct it +// for the conversion, then put it back: callers still want the whole-chain header, which +// is what the native details page reports. +int saveTStart = chain->tStart, saveTEnd = chain->tEnd; +int saveQStart = chain->qStart, saveQEnd = chain->qEnd; +struct cBlock *b = chain->blockList; + +if (b == NULL) + return NULL; + +int tStart = b->tStart, tEnd = b->tEnd, qStart = b->qStart, qEnd = b->qEnd; +for (; b != NULL; b = b->next) + { + if (b->tStart < tStart) + tStart = b->tStart; + if (b->tEnd > tEnd) + tEnd = b->tEnd; + if (b->qStart < qStart) + qStart = b->qStart; + if (b->qEnd > qEnd) + qEnd = b->qEnd; + } +chain->tStart = tStart; +chain->tEnd = tEnd; +chain->qStart = qStart; +chain->qEnd = qEnd; + +struct psl *psl = chainToPsl(chain); + +chain->tStart = saveTStart; +chain->tEnd = saveTEnd; +chain->qStart = saveQStart; +chain->qEnd = saveQEnd; + +struct psl *lifted = quickLiftPsl(chainHash, pMapPsls, psl); +pslFree(&psl); +if (lifted == NULL) + return NULL; + +struct chain *out = chainFromPsl(lifted); +out->score = chain->score; +out->id = chain->id; +pslFree(&lifted); +return out; +} + struct psl *quickLiftPsls(struct hash *chainHash, struct psl *pslList) // Map a list of alignments in the other assembly's coordinates onto our current reference. // Alignments that don't map are dropped. { struct psl *liftedList = NULL; struct psl *psl, *nextPsl; struct hash *mapPsls = NULL; for(psl = pslList; psl; psl = nextPsl) { nextPsl = psl->next; psl->next = NULL; struct psl *lifted = quickLiftPsl(chainHash, &mapPsls, psl); if (lifted != NULL)