357d4dbeca6b3bbb59f60185f6b833d74fd74fbc braney Sun Sep 6 15:51:56 2026 -0700 bigNet: four fixes from the code review, refs #20824 A quickLifted net's details page lifted the row unclipped so it could report the whole extent, but the image lifts clipped. An item too big for the chains loaded in the window is drawn clipped and was then unfindable on click, which puts a box on screen that says it is not there. Try the unclipped lift, fall back to the clipped one, and say plainly when the numbers describe only the part that could be placed. quickLiftGetIntervals can return one source row twice, through two chains whose padded query ranges overlap. helpToNet cannot tell two identical parents apart: the second inherits no children and then draws as one solid box over the first one's gaps. A level, a target range and a chain id name a row in a net, so that is enough to recognize the repeat and drop it. Preventive -- no duplicate was observed in the window measured. The sentence explaining why a lifted net has no alignment to show printed quickLiftDb twice, and a hub can set quickLiftUrl and leave quickLiftDb unset, so it could be handed a null. One printf, and it reads correctly either way. Free the per-row bed in both lift loops. It is about ninety thousand of them on a whole chromosome, which is more than a CGI should be asked to shrug off. Rendering is unchanged: the unlifted net still draws pixel for pixel like the native netAlign track at three widths, every lifted figure but the details page is pixel-identical to the one built before these fixes, and the 36 of 36 agreement with the standalone liftOver tool is unchanged. diff --git src/hg/lib/chainNetDbLoad.c src/hg/lib/chainNetDbLoad.c index f5d5dba18b4..2f2c08c79f9 100644 --- src/hg/lib/chainNetDbLoad.c +++ src/hg/lib/chainNetDbLoad.c @@ -292,60 +292,85 @@ char *bedRow[BIGNET_NUM_COLS]; char startBuf[16], endBuf[16]; char chromName[256]; int fieldCount; /* -1 rather than a remembered chromId: bbiCachedChromLookup leaves the buffer alone * when the id matches, so a cache that outlives the buffer returns stale bytes. */ bbiCachedChromLookup(bbi, bb->chromId, -1, chromName, sizeof(chromName)); fieldCount = bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow)); if (fieldCount != BIGNET_NUM_COLS) errAbort("%s has %d fields, bigNet needs %d", fileName, fieldCount, BIGNET_NUM_COLS); bigNetStaticLoad(bedRow, bn); return bn; } +static boolean cnlSeenBefore(struct hash *seen, struct bigNet *bn, int tStart, int tEnd) +/* Has this row already been added? quickLiftGetIntervals can return one source row + * twice, through two chains whose padded query ranges overlap, and helpToNet cannot + * tell two identical parents apart: the second inherits no children and then draws as + * one solid box over the first one's gaps. A level, a target range and a chain id + * together name a row in a net, so they are enough to recognize the repeat. */ +{ +char key[128]; + +safef(key, sizeof key, "%u:%d:%d:%u", bn->level, tStart, tEnd, bn->chainId); +if (hashLookup(seen, key) != NULL) + return TRUE; +hashAdd(seen, key, NULL); +return FALSE; +} + struct chainNet *chainNetLoadRangeQuickLift(char *quickLiftFile, char *fileName, char *chrom, int start, int end) /* Load the part of a bigNet file that quickLifts into chrom:start-end, and build a * chainNet in the destination assembly's coordinates. Only the target side of the net * moves; the query side describes a third assembly and is carried across untouched. * Note the net->size field is not filled in. */ { struct bbiFile *bbi = bigBedFileOpenAlias(fileName, chromAliasFindAliases); struct hash *chainHash = NULL; struct bigBedInterval *bb, *bbList = quickLiftGetIntervals(quickLiftFile, bbi, chrom, start, end, &chainHash); struct cnlHelper *help = NULL; +struct hash *seen = hashNew(0); struct chainNet *net; for (bb = bbList; bb != NULL; bb = bb->next) { /* Lift through the same code every other quickLift track uses, so a net row lands * where a bed of the same span would. Only the target range comes from the lifted * bed; the rest of the row is read from the interval it came from. */ struct bed *bed = quickLiftIntervalsToBedClip(bbi, chainHash, bb); struct bigNet bn; - if ((bed == NULL) || !sameString(bed->chrom, chrom)) + if (bed == NULL) continue; + if (sameString(bed->chrom, chrom)) + { bigNetFromInterval(bbi, bb, fileName, &bn); + if (!cnlSeenBefore(seen, &bn, bed->chromStart, bed->chromEnd)) + { if (help == NULL) help = cnlHelperNew(chrom); cnlHelperAddBigNet(help, fileName, &bn, bed->chromStart, bed->chromEnd); } + } + bedFree(&bed); + } +hashFree(&seen); bbiFileClose(&bbi); if (help == NULL) return NULL; net = helpToNet(&help); return net; } struct chainNet *chainNetLoadChrom(char *database, char *track, char *chrom, char *extraWhere) /* Load net on whole chromosome. */ { int rowOffset; struct sqlConnection *conn; struct sqlResult *sr; struct chainNet *net;