68b9911e4c156cd1346fde5c957434b1a8780d1c max Wed Sep 9 08:49:07 2026 -0700 Show the transcript's own codon number where it differs from the genomic one The gene tracks count codons along the genome. A RefSeq transcript is a sequence in its own right, so where it has an insertion or a deletion relative to the assembly, every codon 3' of that point gets a different number here than the sequence provider gives it, one codon per three bases. DNM1 on canFam3 is the reported case: the transcript carries 21 bases canFam3 does not, so our p.249 is NCBI's p.256. Neither number is wrong, but HGVS c./p. is defined on the transcript, so the number people quote is the one we were not showing. The genomic number and amino acid are unchanged. Codons whose two numbers disagree now draw in the existing CDS_QUERY_INSERTION orange with a "!" after the codon number, and their mouseover adds the transcript number plus a link to a new FAQ entry. Both directions of indel are covered, and so is the case where the alignment does not reach the start of the CDS (801 transcripts on hg38), which needs the transcript's own CDS annotation as the anchor rather than the alignment. Numbers come from the transcript alignment, ncbiRefSeqPsl or refSeqAli, which is the same source hgvsMapToGenome already uses, so the browser now agrees with its own position search. Two queries per table per window, on the bin index, and only at zoomedToCdsColorLevel, where the mouseover carrying the numbers is drawn: on and off are within noise at every zoom. Gated by showTxCodonNumbers in hg.conf, default off, catalogued as a release gate. With it off nothing is looked up and the rendering and mouseover are byte-identical to before. refs #38298 diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c index 2d0fcbfe828..8babeeef356 100644 --- src/hg/hgTracks/simpleTracks.c +++ src/hg/hgTracks/simpleTracks.c @@ -3234,32 +3234,55 @@ char aaLetter = codon->codonAa; char aaAbbr[8]; char *aaName = NULL; if (aaLetter == '*') { safecpy(aaAbbr, sizeof(aaAbbr), "Ter"); aaName = "termination"; } else if (aaLetter == 'X') // error/partial codon: nothing to show aaAbbr[0] = '\0'; else { aaToAbbr(aaLetter, aaAbbr, sizeof(aaAbbr)); aaName = aaToName(aaLetter); } - dyStringPrintf(codonDy, "<b>Codon: </b> c.%d-%d (p.%d)<br>", + /* These numbers are counted along the genome, as this + * track has always counted them. Where the transcript + * has an indel relative to the genome the transcript's + * own numbering differs, so show both, and name which + * is which only in that case: for every other + * transcript there is one count and "Codon" says it. */ + boolean shifted = baseColorCodonIsShifted(codon); + dyStringPrintf(codonDy, "<b>Codon%s: </b> c.%d-%d (p.%d)<br>", + shifted ? " counted on the genome" : "", cStart, cEnd, pPos); + if (shifted) + { + int txCStart = (codon->txCodonIndex - 1) * 3 + 1; + dyStringPrintf(codonDy, + "<b>Counted on the transcript: </b> " + "c.%d-%d (p.%d)<br>", + txCStart, txCStart+2, codon->txCodonIndex); + dyStringPrintf(codonDy, + "<b>Note: </b>This transcript's sequence has an " + "indel relative to the genome, so the two " + "numbers differ. " + "<a target=_blank " + "href=\"../FAQ/FAQgenes.html#txIndel\">" + "Help</a><br>"); + } if (!isEmpty(aaAbbr)) { if (aaName != NULL) dyStringPrintf(codonDy, "<b>Amino acid: </b> %s - %s<br>", aaAbbr, aaName); else dyStringPrintf(codonDy, "<b>Amino acid: </b> %s<br>", aaAbbr); } } else if (lf->tallStart < lf->tallEnd) { // UTR block of a coding transcript (codonIndex 0, so no // c./p. above): label it with its HGVS UTR range. codonS/ // codonE span the whole UTR portion of this exon. boolean posStrand = (lf->orientation >= 0); int gFivePrime = posStrand ? codonS : codonE - 1; @@ -4519,31 +4542,40 @@ gp = (struct genePred *)(lf->original); boolean baseColorNeedsCodons = (drawOpt == baseColorDrawItemCodons || drawOpt == baseColorDrawDiffCodons || drawOpt == baseColorDrawGenomicCodons); if (psl && baseColorNeedsCodons) { boolean isXeno = ((tg->subType == lfSubXeno) || (tg->subType == lfSubChain) || startsWith("mrnaBla", tg->table)); int sizeMul = pslIsProtein(psl) ? 3 : 1; lf->codons = baseColorCodonsFromPsl(lf, psl, sizeMul, isXeno, maxShade, drawOpt, tg); } else if (drawOpt > baseColorDrawOff) { if (gp && gp->cdsStart != gp->cdsEnd) - lf->codons = baseColorCodonsFromGenePred(lf, gp, (drawOpt != baseColorDrawDiffCodons), cartUsualBooleanClosestToHome(cart, tg->tdb, FALSE, CODON_NUMBERING_SUFFIX, TRUE)); + { + /* Where the transcript has an indel relative to the genome, counting codons along + * the genome does not give the transcript's own codon numbers. This alignment is + * what lets each codon carry both numbers; NULL for a track with no alignment. */ + struct genbankCds txCds; + struct psl *txAli = baseColorTxAliForGenePred(tg, gp, &txCds); + lf->codons = baseColorCodonsFromGenePred(lf, gp, (drawOpt != baseColorDrawDiffCodons), + cartUsualBooleanClosestToHome(cart, tg->tdb, FALSE, CODON_NUMBERING_SUFFIX, TRUE), + txAli, &txCds); + } } if (psl && drawOpt == baseColorDrawCds && !zoomedToCdsColorLevel) baseColorSetCdsBounds(lf, psl, tg); tallStart = lf->tallStart; tallEnd = lf->tallEnd; if ((tallStart == 0 && tallEnd == 0) && lf->start != 0 && !sameWord(tg->table, "jaxQTL3")) { // sometimes a bed <8 will get passed off as a bed 8, tsk tsk tallStart = lf->start; tallEnd = lf->end; } int ourStart = lf->start; if (ourStart < winStart) ourStart = winStart;