02710f0107a6b154d9e5689165941ce724cb4c6a braney Fri Sep 4 13:38:54 2026 -0700 quickLift: fix the seams a second review pass found quickLiftPslBackToProtein left two things wrong. pslTransMap can hand back strand[0] == '-', because it reverse complements the input when the two alignments disagree about the strand of the sequence they share, and forcing strand[1] to '+' on top of that produced "-+". A protein psl is only ever "++" or "+-", and pslShow reads strand[0] == '-' as "reverse complement the query", so it would have reverse complemented a protein as though it were DNA. It now turns the alignment over so the minus lands on the target side. qBaseInsert is in nucleotides like everything else being divided, so it comes down too, and it joins the divisibility guard: without it the result failed pslCheck and the number was printed verbatim on the details page. Adding that back-conversion made a comment in pslTrack.c false. The lift no longer always returns an untranslated alignment, so the drawing code has to ask rather than assume, the way bigBedTrack.c already did. A quickLifted protein psl track was drawing every block at a third of its length. No such track exists on hg19 or hg38 today, so this was latent. The normalized score on the chain details page was read from the assembly on screen. Where that assembly has no such table the page died; where it has a table of the same name, which is the common case for a self or a well known chain track, it silently returned some other assembly's chain and printed a blank score. It now reads the assembly the chain came from, on a connection to it. Two smaller things: htcBigPslAli guarded its connection with trackHubDatabase alone, but a GenArk accession does not start with hub_, so it matches the guard genericClickHandlerPlus already uses; and the table name tests in cds.c now skip the hub prefix the way the ones in hgc.c were changed to, so a lifted refSeqAli reaches its special case. The chain item label took its start from the source chain and its strand character from the lifted one. Both now come from the source chain. refs #38249 diff --git src/hg/hgTracks/pslTrack.c src/hg/hgTracks/pslTrack.c index b7380a339e6..d5fb7c4d4ce 100644 --- src/hg/hgTracks/pslTrack.c +++ src/hg/hgTracks/pslTrack.c @@ -422,34 +422,34 @@ struct hash *chainHash = newHash(8); struct sqlConnection *conn = hAllocConn(liftDb); struct psl *pslList = (struct psl *)quickLiftSql(conn, quickLiftFile, table, chrom, start, end, NULL, pslChromFilterWhere(tg, extraWhere, sizeof(extraWhere)), pslRowLoader, 21, chainHash); hFreeConn(&conn); struct linkedFeatures *lfList = NULL; struct psl *psl, *nextPsl; for(psl = quickLiftPsls(chainHash, pslList); psl != NULL; psl = nextPsl) { nextPsl = psl->next; psl->next = NULL; // lfFromPslx hangs on to the psl, so don't leave it in a list - // sizeMul is 1 whatever the caller passed: the lift returns an untranslated - // alignment, and it puts a protein alignment into nucleotide space on the way, so the - // block sizes are already in bases. - slAddHead(&lfList, lfFromPslx(psl, 1, isXeno, nameGetsPos, tg)); + // The caller's sizeMul does not survive the lift, so ask this alignment what space its + // blocks are in. A protein alignment comes back in protein units when the lift left + // its codons whole, and in bases when it did not. + slAddHead(&lfList, lfFromPslx(psl, pslIsProtein(psl) ? 3 : 1, isXeno, nameGetsPos, tg)); } finishPslLfList(tg, lfList); } static void lfFromPslsInRange(struct track *tg, int start, int end, char *chrom, boolean isXeno, boolean nameGetsPos, int sizeMul) /* Return linked features from range of table. */ { if (quickLiftIsLifted(tg->tdb)) { quickLiftLfFromPsls(tg, chrom, start, end, isXeno, nameGetsPos); return; } struct sqlConnection *conn = hAllocConn(database);