5dd36916814777c4c330de1b41b33c33fbcf758d braney Wed Sep 2 17:42:43 2026 -0700 hgTracks: place a quickLift bigWig block by the window, not by the chain bigWigLoadPreDraw scaled each aligned block by the span of its own chain, but the preDraw buffer covers the window. A chain that only partly covers the view was therefore stretched across the whole image, and when several chains were in the window each stretched independently and overwrote the others. Scale by (winEnd - winStart) instead. chainSubsetOnT clips the blocks to the window, so a block cannot reach past the end of the buffer, but summary[] is exactly summarySize long with no slack, so clamp anyway. Measured on a quickLift from hg38 to hs1, at hs1 chr7:59,324,000-59,326,000. One chain covers 429 of those 2,000 bases, which is x 516 to 717 of the image. Before, the signal was painted from x 156 to 1098, so 625 columns carried a value where nothing is aligned. After, it is painted from x 516 to 717 and no column outside an aligned block carries a value. Same result on a 100 kb view with four chains in it: 24 such columns before, none after. A window whose chain reaches both edges, which is the ordinary case, renders pixel for pixel as it did. The second half of the ticket, the bigWig zoom level being chosen from query coordinates while summarySizeBlock comes from target coordinates, is not a bug. A cBlock has the same length on both sides by construction, so the two agree. refs #37621 diff --git src/hg/hgTracks/bigWigTrack.c src/hg/hgTracks/bigWigTrack.c index e4933f116ff..fc17e92cec8 100644 --- src/hg/hgTracks/bigWigTrack.c +++ src/hg/hgTracks/bigWigTrack.c @@ -70,63 +70,65 @@ struct bbiFile *bbiFile, *bbiNext; for(bbiFile = tg->bbiFile; bbiFile ; bbiFile = bbiNext) { struct preDrawContainer *pre = initPreDrawContainer(width); slAddHead(&preDrawList, pre); if (quickLiftFile != NULL) { char *linkFileName = bigChainGetLinkFile(quickLiftFile); // get the chain that maps to our window coordinates struct chain *chain, *chainList = chainLoadIdRangeHub(NULL, quickLiftFile, linkFileName, chromName, winStart, winEnd, -1); // go through each block of each chain and grab a summary from the query coordinates - // FIXME (refs #37621): quickLift bigWig summary issues. - // 1. summaryOffset/summarySizeBlock are scaled by retChain->tSize, not - // (winEnd - winStart). When a chain only partially covers the visible - // window, its blocks get stretched to fill the full preDraw buffer - // instead of landing at the correct window pixels. With multiple chains - // in chainList, each stretches independently and overwrites the others. - // 2. summarySizeBlock is computed from target span but bigWigSummaryArrayExtended - // is called with query coordinates; the bigWig zoom level it selects is - // based on (qEnd-qStart)/summarySizeBlock, which can pick the wrong zoom - // when target and query block lengths differ significantly (indels). for(chain = chainList; chain; chain = chain->next) { struct chain *retChain, *retChainToFree; char *chrom = chain->qName; chainSubsetOnT(chain, winStart, winEnd, &retChain, &retChainToFree); // chainLoadIdRangeHub returns every chain whose span overlaps the window, // but only loads the links that fall inside it. A chain can therefore // reach across the window with no aligned block in it, in which case // chainSubsetOnT has nothing to subset and hands back NULL. if (retChain == NULL) continue; struct cBlock *cb; for(cb = retChain->blockList; cb; cb = cb->next) { - // figure out where in the summary array the target coordinates put us - int tSize = retChain->tEnd - retChain->tStart; - int summaryOffset = (((double)cb->tStart - retChain->tStart) / tSize ) * summarySize; + // Figure out where in the summary array the target coordinates put us. + // preDraw covers the WINDOW, so the block is placed relative to the + // window and not to the chain. Scaling by the chain's own span stretched + // every partially covering chain across the whole buffer, and with more + // than one chain in the window they overwrote each other (refs #37621). + int tSize = winEnd - winStart; + int summaryOffset = (((double)cb->tStart - winStart) / tSize ) * summarySize; int summarySizeBlock = (((double)cb->tEnd - cb->tStart) / tSize ) * summarySize; + // chainSubsetOnT clips the blocks to the window, so a block cannot reach + // past the end of the buffer. summary[] is exactly summarySize long with + // no slack, so keep the clamp rather than trust that. + if (summaryOffset < 0) + summaryOffset = 0; + if (summaryOffset + summarySizeBlock > summarySize) + summarySizeBlock = summarySize - summaryOffset; + // grab the data using query coordinates - if (summarySizeBlock != 0) + if (summarySizeBlock > 0) { int start = cb->qStart; int end = cb->qEnd; boolean flip = FALSE; if (chain->qStrand == '-') { start = chain->qSize - cb->qEnd; end = chain->qSize - cb->qStart; flip = TRUE; } summaryToPreDraw(bbiFile, chrom, start, end, summarySizeBlock, &summary[summaryOffset], &pre->preDraw[summaryOffset], pre->preDrawZero, flip); } } }