src/hg/hgTracks/cds.c 1.91

1.91 2009/04/01 01:33:18 angie
Calls to hDnaFromSeq are rather expensive for 2bit, so cache genomic sequence from a single hDnaFromSeq call covering all of a track's items (even the ones that are only partly in the window). Replaced several strncpy/snprintf calls with memcpy because strncpy/snprintf can include strlen(source) which can be large (cached seq), bad news when called in codon inner loop. Also fixed off-by-one error that caused blue insert line to be drawn 1 base to the left of the end of + strand items (thx Brooke).
Index: src/hg/hgTracks/cds.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/hgTracks/cds.c,v
retrieving revision 1.90
retrieving revision 1.91
diff -b -B -U 4 -r1.90 -r1.91
--- src/hg/hgTracks/cds.c	19 Mar 2009 22:21:05 -0000	1.90
+++ src/hg/hgTracks/cds.c	1 Apr 2009 01:33:18 -0000	1.91
@@ -150,99 +150,96 @@
 
 return(thisQStart + (s - tStart));
 }
 
-
-static void getCodonDna(char *retStr, char *chrom, int n, unsigned *exonStarts,
-			unsigned *exonEnds, int exonCount, unsigned cdsStart, 
-			unsigned cdsEnd, int startI, boolean reverse, 
-			struct dnaSeq *mrnaSeq)
-//get n bases from coding exons only.
+/* Calls to hDnaFromSeq are rather expensive for 2bit, so cache genomic sequence */
+static char *initedTrack = NULL;
+static struct dnaSeq *cachedGenoDna = NULL;
+static int cachedGenoStart = 0;
+static int cachedGenoEnd = 0;
+
+static void getLinkedFeaturesSpan(struct linkedFeatures *lfList, int *retStart, int *retEnd)
+/* Find the overall lowest and highest coords in lfList.  If any items hang off the
+ * edge of the window, we will end up with coords <winStart and/or >winEnd which is
+ * what we want. */
+{
+int start = winStart, end = winEnd;
+struct linkedFeatures *lf;
+for (lf = lfList;  lf != NULL;  lf = lf->next)
+    {
+    if (lf->start < start)
+	start = lf->start;
+    if (lf->end > end)
+	end = lf->end;
+    }
+if (retStart)
+    *retStart = start;
+if (retEnd)
+    *retEnd = end;
+}
+
+static char *getCachedDna(int chromStart, int chromEnd)
+/* Return a pointer into our cached genomic dna.  chromEnd is just for
+ * bounds-checking (honor system).  Do not change or free the return value! */
+{
+if (!initedTrack || ! cachedGenoDna)
+    errAbort("getCachedDnaAt called before baseColorInitTrack?!");
+if (chromStart < cachedGenoStart || chromEnd > cachedGenoEnd)
+    errAbort("getCachedDnaAt: coords %d,%d are out of cached range %d,%d",
+	     chromStart, chromEnd, cachedGenoStart, cachedGenoEnd);
+return &(cachedGenoDna->dna[chromStart - cachedGenoStart]);
+}
+
+static void getNextCodonDna(char *retStr, int n, struct genePred *gp, int startI,
+			    boolean posStrand)
+/* Get at most n bases from coding exons following exon startI. */
 {
 int i, j, thisN;
-int theStart, theEnd;
-
-//clear retStr
-sprintf(retStr,"   ");
-
-if (!reverse)  /*positive strand*/
+int cdsExonStart, cdsExonEnd;
+char *codonDna;
+for (i = 0;  i < n;  i++)
+    retStr[i] = 'N';
+retStr[n] = '\0';
+if (posStrand)
+    {
+    for (thisN = 0, i = startI+1;  thisN < n && i < gp->exonCount;  i++)
+	{
+        // get dna for exon to the right
+        codonDna = getCachedDna(gp->exonStarts[i], gp->exonEnds[i]);
+	cdsExonStart = gp->exonStarts[i];
+	cdsExonEnd = gp->exonEnds[i];
+	if (gp->cdsStart < gp->txEnd && cdsExonStart < gp->cdsStart)
+	    cdsExonStart = gp->cdsStart;
+	if (gp->cdsEnd > gp->txStart  && cdsExonEnd > gp->cdsEnd)
+	    cdsExonEnd = gp->cdsEnd;
+	for (j=0; j < (cdsExonEnd - cdsExonStart); j++)
     {
-   
-    thisN = 0;
-    //move to next block start
-    i = startI+1;
-    while (thisN < n)
-	{
-        struct dnaSeq *codonDna = NULL;
-
-	/*already on last block*/
-	if (i >= exonCount) 
-	    break;
-
-        //get dna for this exon
-        codonDna = hDnaFromSeq(database, chrom, exonStarts[i], exonEnds[i], dnaUpper );
-
-	theStart = exonStarts[i];
-	theEnd = exonEnds[i];
-	if (cdsStart > 0 && theStart < cdsStart)
-	    theStart = cdsStart;
-
-	if (cdsEnd > 0 && theEnd > cdsEnd)
-	    theEnd = cdsEnd;
-           
-	for (j=0; j<(theEnd - theStart); j++)
-	    {
-	    if (mrnaSeq != NULL)
-		retStr[thisN] = mrnaSeq->dna[theStart+j];
-	    else
-                retStr[thisN] = codonDna->dna[j+theStart-exonStarts[i]];
-
+	    retStr[thisN] = codonDna[j+cdsExonStart-gp->exonStarts[i]];
 	    thisN++;
 	    if (thisN >= n) 
 		break;
 	    }
-
-	i++;
 	}
-    retStr[thisN] = '\0';
-       
     }
-else   /*negative strand*/
+else
     {
-    retStr[n] = '\0';
-    thisN = n-1;
-    //move to previous block end
-    i = startI-1;
-    while(thisN >= 0)
+    for (thisN = n-1, i = startI-1;  thisN >= 0 && i >= 0;  i--)
 	{
-        struct dnaSeq *codonDna = NULL;
-
-	if (i < 0)
-	    break;
-
-        //get dna for this exon
-        codonDna = hDnaFromSeq(database, chrom, exonStarts[i], exonEnds[i], dnaUpper );
-
-	theStart = exonStarts[i];
-	theEnd = exonEnds[i];
-	if (theStart < cdsStart)
-	    theStart = cdsStart;
-
-	if (theEnd > cdsEnd)
-	    theEnd = cdsEnd;
-           
-	for (j=0; j<(theEnd - theStart); j++)
+        // get dna for exon to the left
+        codonDna = getCachedDna(gp->exonStarts[i], gp->exonEnds[i]);
+	cdsExonStart = gp->exonStarts[i];
+	cdsExonEnd = gp->exonEnds[i];
+	if (gp->cdsStart < gp->txEnd && cdsExonStart < gp->cdsStart)
+	    cdsExonStart = gp->cdsStart;
+	if (gp->cdsEnd > gp->txStart && cdsExonEnd > gp->cdsEnd)
+	    cdsExonEnd = gp->cdsEnd;
+	for (j=0; j < (cdsExonEnd - cdsExonStart); j++)
 	    {
-	    if (mrnaSeq != NULL)
-		retStr[thisN] = mrnaSeq->dna[theEnd-j-1];
-	    else
-                retStr[thisN] = codonDna->dna[theEnd-j-1-exonStarts[i]];
-
+	    retStr[thisN] = codonDna[cdsExonEnd-j-1-gp->exonStarts[i]];
 	    thisN--;
 	    if (thisN < 0) 
 		break;
 	    }
-	i--;
 	}
     }
 }
 
@@ -276,30 +273,29 @@
  * This assumes that lf has been drawn already, we're zoomed out past 
  * zoomedToBaseLevel, we're not in dense mode etc. */
 {
 struct simpleFeature *sf = NULL;
+char *winDna = getCachedDna(winStart, winEnd);
 for (sf = lf->components; sf != NULL; sf = sf->next)
     {
-    int s = sf->start;
-    int e = sf->end;
+    int s = max(winStart, sf->start);
+    int e = min(winEnd, sf->end);
     if (s > winEnd || e < winStart)
       continue;
     if (e > s)
 	{
 	int mrnaS = convertCoordUsingPsl(s, psl);
 	if(mrnaS >= 0)
 	    {
-	    struct dnaSeq *genoSeq = hDnaFromSeq(database, chromName, s, e, dnaUpper);
 	    int i;
 	    for (i=0; i < (e - s); i++)
 		{
-		if (mrnaSeq->dna[mrnaS+i] != genoSeq->dna[i])
+		if (mrnaSeq->dna[mrnaS+i] != winDna[s-winStart+i])
 		    {
 		    drawVertLine(lf, hvg, s+i, xOff, y+1, heightPer-2, scale,
 				 cdsColor[CDS_STOP]);
 		    }
 		}
-	    dnaSeqFree(&genoSeq);
 	    }
 	}
     }
 }
@@ -590,19 +586,18 @@
      * didn't work very well for TransMap alignments and not clear that its
      * the right thing to do for any alignment.  By using exonFrames for
      * genomic codons, this is letting the query sequence define the frame.
      */
-    boolean useExonFrames = TRUE;
     struct genbankCds cds;
     getPslCds(psl, tg, &cds);
-    int insertMergeSize = useExonFrames ? -1 : 0;
-    unsigned opts = genePredCdsStatFld|(useExonFrames ? genePredExonFramesFld : 0);
+    int insertMergeSize = -1;
+    unsigned opts = genePredCdsStatFld|genePredExonFramesFld;
     struct genePred *gp = genePredFromPsl2(psl, opts, &cds, insertMergeSize);
     lf->start = gp->txStart;
     lf->end = gp->txEnd;
     lf->tallStart = gp->cdsStart;
     lf->tallEnd = gp->cdsEnd;
-    sfList = baseColorCodonsFromGenePred(lf, gp, useExonFrames, colorStopStart);
+    sfList = baseColorCodonsFromGenePred(lf, gp, colorStopStart);
     genePredFree(&gp);
     }
 return(sfList);
 }
