4aa77873cb618e254b2f90d091d7bdef844947c2
max
  Wed Jul 8 04:28:42 2026 -0700
Let assembly hubs assign genetic codes per sequence for amino acid display

Adds a "codonTable" genomes.txt setting, e.g. "codonTable default=1
NC_017929.1=13", so an assembly hub can pick the NCBI translation table used to
show amino acids for each sequence.  New hGeneticCodeForChrom(db, chrom) in
hdb.c resolves the code (per-db cached), falling back to the previous behavior:
chrM/chrMT use the vertebrate mitochondrial code, everything else the standard
code.

Wired into the two browser display paths, which both go through cds.c's
baseColorLookupCodon: the base position track three-frame translation and
codon-colored annotation tracks such as gene predictions (also PSL/BAM).  Also
used for the hgc SNP amino acid details, and genePredTranslate gains a db
parameter (genePredToProt gains an optional -db flag) so command-line
translation can honor the same setting.  Documented in assemblyHubHelp.html.
refs #16550

diff --git src/hg/lib/genePred.c src/hg/lib/genePred.c
index 4410902f9f1..0cfbceddf36 100644
--- src/hg/lib/genePred.c
+++ src/hg/lib/genePred.c
@@ -2339,85 +2339,88 @@
 int dir = (gp->strand[0] == '+') ? 1 : -1;
 int iExon = (dir > 0) ? 0 : gp->exonCount-1;
 int iStop = (dir > 0) ? gp->exonCount : -1;
 int exonCdsStart, exonCdsEnd;
 for (; iExon != iStop; iExon += dir)
     {
     if (genePredCdsExon(gp, iExon, &exonCdsStart, &exonCdsEnd))
         addCdsExon(genomeSeqs, gp, exonCdsStart, exonCdsEnd, gp->exonFrames[iExon], &cds);
     }
 if (cds.nextFrame != 0)
     removePartialCodon(&cds);
 assert((strlen(cds.bases) % 3) == 0);  // ;-)
 return cds.bases;
 }
 
-static char translateCodon(boolean isChrM, char* codon, bool lastCodon, unsigned options)
+static char translateCodon(struct geneticCode *code, char* codon, bool lastCodon, unsigned options)
 /* translate the first three bases starting at codon, handling weird
  * biology as requested giving */
 {
-char aa = isChrM ? lookupMitoCodon(codon) : lookupCodon(codon);
+char aa = lookupCodonInCode(code, codon);
 if (aa == '\0')
     {
     // stop, contains `N' or selenocysteine
     boolean isStopOrSelno = isStopCodon(codon);
     boolean isRealStop = isReallyStopCodon(codon, !lastCodon); // internal could be selenocysteine
     if (lastCodon)
         {
         if ((options & GENEPRED_TRANSLATE_INCLUDE_STOP) != 0)
             aa = '*';
         else if (!isRealStop)
             aa = 'X';
         // others, \0' will terminate
         }
     else if (((options & GENEPRED_TRANSLATE_SELENO) != 0)
              && isStopOrSelno && !isRealStop)
         aa = 'U';
     else if (isRealStop && ((options & GENEPRED_TRANSLATE_STAR_INFRAME_STOPS) != 0))
         aa = '*';
     else
         aa = 'X';
     }
 return aa;
 }
 
-static char* translateCds(char* chrom, char* cds, unsigned options)
-/* translate the CDS */
+static char* translateCds(char *db, char* chrom, char* cds, unsigned options)
+/* translate the CDS using the genetic code assigned to chrom in db (an
+ * assembly hub may set this; chrM/chrMT default to the mitochondrial code). */
 {
 int cdsLen = strlen(cds);
 char *prot = needMem((cdsLen/3)+1);
-boolean isChrM = isMito(chrom);
+struct geneticCode *code = hGeneticCodeForChrom(db, chrom);
 int iCds, iProt;
 for (iCds = 0, iProt = 0; iCds < cdsLen; iCds+=3, iProt++)
-    prot[iProt] = translateCodon(isChrM, cds+iCds, (iCds == cdsLen-3), options);
+    prot[iProt] = translateCodon(code, cds+iCds, (iCds == cdsLen-3), options);
 return prot;
 }
 
 void genePredTranslate(struct genePred *gp, struct nibTwoCache* genomeSeqs, unsigned options,
-                       char **protRet, char **cdsRet)
+                       char *db, char **protRet, char **cdsRet)
 /* Translate a genePred into a protein.  It can also return the CDS part of the
- * mRNA sequence. If the chrom is chrM, the mitochondrial translation tables are
- * used. If protRet or cdsRet is NULL, those sequences are not returned.
+ * mRNA sequence. The genetic code is that assigned to gp->chrom in db (an
+ * assembly hub may set this; chrM/chrMT default to the mitochondrial code).
+ * db may be NULL, in which case only the chrM/chrMT default applies.
+ * If protRet or cdsRet is NULL, those sequences are not returned.
  */
 {
 // note: code tests by genePredToProt
 bool haveFrames = (gp->exonFrames != NULL);
 if (!haveFrames)
     genePredAddExonFrames(gp);  // assume correct frame if not included
 
 char* cds = getCdsCodons(gp, genomeSeqs);
-char *prot = translateCds(gp->chrom, cds, options);
+char *prot = translateCds(db, gp->chrom, cds, options);
 
 if (protRet != NULL)
     *protRet = prot;
 else
     freeMem(prot);
 if (cdsRet != NULL)
     *cdsRet = cds;
 else
     freeMem(cds);
 
 if (!haveFrames)
     freez(&gp->exonFrames);
 }
 
 void genePredToCds(struct genePred *gp, struct genbankCds *cds)