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/hgc/hgc.c src/hg/hgc/hgc.c
index f0072ae3682..c0caf9e6b10 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -4217,71 +4217,90 @@
 
 void printLabeledPercent(char *org, char *label, long p, long q)
 /* Print label: in bold, then p, and then 100 * p/q */
 {
 char *space = " ";
 if (org == NULL)
     org = space = "";
 printf("<B>%s%s%s:</B> ", org, space, label);
 printLongWithCommas(stdout, p);
 if (q != 0)
     printf(" (%3.1f%%)", 100.0 * p / q);
 printf("<BR>\n");
 }
 
 static struct netAlign *bigNetLoadOne(struct trackDb *tdb, char *chrom, int start,
-                                      unsigned level)
+                                      unsigned level, boolean *retClipped)
 /* Load the record from a bigNet file at the given level that covers start.
- * Returns NULL if there isn't one. */
+ * Returns NULL if there isn't one.  Sets *retClipped when a quickLifted row would
+ * only lift with its ends pulled in, so the extent reported is the visible part
+ * rather than the whole item. */
 {
 char *fileName = hReplaceGbdb(trackDbSetting(tdb, "bigDataUrl"));
 char *quickLiftFile = trackDbSetting(tdb, "quickLiftUrl");
 if (fileName == NULL)
     errAbort("No bigDataUrl in track %s", tdb->track);
 struct lm *lm = lmInit(0);
 struct bbiFile *bbi = bigBedFileOpenAlias(fileName, chromAliasFindAliases);
 struct hash *chainHash = NULL;
 struct bigBedInterval *bb, *bbList;
 struct netAlign *na = NULL;
 
 /* A quickLifted net is read over the whole window, because that is the lift the image
  * was drawn from, and each row is then tested against the clicked base once it has
  * landed. */
 if (quickLiftFile != NULL)
     bbList = quickLiftGetIntervals(quickLiftFile, bbi, chrom, winStart, winEnd, &chainHash);
 else
     bbList = bigBedIntervalQuery(bbi, chrom, start, start+1, 0, lm);
 
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
     struct bigNet bn;
     unsigned tStart = bb->start, tEnd = bb->end;
+    boolean thisClipped = FALSE;
 
     if (quickLiftFile != NULL)
         {
-        /* The unclipped lift, so the details page reports the item's whole extent. */
+        /* The unclipped lift first, so the page can report the item's whole extent.
+         * An item too big for the chains loaded here lifts only with its ends pulled
+         * in, and that is how the image drew it, so fall back to the clipped lift
+         * rather than reporting an item the reader can plainly see as missing. */
         struct bed *bed = quickLiftIntervalsToBed(bbi, chainHash, bb);
-        if ((bed == NULL) || !sameString(bed->chrom, chrom))
+        if (bed == NULL)
+            {
+            bed = quickLiftIntervalsToBedClip(bbi, chainHash, bb);
+            thisClipped = TRUE;
+            }
+        if (bed == NULL)
+            continue;
+        if (!sameString(bed->chrom, chrom))
+            {
+            bedFree(&bed);
             continue;
+            }
         tStart = bed->chromStart;
         tEnd = bed->chromEnd;
+        bedFree(&bed);
         }
     if ((tStart > start) || (tEnd <= start))
         continue;
     bigNetFromInterval(bbi, bb, fileName, &bn);
     if (bn.level != level)
         continue;
+    if (retClipped != NULL)
+        *retClipped = thisClipped;
     AllocVar(na);
     na->level = bn.level;
     na->tName = cloneString(chrom);
     na->tStart = tStart;
     na->tEnd = tEnd;
     safecpy(na->strand, sizeof na->strand, bn.strand);
     na->qName = cloneString(bn.name);
     na->qStart = bn.qStart;
     na->qEnd = bn.qEnd;
     na->chainId = bn.chainId;
     na->ali = bn.ali;
     na->score = bn.chainScore;
     na->qOver = bn.qOver;
     na->qFar = bn.qFar;
     na->qDup = bn.qDup;
@@ -4332,45 +4351,48 @@
 struct sqlResult *sr;
 char **row;
 struct netAlign *net;
 char *org = hOrganism(database);
 char *otherOrg = hOrganism(otherDb);
 char *otherOrgBrowser = otherOrg;
 int tSize, qSize;
 int netWinSize;
 struct chain *chain;
 boolean isBig = startsWith("big", tdb->type);
 /* A quickLifted net has moved to this assembly on its own.  The chain track its type
  * line names is still on the source assembly, so there is no alignment to show and no
  * chain to follow -- only the net itself lifted. */
 boolean isLifted = isBig && (trackDbSetting(tdb, "quickLiftUrl") != NULL);
 struct trackDb *chainTdb = NULL;
+/* Set when a lifted row would only lift with its ends pulled in to the chains we
+ * loaded, so the numbers below describe the visible part and not the whole item. */
+boolean clipped = FALSE;
 
 if (isBig && !isLifted)
     {
     chainTrack = netChainTrackName(tdb, chainTrack);
     chainTdb = netChainTdb(chainTrack);
     }
 
 if (otherOrg == NULL)
     {
     /* use first word in short track label */
     otherOrg = firstWordInLine(cloneString(tdb->shortLabel));
     }
 if (isBig)
     {
-    net = bigNetLoadOne(tdb, seqName, start, sqlUnsigned(item));
+    net = bigNetLoadOne(tdb, seqName, start, sqlUnsigned(item), &clipped);
     if (net == NULL)
         errAbort("Couldn't find %s:%d at level %s in %s", seqName, start, item, tdb->track);
     }
 else
     {
     if (!hFindSplitTable(database, seqName, tdb->table, table, sizeof table, &hasBin))
         errAbort("genericNetClick track %s not found", tdb->table);
     sqlSafef(query, sizeof(query),
              "select * from %s where tName = '%s' and tStart <= %d and tEnd > %d "
              "and level = %s",
              table, seqName, start, start, item);
     sr = sqlGetResult(conn, query);
     if ((row = sqlNextRow(sr)) == NULL)
         errAbort("Couldn't find %s:%d in %s", seqName, start, table);
 
@@ -4420,33 +4442,34 @@
         chain = chainLoadItemInRange(chainTdb, idBuf);
         }
     else
         chain = chainDbLoad(conn, database, chainTrack, seqName, net->chainId);
     if (chain != NULL)
         {
          /* print link to browser for otherDb only if otherDb is active */
         if (hDbIsActive(otherDb))
 	    chainToOtherBrowser(chain, otherDb, otherOrgBrowser, NULL);
 	chainFree(&chain);
 	}
     htmlHorizontalLine();
     }
 else if ((net->chainId != 0) && isLifted)
     {
-    printf("<BR>This net was lifted to %s from %s.  Its chains are in %s, so the "
-           "alignment cannot be shown here.<BR>\n",
-           database, trackDbSetting(tdb, "quickLiftDb"), trackDbSetting(tdb, "quickLiftDb"));
+    char *sourceDb = trackDbSetting(tdb, "quickLiftDb");
+    printf("<BR>This net was lifted from %s, so its chains are not on this assembly "
+           "and the alignment cannot be shown here.<BR>\n",
+           isEmpty(sourceDb) ? "another assembly" : sourceDb);
     htmlHorizontalLine();
     }
 printf("<B>Type:</B> %s<BR>\n", net->type);
 printf("<B>Level:</B> %d<BR>\n", (net->level+1)/2);
 printf("<B>%s position:</B> %s:%d-%d<BR>\n",
        org, net->tName, net->tStart+1, net->tEnd);
 printf("<B>%s position:</B> %s:%d-%d<BR>\n",
        otherOrg, net->qName, net->qStart+1, net->qEnd);
 printf("<B>Strand:</B> %c<BR>\n", net->strand[0]);
 printLabeledNumber(NULL, "Score", net->score);
 if (net->chainId)
     {
     printf("<B>Chain ID:</B> %u<BR>\n", net->chainId);
     printLabeledNumber(NULL, "Bases aligning", net->ali);
     if (net->qOver >= 0)
@@ -4468,30 +4491,34 @@
     printLabeledPercent(org, "RepeatMasker bases", net->tR, tSize);
 if (net->qR >= 0)
     printLabeledPercent(otherOrg, "RepeatMasker bases", net->qR, qSize);
 if (net->tOldR >= 0)
     printLabeledPercent(org, "old repeat bases", net->tOldR, tSize);
 if (net->qOldR >= 0)
     printLabeledPercent(otherOrg, "old repeat bases", net->qOldR, qSize);
 if (net->tNewR >= 0)
     printLabeledPercent(org, "new repeat bases", net->tOldR, tSize);
 if (net->qNewR >= 0)
     printLabeledPercent(otherOrg, "new repeat bases", net->qOldR, qSize);
 if (net->tEnd >= net->tStart)
     printLabeledNumber(org, "size", net->tEnd - net->tStart);
 if (net->qEnd >= net->qStart)
     printLabeledNumber(otherOrg, "size", net->qEnd - net->qStart);
+if (clipped)
+    printf("<BR>Fields above refer to the part of this chain or gap that could be "
+           "placed on this assembly, not to the whole of it.<BR>\n");
+else
     printf("<BR>Fields above refer to entire chain or gap, not just the part inside the window.<BR>\n");
 netAlignFree(&net);
 }
 
 void tfbsConsSites(struct trackDb *tdb, char *item)
 /* detail page for tfbsConsSites track */
 {
 boolean printedPlus = FALSE;
 boolean printedMinus = FALSE;
 int start = cartInt(cart, "o");
 struct sqlConnection *conn = hAllocConn(database);
 char table[HDB_MAX_TABLE_STRING];
 boolean hasBin;
 char query[512];
 struct sqlResult *sr;