@@ -871,22 +866,24 @@
 }
 
 
 
-static void updatePartialCodon(char *retStr, char *chrom, int start,
-        int end, boolean reverse, struct dnaSeq *codonDna, int base)
-{
-    char tmpStr[5];
-    char tmpDna[5];
-
-    snprintf(tmpDna, min(3-strlen(retStr)+1,abs(end-start+1)), "%s",
-            &codonDna->dna[start-base]);
-    if (!reverse)
-        safef(tmpStr, 4, "%s%s", retStr, tmpDna);
-    else
-        safef(tmpStr, 4, "%s%s", tmpDna, retStr);
-
-    strncpy( retStr, tmpStr, 4 );
+static void updatePartialCodon(char *retStr, int exonStart, int exonEnd, boolean posStrand)
+/* Add bases to the appropriate end of retStr, from exonStart until we have 3 bases or
+ * run into exonEnd*/
+{
+char tmpStr[4];
+char tmpDna[4];
+char *codonDna = getCachedDna(exonStart, exonEnd);
+int baseCount = min(3-strlen(retStr), abs(exonEnd-exonStart));
+memcpy(tmpDna, codonDna, baseCount);
+tmpDna[baseCount] = '\0';
+if (posStrand)
+    safef(tmpStr, sizeof(tmpStr), "%s%s", retStr, tmpDna);
+else
+    safef(tmpStr, sizeof(tmpStr), "%s%s", tmpDna, retStr);
+memcpy(retStr, tmpStr, 3);
+retStr[3] = '\0';
 }
 
 struct simpleFeature *baseColorCodonsFromDna(int frame, int chromStart,
 	int chromEnd, struct dnaSeq *seq, bool reverse)
@@ -932,19 +929,25 @@
 slReverse(&sfList);
 return sfList;
 }
 
