src/hg/hgTracks/bamTrack.c 1.12

1.12 2009/10/19 22:50:08 angie
Implemented grayscale for base qualities and coloring by user-specified tag.
Index: src/hg/hgTracks/bamTrack.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/hgTracks/bamTrack.c,v
retrieving revision 1.11
retrieving revision 1.12
diff -b -B -U 4 -r1.11 -r1.12
--- src/hg/hgTracks/bamTrack.c	16 Oct 2009 00:34:31 -0000	1.11
+++ src/hg/hgTracks/bamTrack.c	19 Oct 2009 22:50:08 -0000	1.12
@@ -6,8 +6,9 @@
 #include "common.h"
 #include "hCommon.h"
 #include "hash.h"
 #include "linefile.h"
+#include "htmshell.h"
 #include "jksql.h"
 #include "hdb.h"
 #include "hgTracks.h"
 #include "cds.h"
@@ -21,9 +22,8 @@
     struct hash *pairHash;
     int minAliQual;
     char *colorMode;
     char *grayMode;
-    boolean grayUnpaired;
     char *userTag;
     };
 
 struct simpleFeature *sfFromNumericCigar(const bam1_t *bam, int *retLength)
@@ -70,8 +70,61 @@
     *retLength = tLength;
 return sfList;
 }
 
+INLINE int shadeTransform(int min, int max, int score)
+/* Linearly transform score's place between min and max into a visible shade. */
+{
+int minShade = 2;
+int ix = minShade + (maxShade - minShade) * ((double)(score - min) / (double)(max-min));
+if (ix > maxShade)
+    ix = maxShade;
+if (ix < minShade)
+    ix = minShade;
+return ix;
+}
+
+static struct simpleFeature *expandSfQuals(struct simpleFeature *blocksIn, UBYTE *quals,
+					   int orientation, int qLen)
+/* Chop up blocksIn into one sf per query base, with sf->grayIx set according to
+ * base quality score. */
+{
+struct simpleFeature *sf, *blocksOut = NULL;
+for (sf = blocksIn;  sf != NULL;  sf = sf->next)
+    {
+    struct simpleFeature *newSf;
+    int i;
+    for (i = sf->qStart;  i < sf->qEnd;  i++)
+	{
+	AllocVar(newSf);
+	newSf->start = sf->start + (i - sf->qStart);
+	newSf->end = newSf->start + 1;
+	newSf->qStart = i;
+	newSf->qEnd = i + 1;
+	int offset = (orientation < 0) ? (qLen - i - 1) : i;
+	// hardcode min & max for now; if user demand, make into tdb/cart vars.
+	newSf->grayIx = shadeTransform(0, 40, quals[offset]);
+	slAddHead(&blocksOut, newSf);
+	}
+    }
+slReverse(&blocksOut);
+return blocksOut;
+}
+
+// similar but not identical to customFactory.c's parseRgb:
+static boolean parseRgb(char *s, unsigned char *retR, unsigned char *retG, unsigned char *retB)
+/* Turn comma separated list to RGB vals; return FALSE if input is not as expected. */
+{
+char *row[4];
+int wordCount = chopString(s, ",", row, ArraySize(row));
+if ((wordCount != 3) || (!isdigit(row[0][0]) || !isdigit(row[1][0]) || !isdigit(row[2][0])))
+    return FALSE;
+*retR = atoi(row[0]);
+*retG = atoi(row[1]);
+*retB = atoi(row[2]);
+return TRUE;
+}
+
 struct linkedFeatures *bamToLf(const bam1_t *bam, void *data)
 /* Translate a BAM record into a linkedFeatures item. */
 {
 struct bamTrackData *btd = (struct bamTrackData *)data;
@@ -88,17 +141,44 @@
 lf->extra = bamGetQuerySequence(bam);
 if (sameString(btd->colorMode, BAM_COLOR_MODE_GRAY) &&
     sameString(btd->grayMode, BAM_GRAY_MODE_ALI_QUAL))
     {
-    // hardcode transform coefficients for now; if user demand, make into tdb/cart vars.
-    int min = 0, max = 99;
-    int minShade = 2;
-    int ix = minShade + (maxShade - minShade) * ((double)(core->qual - min) / (double)(max-min));
-    if (ix > maxShade)
-	ix = maxShade;
-    if (ix < minShade)
-	ix = minShade;
-    lf->grayIx = ix;
+    // hardcode min & max for now; if user demand, make into tdb/cart vars.
+    lf->grayIx = shadeTransform(0, 99, core->qual);
+    }
+else if (sameString(btd->colorMode, BAM_COLOR_MODE_GRAY) &&
+	 sameString(btd->grayMode, BAM_GRAY_MODE_BASE_QUAL))
+    {
+    UBYTE *quals = bamGetQueryQuals(bam);
+    lf->components = expandSfQuals(lf->components, quals, lf->orientation, core->l_qseq);
+    lf->grayIx = maxShade - 3;
+    }
+else if (sameString(btd->colorMode, BAM_COLOR_MODE_TAG) && isNotEmpty(btd->userTag))
+    {
+    char buf[16];
+    char *rgb = bamGetTagString(bam, btd->userTag, buf, sizeof(buf));
+    if (rgb != NULL)
+	{
+	// We don't have access to hvg at loadtime, so can't allocate color here.
+	// Instead, pack RGB values into lf->filterColor which fortunately is an int.
+	unsigned char r, g, b;
+	if (parseRgb(rgb, &r, &g, &b))
+	    lf->filterColor = ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
+	else
+	    {
+	    static boolean already = FALSE;
+	    if (! already)
+		{
+		warn("%s: At least one BAM tag value for %s (%s) is not in the expected "
+		     "RGB format: N,N,N where each N is from 0 to 255.",
+		     btd->tg->tdb->shortLabel, btd->userTag, htmlEncode(rgb));
+		already = TRUE;
+		btd->userTag = NULL;
+		}
+	    }
+	}
+    else
+	lf->grayIx = maxShade;
     }
 else
     lf->grayIx = maxShade;
 return lf;
@@ -202,9 +282,10 @@
 		stub = lfStub(core->mpos, -lf->orientation);
 		}
 	    lf->next = stub;
 	    }
