f29b65452a1cca8a5bc62f007100b313e589d036
braney
  Sun Sep 6 15:11:36 2026 -0700
quickLift: lift a bigNet track, refs #20824

A net is not an ordinary track to lift.  It is an alignment of two assemblies,
so only its target side moves to the new reference; the query side names a third
assembly and is carried across untouched.  And the browser draws a net by
recursion, so what really has to survive the lift is the tree: a row's level only
means anything relative to the row above it.

chainNetLoadRangeQuickLift() maps each row's target range with
quickLiftIntervalsToBedClip, which is the same code every other quickLift track
uses, so a net row lands where a bed of the same span would.  The surviving rows
then go to the same helpToNet() the unlifted path uses, so there is one tree
builder and not two.  The row collection either path does is now cnlHelperNew and
cnlHelperAddBigNet.

validateOneTdb lets bigNet into a quickLift hub.  Its chain track is not offered,
so a lifted net's details page has no chain to follow and says so rather than
looking for a track this assembly does not have.  bigNetLoadOne lifts the same
way for that page, unclipped, so it reports the item's whole extent.

bigNetFromInterval passes -1 as the cached chromId.  bbiCachedChromLookup leaves
the buffer alone when the id matches the one before it, so a cache that outlives
the buffer hands back stale bytes.

Measured against the standalone liftOver tool, on hg19 chr22's mouse net lifted
to hg38: 36 of 36 source rows in the sampled window land on the same coordinates,
counting the one liftOver will not take whole, whose two ends it does place
exactly where the browser puts them.  Against hg38's own mouse net, computed
independently, 1.02% of the drawn pixels differ.

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index efca28e8780..f0072ae3682 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -4222,53 +4222,71 @@
 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)
 /* Load the record from a bigNet file at the given level that covers start.
  * Returns NULL if there isn't one. */
 {
 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 bigBedInterval *bb, *bbList = bigBedIntervalQuery(bbi, chrom, start, start+1, 0, lm);
-char *bedRow[BIGNET_NUM_COLS];
-char startBuf[16], endBuf[16];
+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;
-    int fieldCount = bigBedIntervalToRow(bb, chrom, 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);
+    unsigned tStart = bb->start, tEnd = bb->end;
+
+    if (quickLiftFile != NULL)
+        {
+        /* The unclipped lift, so the details page reports the item's whole extent. */
+        struct bed *bed = quickLiftIntervalsToBed(bbi, chainHash, bb);
+        if ((bed == NULL) || !sameString(bed->chrom, chrom))
+            continue;
+        tStart = bed->chromStart;
+        tEnd = bed->chromEnd;
+        }
+    if ((tStart > start) || (tEnd <= start))
+        continue;
+    bigNetFromInterval(bbi, bb, fileName, &bn);
     if (bn.level != level)
         continue;
     AllocVar(na);
     na->level = bn.level;
-    na->tName = cloneString(bn.chrom);
-    na->tStart = bn.chromStart;
-    na->tEnd = bn.chromEnd;
+    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;
     na->type = cloneString(bn.type);
     na->tN = bn.tN;
     na->qN = bn.qN;
     na->tR = bn.tR;
     na->qR = bn.qR;
@@ -4284,81 +4302,97 @@
 lmCleanup(&lm);
 return na;
 }
 
 static char *netChainTrackName(struct trackDb *tdb, char *chainTrack)
 /* The type line of a net track names its chain track without any hub prefix.
  * If the net track came from a hub, so did the chain track it names. */
 {
 if (!isHubTrack(tdb->track))
     return chainTrack;
 char buf[256];
 safef(buf, sizeof buf, "hub_%d_%s", hubIdFromTrackName(tdb->track), chainTrack);
 return cloneString(buf);
 }
 
+static struct trackDb *netChainTdb(char *chainTrack)
+/* The tdb of the chain track a net track's type line names, or NULL if this assembly
+ * has no such track.  A quickLifted net is the case with none: the net comes across on
+ * its own and its chain track stays behind on the source assembly. */
+{
+return (trackHash == NULL) ? NULL : hashFindVal(trackHash, chainTrack);
+}
+
 void genericNetClick(struct sqlConnection *conn, struct trackDb *tdb,
                      char *item, int start, char *otherDb, char *chainTrack)
 /* Generic click handler for net tracks. */
 {
 char table[HDB_MAX_TABLE_STRING];
 boolean hasBin;
 char query[256];
 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;
 
-if (isBig)
+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));
     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);
 
     net = netAlignLoad(row+hasBin);
     sqlFreeResult(&sr);
     }
 tSize = net->tEnd - net->tStart;
 qSize = net->qEnd - net->qStart;
 
-if (net->chainId != 0)
+if ((net->chainId != 0) && (!isBig || (chainTdb != NULL)))
     {
     netWinSize = min(winEnd-winStart, net->tEnd - net->tStart);
     printf("<BR>\n");
     /* Show alignment if the database exists and */
     /* if there is a chromInfo table for that database and the sequence */
     /* file exists. This means that alignments can be shown on the archive */
     /* server (or in other cases) if there is a database with a chromInfo */
     /* table, the sequences are available and there is an entry added to */
     /* dbDb for the otherDb. */
     if (chromSeqFileExists(otherDb, net->qName))
         {
         if (netWinSize < 1000000)
 	    {
 	    int ns = max(winStart, net->tStart);
 	    int ne = min(winEnd, net->tEnd);
@@ -4371,43 +4405,50 @@
 	        }
 	    else
 	        {
 	        printf("Odd, net not in window<BR>\n");
 	        }
 	    }
         else
 	    {
 	    printf("To see alignment details zoom so that the browser window covers 1,000,000 bases or less.<BR>\n");
 	    }
         }
     if (isBig)
         {
         char idBuf[32];
         safef(idBuf, sizeof idBuf, "%u", net->chainId);
-        chain = chainLoadItemInRange(getTdbForTrackName(chainTrack), idBuf);
+        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"));
+    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)
 	printLabeledNumber(otherOrg, "parent overlap", net->qOver);
     if (net->qFar >= 0)