-static struct simpleFeature *splitByCodon(struct linkedFeatures *lf, 
-        unsigned *starts, unsigned *ends, int blockCount, unsigned cdsStart, unsigned cdsEnd,
-        int *exonFrames, boolean colorStopStart)
+struct simpleFeature *baseColorCodonsFromGenePred(struct linkedFeatures *lf, 
+	struct genePred *gp, boolean colorStopStart)
+/* Given an lf and the genePred from which the lf was constructed, 
+ * return a list of simpleFeature elements, one per codon (or partial 
+ * codon if the codon falls on a gap boundary. */
 {
-    int codon = 0;
+unsigned *starts = gp->exonStarts;
+unsigned *ends = gp->exonEnds;
+int blockCount = gp->exonCount;
+unsigned cdsStart = gp->cdsStart;
+unsigned cdsEnd = gp->cdsEnd;
+int *exonFrames = gp->exonFrames;
+boolean useExonFrames = (gp->optFields >= genePredExonFramesFld);
     int frame = 0;
     int currentStart = 0, currentEnd = 0;
-    struct dnaSeq *codonDna = NULL;
     char partialCodonSeq[4];
-    char theRestOfCodon[4];
-    int currentSize, base;
+    int currentSize;
     int i;
 
     boolean foundStart = FALSE;
     struct simpleFeature *sfList = NULL, *sf = NULL;
@@ -963,13 +966,15 @@
         i0 = blockCount-1; iN=-1; iInc = -1;
         posStrand = FALSE;
     }
 