-	else if (btd->grayUnpaired)
+	else if (sameString(btd->colorMode, BAM_COLOR_MODE_GRAY) &&
+		 sameString(btd->grayMode, BAM_GRAY_MODE_UNPAIRED))
 	    // not properly paired: make it a lighter shade.
 	    lf->grayIx -= 3;
 	hashAdd(btd->pairHash, lf->name, lf);
 	}
@@ -258,14 +339,12 @@
 		         trackDbSettingOrDefault(tg->tdb, BAM_COLOR_MODE, BAM_COLOR_MODE_DEFAULT));
 safef(cartVarName, sizeof(cartVarName), "%s_" BAM_GRAY_MODE, tg->tdb->tableName);
 char *grayMode = cartUsualString(cart, cartVarName,
 		         trackDbSettingOrDefault(tg->tdb, BAM_GRAY_MODE, BAM_GRAY_MODE_DEFAULT));
-boolean grayUnpaired = (sameString(colorMode, BAM_COLOR_MODE_GRAY) &&
-			sameString(grayMode, BAM_GRAY_MODE_UNPAIRED));
 safef(cartVarName, sizeof(cartVarName), "%s_" BAM_COLOR_TAG, tg->tdb->tableName);
 char *userTag = cartUsualString(cart, cartVarName,
 		         trackDbSettingOrDefault(tg->tdb, BAM_COLOR_TAG, BAM_COLOR_TAG_DEFAULT));
