7f65d3da8f2138d847f15a8398f8dcee1a079027 braney Fri Aug 7 12:07:59 2026 -0700 quickLift: clip an oversized item to the chains we loaded instead of dropping it, refs #38042 quickLift loads chains for the window plus padding, capped at 1 Mb. An item that reaches further than that has ends where no chain reaches, remapRangeList can place neither of them, and the whole item is dropped even though the part on screen maps fine. ClinVar copy number variants run to 159 Mb, so 44 of the 46 in one window disappeared. Pull the ends in to what the chains cover before mapping. The browser never draws the ends of an item that spans the window, so their exact position does not matter, and the existing spanned-item merge still reports it. A clipped end is snapped to a base inside an aligned block, since remapRangeList will only place a coordinate that lands on real alignment. The clip is in the quickLift code, not the shared liftOver remap path. Only hgTracks asks for it, so the details page keeps the item's true extent. Gated by quickLiftClipToChains, on by default. diff --git src/hg/lib/quickLift.c src/hg/lib/quickLift.c index 03984ea5535..03781faa09c 100644 --- src/hg/lib/quickLift.c +++ src/hg/lib/quickLift.c @@ -119,49 +119,153 @@ } return bbList; } static void make12(struct bed *bed) /* Make a bed12 out of something less than that. */ { bed->blockCount = 1; bed->blockSizes = needMem(sizeof(int)); bed->blockSizes[0] = bed->chromEnd - bed->chromStart; bed->chromStarts = needMem(sizeof(int)); bed->chromStarts[0] = 0; } +static int snapToBlock(struct chain *chain, int pos, boolean forward) +/* remapRangeList will only place a coordinate that falls inside an aligned block, so a + * clipped end has to land on real alignment, not merely inside the chain. Return pos if + * it is already aligned, otherwise the nearest aligned coordinate looking forward (for a + * start) or backward (for an end). Return -1 if there is no such coordinate. + * The asymmetry matches remapRangeList: a start needs b->tStart <= start < b->tEnd, so + * b->tStart is legal; an end needs b->tStart < end <= b->tEnd, so b->tEnd is legal. */ +{ +struct cBlock *b, *prev = NULL; + +for (b = chain->blockList; b != NULL; b = b->next) + { + if ((b->tStart <= pos) && (pos < b->tEnd)) + return pos; + if (forward && (b->tStart > pos)) + return b->tStart; + if (b->tEnd <= pos) + prev = b; + } + +return (!forward && (prev != NULL)) ? prev->tEnd : -1; +} + +static boolean clipBedToChains(struct hash *chainHash, struct bed *bed) +/* An item can be far bigger than the region we loaded chains for -- ClinVar has copy + * number variants spanning most of a chromosome. Its ends then sit where no chain + * reaches, remapRangeList can place neither of them, and the whole item is dropped even + * though the part on screen maps perfectly well. Pull the ends in to the nearest aligned + * base so the visible part can lift. Return TRUE if the item was clipped. */ +{ +struct chain *chain = liftOverChainForRange(chainHash, bed->chrom, + bed->chromStart, bed->chromEnd); +if (chain == NULL) + return FALSE; // nothing covers it, let it fail the way it used to + +int newStart = snapToBlock(chain, bed->chromStart, TRUE); +int newEnd = snapToBlock(chain, bed->chromEnd, FALSE); + +if ((newStart < 0) || (newEnd < 0) || (newStart >= newEnd)) + return FALSE; +if ((newStart == bed->chromStart) && (newEnd == bed->chromEnd)) + return FALSE; // both ends already sit on alignment, nothing to do + +/* Trim the blocks to the new range. Blocks are in ascending order, so walk them and + * keep the part that survives; a block entirely outside the range is dropped. */ +if (bed->blockCount > 0) + { + int i, keep = 0; + for (i = 0; i < bed->blockCount; ++i) + { + int bStart = bed->chromStart + bed->chromStarts[i]; + int bEnd = bStart + bed->blockSizes[i]; + + if (bStart < newStart) + bStart = newStart; + if (bEnd > newEnd) + bEnd = newEnd; + if (bStart >= bEnd) + continue; + bed->chromStarts[keep] = bStart - newStart; + bed->blockSizes[keep] = bEnd - bStart; + keep++; + } + if (keep == 0) + return FALSE; + bed->blockCount = keep; + } + +bed->chromStart = newStart; +bed->chromEnd = newEnd; +if (bed->thickStart < newStart) + bed->thickStart = newStart; +if (bed->thickEnd > newEnd) + bed->thickEnd = newEnd; +if (bed->thickStart > bed->thickEnd) + bed->thickStart = bed->thickEnd; + +return TRUE; +} + +static struct bed *quickLiftBed(struct bbiFile *bbi, struct hash *chainHash, struct bigBedInterval *bb, boolean clip); + struct bed *quickLiftIntervalsToBed(struct bbiFile *bbi, struct hash *chainHash, struct bigBedInterval *bb) /* Using chains stored in chainHash, port a bigBedInterval from another assembly to a bed * on the reference. */ { +return quickLiftBed(bbi, chainHash, bb, FALSE); +} + +struct bed *quickLiftIntervalsToBedClip(struct bbiFile *bbi, struct hash *chainHash, struct bigBedInterval *bb) +/* Like quickLiftIntervalsToBed, but an item too big for the chains we loaded is pulled in + * to what they cover rather than dropped. Callers that need the item's true extent (the + * details page) should use quickLiftIntervalsToBed instead. */ +{ +// quickLiftClipToChains=off restores the old behavior, where an item whose ends fall +// outside the chains we loaded is dropped instead of being pulled in. +boolean clip = cfgOptionBooleanDefault("quickLiftClipToChains", TRUE); + +return quickLiftBed(bbi, chainHash, bb, clip); +} + +static struct bed *quickLiftBed(struct bbiFile *bbi, struct hash *chainHash, struct bigBedInterval *bb, boolean clip) +/* Port a bigBedInterval to a bed on the reference. If clip, an item too big for the + * chains we loaded is pulled in to what they cover rather than dropped. */ +{ char startBuf[16], endBuf[16]; char *bedRow[bbi->fieldCount]; char chromName[256]; static int lastChromId = -1; bbiCachedChromLookup(bbi, bb->chromId, lastChromId, chromName, sizeof(chromName)); bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow)); struct bed *bed = bedLoadN(bedRow, bbi->definedFieldCount); char *error; if (bbi->definedFieldCount < 12) make12(bed); +if (clip) + clipBedToChains(chainHash, bed); + if ((error = remapBlockedBed(chainHash, bed, 0.0, 0.1, TRUE, TRUE, NULL, NULL)) == NULL) return bed; //else //printf("bed %s error:%s<BR>", bed->name, error); return NULL; } char *quickLiftGetChainPath(struct cart *cart, char *fromDb, char *toDb) /* Return the path from the quickLiftChain table for given assemblies. */ { if (!quickLiftEnabled(cart)) return 0; struct sqlConnection *conn = hConnectCentral();