+    bool altColor = FALSE;
+    unsigned cds5Prime = posStrand ? cdsStart : cdsEnd;
     for (i=i0; (iInc*i)<(iInc*iN); i=i+iInc)
     {
-        int thisStart, thisEnd;
-	unsigned cdsLine;
-        if(exonFrames != NULL)
+        int exonStart = starts[i];
+        int exonEnd = ends[i];
+        if (useExonFrames)
         {
             if(exonFrames[i] > 0)
                 frame = 3 - exonFrames[i];
             else
@@ -978,89 +983,85 @@
 
         if(frame == 0)
             strcpy(partialCodonSeq,"");
 
-        thisStart = starts[i];
-        thisEnd = ends[i];
-        cdsLine = posStrand?cdsStart:cdsEnd;
-
-        // 3' block or 5' block
-        if ((thisStart < cdsStart && thisEnd <= cdsStart) ||
-                (thisStart >= cdsEnd && thisEnd > cdsEnd))
+
+        // 3' or 5' UTR exon
+        if ((exonStart < cdsStart && exonEnd <= cdsStart) ||
+                (exonStart >= cdsEnd && exonEnd > cdsEnd))
+	    {
+	    if (exonEnd > winStart && exonStart < winEnd)
         {
             AllocVar(sf);
-            sf->start = thisStart;
-            sf->end = thisEnd; 
+		sf->start = exonStart;
+		sf->end = exonEnd; 
             slAddHead(&sfList, sf);
+		}
             continue;
         }
-        //UTR to coding block
-        else if (thisEnd > cdsLine && thisStart < cdsLine)
+        // UTR + coding exon
+        else if (exonEnd > cds5Prime && exonStart < cds5Prime)
         {
-            AllocVar(sf);
-            if(posStrand)
+	    int utrStart, utrEnd;
+            if (posStrand)
             {
-                sf->start = thisStart;
-                sf->end = cdsStart;
+                utrStart = exonStart;
+                utrEnd = cdsStart;
                 currentStart = cdsStart;
             }
             else
             {
-                sf->start = cdsEnd;
-                sf->end = thisEnd;
+                utrStart = cdsEnd;
+                utrEnd = exonEnd;
                 currentEnd = cdsEnd;
             }
+	    if (utrEnd > winStart && utrStart < winEnd)
+		{
+		AllocVar(sf);
+		sf->start = utrStart;
+		sf->end = utrEnd; 
             slAddHead(&sfList, sf);
         }
+	    }
         else
             if(posStrand)
-                currentStart = thisStart;
+                currentStart = exonStart;
             else 
-                currentEnd = thisEnd;
-
+                currentEnd = exonEnd;
 
-        /*get dna for entire coding block. this is faster than
-          getting it for each codon, but suprisingly not faster
-          than getting it for each linked feature*/
-	if (thisStart < cdsStart) thisStart = cdsStart;
-	if (thisEnd > cdsEnd) thisEnd = cdsEnd;
-        codonDna = hDnaFromSeq(database, chromName, thisStart, thisEnd, dnaUpper );
-        base = thisStart;
+	// If UTR + coding, trim to coding portion:
+	if (exonStart < cdsStart) exonStart = cdsStart;
+	if (exonEnd > cdsEnd) exonEnd = cdsEnd;
 
-        //break each block by codon and set color code to denote codon
+        // break each exon into codons and assign alternating shades.
         while (TRUE)
         {
             int codonInc = frame;
             if (frame == 0)  
             {
-                codon++; codon %= 2; codonInc = 3;
+                altColor = altColor ? FALSE : TRUE;
+		codonInc = 3;
             }
             
             if(posStrand)
                 currentEnd = currentStart + codonInc;
             else
                 currentStart = currentEnd - codonInc; 
 
-            AllocVar(sf);
-            sf->start = currentStart;
-            sf->end = currentEnd;
-
-            //we've gone off the end of the current block
-            if ((posStrand && currentEnd > thisEnd) ||
-                    (!posStrand && currentStart < thisStart ))
+            //we've gone off the end of the current exon
+            if ((posStrand && currentEnd > exonEnd) ||
+		(!posStrand && currentStart < exonStart ))
             {
-
                 if (posStrand)
                 {
-                    frame = currentEnd - thisEnd;
-                    sf->end = currentEnd = thisEnd;
+                    frame = currentEnd - exonEnd;
+                    currentEnd = exonEnd;
                 }
                 else
                 {
-                    frame = thisStart - currentStart;
-                    sf->start = currentStart = thisStart;
+                    frame = exonStart - currentStart;
+                    currentStart = exonStart;
                 }
-
                 if(frame == 3) 
                 {
                     frame = 0;
                     break;
@@ -1064,19 +1065,15 @@
                 {
                     frame = 0;
                     break;
                 }
-
-                /*accumulate partial codon in case of 
-                  one base exon, or start a new one.*/
-                updatePartialCodon(partialCodonSeq, chromName, sf->start,
-                                sf->end, !posStrand, codonDna, base);
-
-                /*get next 'frame' nt's to see what codon will be 
-                  (skipping intron sequence)*/
-                getCodonDna(theRestOfCodon, chromName, frame, starts, ends, 
-                        blockCount, cdsStart, cdsEnd, i, !posStrand, NULL);
-
+		/* accumulate partial codon in case of one base exon, or start a new one. */
+		updatePartialCodon(partialCodonSeq, currentStart, currentEnd, posStrand);
+		if (currentStart < winEnd && currentEnd > winStart)
+		    {
+		    // get next 'frame' nt's to see what codon will be (skipping intron sequence)
+		    char theRestOfCodon[4];
+		    getNextCodonDna(theRestOfCodon, frame, gp, i, posStrand);
                 /* This code doesn't really work right in all cases of a
                  * one-base blocks. It broke with some TransMap alignments
                  * with indels around the one base.  This code is fragile, so
                  * just work around it by truncating the sequence.
@@ -1089,91 +1086,93 @@
                     safef(tempCodonSeq, sizeof(tempCodonSeq), "%s%s", theRestOfCodon, 
                             partialCodonSeq );
                 tempCodonSeq[4] = '\0';  // no more than 3 bases
 
+		    AllocVar(sf);
+		    sf->start = currentStart;
+		    sf->end = currentEnd;
                 sf->grayIx = ((posStrand && currentEnd <= cdsEnd) || 
-                             (!posStrand && currentStart >= cdsStart))?
-                             setColorByCds( tempCodonSeq,codon,&foundStart, 
-                             !posStrand, colorStopStart):-2;
+				  (!posStrand && currentStart >= cdsStart)) ?
+			setColorByCds(tempCodonSeq, altColor, &foundStart, 
+				      !posStrand, colorStopStart) :
+			-2;
                 slAddHead(&sfList, sf);
-                break;
             }
+                break;
+		} // end if we've gone off the end of the current exon
 
-            currentSize = sf->end - sf->start;
+            currentSize = currentEnd - currentStart;
             /*inside a coding block (with 3 bases)*/
             if (currentSize == 3)
             {
-                char currentCodon[5];
-                char *thisDna = &codonDna->dna[sf->start - base];
-                snprintf(currentCodon, currentSize+1, "%s", thisDna);
-                sf->grayIx = ((posStrand && currentEnd <= cdsEnd) || 
-                             (!posStrand && currentStart >= cdsStart))?
-                             setColorByCds(currentCodon,codon,&foundStart, 
-                                     !posStrand, colorStopStart):-2;
+		AllocVar(sf);
+		sf->start = currentStart;
+		sf->end = currentEnd;
+		if ((posStrand && currentEnd <= cdsEnd) ||
+		    (!posStrand && currentStart >= cdsStart))
+		    {
+		    char currentCodon[4];
+		    char *thisDna = getCachedDna(currentStart, currentEnd);
+		    memcpy(currentCodon, thisDna, 3);
+		    currentCodon[3] = '\0';
+		    sf->grayIx = setColorByCds(currentCodon, altColor, &foundStart, 
+					       !posStrand, colorStopStart);
+		    }
+		else
+		    sf->grayIx = -2;
             }
             /*start of a coding block with less than 3 bases*/
             else if (currentSize < 3)
             {
-                updatePartialCodon(partialCodonSeq,chromName,sf->start,
-                        sf->end,!posStrand,codonDna, base);
+                updatePartialCodon(partialCodonSeq, currentStart, currentEnd, posStrand);
+		AllocVar(sf);
+		sf->start = currentStart;
+		sf->end = currentEnd;
                 if (strlen(partialCodonSeq) == 3) 
-                    sf->grayIx = setColorByCds(partialCodonSeq,codon,
+                    sf->grayIx = setColorByCds(partialCodonSeq, altColor,
                             &foundStart, !posStrand, colorStopStart);
                 else
                     sf->grayIx = -2;
                 strcpy(partialCodonSeq,"" );
 
                 /*update frame based on bases appended*/
-                frame -= (sf->end - sf->start);     
+                frame -= currentSize;
             }
             else
-                errAbort("%s: Too much dna (%d,%s)<br>\n", lf->name, 
-                        codonDna->size, codonDna->dna );
+                errAbort("%s: Too much dna (%d - %d = %d)<br>\n", lf->name, 
+			 currentEnd, currentStart, currentSize);
 
             slAddHead(&sfList, sf);
             if(posStrand)
                 currentStart = currentEnd;
             else
                 currentEnd = currentStart;
-        }
-	/* coding block to UTR block */
-	if (posStrand && (thisEnd < ends[i]))
+	    } // end loop on codons within exon
+
+	/* coding + UTR exon */
+	if (posStrand && (exonEnd < ends[i]) &&
+	    exonEnd < winEnd && ends[i] > winStart)
 	    {
             AllocVar(sf);
-            sf->start = thisEnd;
+            sf->start = exonEnd;
             sf->end = ends[i];
             slAddHead(&sfList, sf);
 	    }
-	else if (!posStrand && (thisStart > starts[i]))
+	else if (!posStrand && (exonStart > starts[i]) &&
+		 starts[i] < winEnd && exonStart > winStart)
 	    {
             AllocVar(sf);
             sf->start = starts[i];
-            sf->end = thisStart;
+            sf->end = exonStart;
             slAddHead(&sfList, sf);
 	    }
-    }
+	} // end loop on exons
 
     if(posStrand)
         slReverse(&sfList);
     return(sfList);
 }
 
-struct simpleFeature *baseColorCodonsFromGenePred(struct linkedFeatures *lf, 
-	struct genePred *gp, boolean useExonFrames, boolean colorStopStart)
-/* Given an lf and the genePred from which the lf was constructed, 
- * return a list of simpleFeature elements, one per codon (or partial 
- * codon if the codon falls on a gap boundary.  If useExonFrames is true, 
- * use the frames portion of gp (which should be from a genePredExt);
- * otherwise determine frame from genomic sequence. */
-{
-    if(useExonFrames)
-        return(splitByCodon(lf, gp->exonStarts, gp->exonEnds, gp->exonCount,
-			    gp->cdsStart, gp->cdsEnd, gp->exonFrames, colorStopStart));
-    else
-        return(splitByCodon(lf, gp->exonStarts, gp->exonEnds, gp->exonCount,
-			    gp->cdsStart, gp->cdsEnd, NULL, colorStopStart));
-}
-
 
 static void getMrnaBases(struct psl *psl, struct dnaSeq *mrnaSeq,
 			 int mrnaS, int s, int e, boolean isRc,
 			 char retMrnaBases[4], boolean *retQueryInsertion)
@@ -1219,24 +1218,24 @@
 
 	if (!appendAtStart)
             {
 	    newIdx = mrnaS + size;
-	    strncpy(retMrnaBases, &mrnaSeq->dna[mrnaS], size);
-	    strncpy(retMrnaBases+size, &mrnaSeq->dna[newIdx], 3-size);
+	    memcpy(retMrnaBases, &mrnaSeq->dna[mrnaS], size);
+	    memcpy(retMrnaBases+size, &mrnaSeq->dna[newIdx], 3-size);
             }
 	else
             {
 	    newIdx = mrnaS - (3 - size);
-	    strncpy(retMrnaBases, &mrnaSeq->dna[newIdx], 3);
+	    memcpy(retMrnaBases, &mrnaSeq->dna[newIdx], 3);
             }
         }
     else
 	{
-	strncpy(retMrnaBases, "NNN", 3);
+	memcpy(retMrnaBases, "NNN", 3);
 	}
     }
 else