-struct bamTrackData btd = {tg, pairHash, minAliQual, colorMode, grayMode, grayUnpaired, userTag};
+struct bamTrackData btd = {tg, pairHash, minAliQual, colorMode, grayMode, userTag};
 char *fileName;
 if (tg->customPt)
     {
     fileName = trackDbSetting(tg->tdb, "bigDataUrl");
@@ -337,19 +416,24 @@
 struct dnaSeq *mrnaSeq = NULL;
 enum baseColorDrawOpt drawOpt = baseColorDrawOff;
 boolean indelShowDoubleInsert, indelShowQueryInsert, indelShowPolyA;
 struct psl *psl = NULL;
+char cartVarName[1024];
+safef(cartVarName, sizeof(cartVarName), "%s_" BAM_COLOR_MODE, tg->tdb->tableName);
+char *colorMode = cartUsualString(cart, cartVarName,
+		         trackDbSettingOrDefault(tg->tdb, BAM_COLOR_MODE, BAM_COLOR_MODE_DEFAULT));
+safef(cartVarName, sizeof(cartVarName), "%s_" BAM_GRAY_MODE, tg->tdb->tableName);
+char *grayMode = cartUsualString(cart, cartVarName,
+		         trackDbSettingOrDefault(tg->tdb, BAM_GRAY_MODE, BAM_GRAY_MODE_DEFAULT));
+bool baseQualMode = (sameString(colorMode, BAM_COLOR_MODE_GRAY) &&
+		     sameString(grayMode, BAM_GRAY_MODE_BASE_QUAL));
 if (vis != tvDense)
     {
     drawOpt = baseColorDrawSetup(hvg, tg, lf, &mrnaSeq, &psl);
     if (drawOpt > baseColorDrawOff)
 	exonArrows = FALSE;
     }
 
-char cartVarName[1024];
-safef(cartVarName, sizeof(cartVarName), "%s_" BAM_COLOR_MODE, tg->tdb->tableName);
-char *colorMode = cartUsualString(cart, cartVarName,
-		         trackDbSettingOrDefault(tg->tdb, BAM_COLOR_MODE, BAM_COLOR_MODE_DEFAULT));
 static Color darkBlueColor = 0;
 static Color darkRedColor = 0;
 if (darkRedColor == 0)
     {
@@ -358,9 +442,16 @@
     }
 if (sameString(colorMode, BAM_COLOR_MODE_STRAND))
     color = (lf->orientation < 0) ? darkRedColor : darkBlueColor;
 else if (lf->filterColor > 0)
-    color = lf->filterColor;
+    {
+    // In bamTrack, lf->filterColor is a packed int.  Unpack:
+    int r, g, b;
+    r = (lf->filterColor >> 16) & 0xff;
+    g = (lf->filterColor >> 8) & 0xff;
+    b = lf->filterColor & 0xff;
+    color = hvGfxFindColorIx(hvg, r, g, b);
+    }
 else if (tg->colorShades)
     color = tg->colorShades[lf->grayIx];
 
 
@@ -370,8 +461,10 @@
     innerLine(hvg, x1, midY, w, color);
 for (sf = lf->components; sf != NULL; sf = sf->next)
     {
     int s = sf->start,  e = sf->end;
+    if (baseQualMode)
+	color = tg->colorShades[sf->grayIx];
     baseColorDrawItem(tg, lf, sf->grayIx, hvg, xOff, y, scale, font, s, e, heightPer,
 		      zoomedToCodonLevel, mrnaSeq, sf, psl, drawOpt, MAXPIXELS, winStart, color);
     if (tg->exonArrowsAlways ||
 	(exonArrows &&
@@ -439,8 +532,25 @@
 char varName[1024];
 safef(varName, sizeof(varName), "%s_" BAM_PAIR_ENDS_BY_NAME, track->mapName);
 boolean isPaired = cartUsualBoolean(cart, varName,
 				    (trackDbSetting(track->tdb, BAM_PAIR_ENDS_BY_NAME) != NULL));
+safef(varName, sizeof(varName), "%s_" BAM_COLOR_MODE, track->tdb->tableName);
+char *colorMode = cartUsualString(cart, varName,
+		      trackDbSettingOrDefault(track->tdb, BAM_COLOR_MODE, BAM_COLOR_MODE_DEFAULT));
+safef(varName, sizeof(varName), "%s_" BAM_COLOR_TAG, track->tdb->tableName);
+char *userTag = cartUsualString(cart, varName,
+			trackDbSettingOrDefault(track->tdb, BAM_COLOR_TAG, BAM_COLOR_TAG_DEFAULT));
+if (sameString(colorMode, BAM_COLOR_MODE_TAG) && userTag != NULL)
+    {
+    if (! (isalpha(userTag[0]) && isalnum(userTag[1]) && userTag[2] == '\0'))
+	{
+	warn("%s: BAM tag '%s' is not valid -- must be a letter followed by a letter or number.",
+	     track->tdb->shortLabel, htmlEncode(userTag));
+	safef(varName, sizeof(varName), "%s_" BAM_COLOR_TAG, track->tdb->tableName);
+	cartRemove(cart, varName);
+	}
+    }
+
 if (isPaired)
     {
     linkedFeaturesSeriesMethods(track);
     track->loadItems = bamPairedLoadItems;