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, "Codon: c.%d-%d (p.%d)
",
+ /* 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, "Codon%s: c.%d-%d (p.%d)
",
+ shifted ? " counted on the genome" : "",
cStart, cEnd, pPos);
+ if (shifted)
+ {
+ int txCStart = (codon->txCodonIndex - 1) * 3 + 1;
+ dyStringPrintf(codonDy,
+ "Counted on the transcript: "
+ "c.%d-%d (p.%d)
",
+ txCStart, txCStart+2, codon->txCodonIndex);
+ dyStringPrintf(codonDy,
+ "Note: This transcript's sequence has an "
+ "indel relative to the genome, so the two "
+ "numbers differ. "
+ ""
+ "Help
");
+ }
if (!isEmpty(aaAbbr))
{
if (aaName != NULL)
dyStringPrintf(codonDy, "Amino acid: %s - %s
", aaAbbr, aaName);
else
dyStringPrintf(codonDy, "Amino acid: %s
", 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;