-    strncpy(retMrnaBases, &mrnaSeq->dna[mrnaS], 3);
+    memcpy(retMrnaBases, &mrnaSeq->dna[mrnaS], 3);
 retMrnaBases[3] = '\0';
 if (isRc)
     reverseComplement(retMrnaBases, strlen(retMrnaBases));
 }
@@ -1593,23 +1592,59 @@
 	/* Insert at end of query -- draw vertical blue line unless it's 
 	 * all polyA. */
 	s = (psl->strand[1] == '-') ?
 	    (psl->tSize - (psl->tStarts[lastBlk] + psl->blockSizes[lastBlk])) :
-	    (psl->tStarts[lastBlk] + psl->blockSizes[lastBlk] - 1);
+	    (psl->tStarts[lastBlk] + psl->blockSizes[lastBlk]);
 	drawVertLine(lf, hvg, s, xOff, y, heightPer-1, scale,
 		     cdsColor[CDS_QUERY_INSERTION_AT_END]);
 	}
     }
 }
 
+void baseColorInitTrack(struct hvGfx *hvg, struct track *tg)
+/* Set up base coloring state (e.g. cache genomic sequence) for tg.
+ * This must be called by tg->drawItems if baseColorDrawSetup is used 
+ * in tg->drawItemAt.  Assumes tg->items is linkedFeatures. */
+{
+if (initedTrack == NULL || differentString(tg->mapName, initedTrack))
+    {
+    int overallStart, overallEnd;
+    getLinkedFeaturesSpan((struct linkedFeatures *)tg->items, &overallStart, &overallEnd);
+    if (overallStart < cachedGenoStart || overallEnd > cachedGenoEnd)
+	{
+	// leak mem to save time (don't bother freeing old cached dna)
+	cachedGenoStart = overallStart;
+	cachedGenoEnd = overallEnd;
+	cachedGenoDna = hDnaFromSeq(database, chromName, cachedGenoStart, cachedGenoEnd, dnaUpper);
+	}
+    initedTrack = cloneString(tg->mapName);
+    }
+
+/* allocate colors for coding coloring */
+if (!cdsColorsMade)
+    { 
+    makeCdsShades(hvg, cdsColor);
+    cdsColorsMade = TRUE;
+    }
+}
+
+static void checkTrackInited(struct track *tg, char *what)
+/* Die if baseColorInitTrack has not been called (most recently) for this track. */
+{
+if (initedTrack == NULL || differentString(tg->mapName, initedTrack))
+    errAbort("Error: Track %s should have been baseColorInitTrack'd before %s.",
+	     tg->mapName, what);
+}
 
 enum baseColorDrawOpt baseColorDrawSetup(struct hvGfx *hvg, struct track *tg,
 			struct linkedFeatures *lf,
 			struct dnaSeq **retMrnaSeq, struct psl **retPsl)
 /* Returns the CDS coloring option, allocates colors if necessary, and 
  * returns the sequence and psl record for the given item if applicable. 
  * Note: even if base coloring is not enabled, this will return psl and 
- * mrna seq if query insert/polyA coloring is enabled. */
+ * mrna seq if query insert/polyA coloring is enabled.
+ * baseColorInitTrack must be called before this (in tg->drawItems) --
+ * this is meant to be called by tg->drawItemAt (i.e. linkedFeaturesDrawAt). */
 {
 enum baseColorDrawOpt drawOpt = baseColorGetDrawOpt(tg);
 boolean indelShowDoubleInsert, indelShowQueryInsert, indelShowPolyA;
 
@@ -1618,14 +1653,9 @@
 
 if (drawOpt <= baseColorDrawOff && !(indelShowQueryInsert || indelShowPolyA))
     return drawOpt;
 
-/* allocate colors for coding coloring */
-if (!cdsColorsMade)
-    { 
-    makeCdsShades(hvg, cdsColor);
-    cdsColorsMade = TRUE;
-    }
+checkTrackInited(tg, "calling baseColorDrawSetup");
    
 /* If we are using item sequence, fetch alignment and sequence: */
 if ((drawOpt > baseColorDrawOff && startsWith("psl", tg->tdb->type)) ||
     indelShowQueryInsert || indelShowPolyA)