JAL-2286 javadoc, refactor, unit tests for threshold and conservation
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 26 Oct 2016 15:09:21 +0000 (16:09 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 26 Oct 2016 15:09:21 +0000 (16:09 +0100)
colouring

src/jalview/schemes/ResidueColourScheme.java
test/jalview/schemes/ResidueColourSchemeTest.java [new file with mode: 0644]

index f3f08cb..56c573c 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.analysis.Profile;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
@@ -59,9 +60,9 @@ public class ResidueColourScheme implements ColourSchemeI
    */
   char[] conservation;
 
-  int conservationLength = 0;
-
-  /** DOCUMENT ME!! */
+  /*
+   * The conservation slider percentage setting 
+   */
   int inc = 30;
 
   /**
@@ -144,45 +145,55 @@ public class ResidueColourScheme implements ColourSchemeI
   }
 
   /**
-   * DOCUMENT ME!
+   * Sets the percentage consensus threshold value, and whether gaps are ignored
+   * in percentage identity calculation
    * 
-   * @param ct
-   *          DOCUMENT ME!
+   * @param consensusThreshold
+   * @param ignoreGaps
    */
   @Override
-  public void setThreshold(int ct, boolean ignoreGaps)
+  public void setThreshold(int consensusThreshold, boolean ignoreGaps)
   {
-    threshold = ct;
+    threshold = consensusThreshold;
     this.ignoreGaps = ignoreGaps;
   }
 
   /**
-   * DOCUMENT ME!
+   * Answers true if there is a consensus profile for the specified column, and
+   * the given residue matches the consensus (or joint consensus) residue for
+   * the column, and the percentage identity for the profile is equal to or
+   * greater than the current threshold; else answers false. The percentage
+   * calculation depends on whether or not we are ignoring gapped sequences.
    * 
-   * @param s
-   *          DOCUMENT ME!
-   * @param j
-   *          DOCUMENT ME!
+   * @param residue
+   * @param column
+   *          (index into consensus profiles)
    * 
-   * @return DOCUMENT ME!
+   * @return
+   * @see #setThreshold(int, boolean)
    */
-  public boolean aboveThreshold(char c, int j)
+  public boolean aboveThreshold(char residue, int column)
   {
-    if ('a' <= c && c <= 'z')
+    if ('a' <= residue && residue <= 'z')
     {
       // TO UPPERCASE !!!
       // Faster than toUpperCase
-      c -= ('a' - 'A');
+      residue -= ('a' - 'A');
     }
 
-    if (consensus == null || consensus.length < j || consensus[j] == null)
+    if (consensus == null || consensus.length < column
+            || consensus[column] == null)
     {
       return false;
     }
 
-    if (consensus[j].getMaxCount() > 0) // != -1))
+    /*
+     * test whether this is the consensus (or joint consensus) residue
+     */
+    if (consensus[column].getModalResidue().contains(
+            String.valueOf(residue)))
     {
-      if (consensus[j].getPercentageIdentity(ignoreGaps) >= threshold)
+      if (consensus[column].getPercentageIdentity(ignoreGaps) >= threshold)
       {
         return true;
       }
@@ -243,71 +254,88 @@ public class ResidueColourScheme implements ColourSchemeI
     else
     {
       conservationColouring = true;
-      int i, iSize = cons.getConsSequence().getLength();
+      int iSize = cons.getConsSequence().getLength();
       conservation = new char[iSize];
-      for (i = 0; i < iSize; i++)
+      for (int i = 0; i < iSize; i++)
       {
         conservation[i] = cons.getConsSequence().getCharAt(i);
       }
-      conservationLength = conservation.length;
     }
 
   }
 
   /**
-   * DOCUMENT ME!
+   * Applies a combination of column conservation score, and conservation
+   * percentage slider, to 'bleach' out the residue colours towards white.
+   * <p>
+   * If a column is fully conserved (identical residues, conservation score 11,
+   * shown as *), or all 10 physico-chemical properties are conserved
+   * (conservation score 10, shown as +), then the colour is left unchanged.
+   * <p>
+   * Otherwise a 'bleaching' factor is computed and applied to the colour. This
+   * is designed to fade colours for scores of 0-9 completely to white at slider
+   * positions ranging from 18% - 100% respectively.
    * 
-   * @param s
-   *          DOCUMENT ME!
-   * @param i
-   *          DOCUMENT ME!
+   * @param currentColour
+   * @param column
    * 
-   * @return DOCUMENT ME!
+   * @return bleached (or unmodified) colour
    */
-
-  Color applyConservation(Color currentColour, int i)
+  Color applyConservation(Color currentColour, int column)
   {
+    if (conservation == null || conservation.length <= column)
+    {
+      return currentColour;
+    }
+    char conservationScore = conservation[column];
+
+    /*
+     * if residues are fully conserved (* or 11), or all properties
+     * are conserved (+ or 10), leave colour unchanged
+     */
+    if (conservationScore == '*' || conservationScore == '+'
+            || conservationScore == (char) 10
+            || conservationScore == (char) 11)
+    {
+      return currentColour;
+    }
 
-    if ((conservationLength > i) && (conservation[i] != '*')
-            && (conservation[i] != '+'))
+    if (Comparison.isGap(conservationScore))
     {
-      if (jalview.util.Comparison.isGap(conservation[i]))
-      {
-        currentColour = Color.white;
-      }
-      else
-      {
-        float t = 11 - (conservation[i] - '0');
-        if (t == 0)
-        {
-          return Color.white;
-        }
-
-        int red = currentColour.getRed();
-        int green = currentColour.getGreen();
-        int blue = currentColour.getBlue();
-
-        int dr = 255 - red;
-        int dg = 255 - green;
-        int db = 255 - blue;
-
-        dr *= t / 10f;
-        dg *= t / 10f;
-        db *= t / 10f;
-
-        red += (inc / 20f) * dr;
-        green += (inc / 20f) * dg;
-        blue += (inc / 20f) * db;
-
-        if (red > 255 || green > 255 || blue > 255)
-        {
-          currentColour = Color.white;
-        }
-        else
-        {
-          currentColour = new Color(red, green, blue);
-        }
-      }
+      return Color.white;
+    }
+
+    /*
+     * convert score 0-9 to a bleaching factor 1.1 - 0.2
+     */
+    float bleachFactor = (11 - (conservationScore - '0')) / 10f;
+
+    /*
+     * scale this by the percentage slider / 20
+     */
+    bleachFactor *= (inc / 20f);
+
+    int red = currentColour.getRed();
+    int green = currentColour.getGreen();
+    int blue = currentColour.getBlue();
+
+    /*
+     * bleach colours towards white (255, 255, 255),
+     * depending on the consensus score and the conservation slider value
+     * scores of:                      0  1  2  3  4  5  6  7  8  9
+     * fade to white at slider value: 18 20 22 25 29 33 40 50 67 100%
+     */
+    red += (255 - red) * bleachFactor;
+    green += (255 - green) * bleachFactor;
+    blue += (255 - blue) * bleachFactor;
+
+    if (red > 255 || green > 255 || blue > 255)
+    {
+      currentColour = Color.white;
+    }
+    else
+    {
+      currentColour = new Color(red, green, blue);
     }
     return currentColour;
   }
diff --git a/test/jalview/schemes/ResidueColourSchemeTest.java b/test/jalview/schemes/ResidueColourSchemeTest.java
new file mode 100644 (file)
index 0000000..8173dd1
--- /dev/null
@@ -0,0 +1,147 @@
+package jalview.schemes;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+import jalview.analysis.Profile;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class ResidueColourSchemeTest
+{
+  @Test(groups = "Functional")
+  public void testAboveThreshold()
+  {
+    /*
+     * make up profiles for this alignment:
+     * AR-Q
+     * AR--
+     * SR-T
+     * SR-T
+     */
+    Profile[] profiles = new Profile[4]; 
+    profiles[0] = new Profile(4, 0, 2, "AS");
+    profiles[1] = new Profile(4, 0, 4, "R");
+    profiles[2] = new Profile(4, 4, 0, "");
+    profiles[3] = new Profile(4, 1, 2, "T");
+    ResidueColourScheme rcs = new ResidueColourScheme();
+    rcs.setConsensus(profiles);
+    
+    /*
+     * no threshold
+     */
+    rcs.setThreshold(0, true);
+    assertTrue(rcs.aboveThreshold('a', 0));
+    assertTrue(rcs.aboveThreshold('S', 0));
+    assertFalse(rcs.aboveThreshold('W', 0));
+    assertTrue(rcs.aboveThreshold('R', 1));
+    assertFalse(rcs.aboveThreshold('W', 2));
+    assertTrue(rcs.aboveThreshold('t', 3));
+    assertFalse(rcs.aboveThreshold('Q', 3));
+
+    /*
+     * with threshold, include gaps
+     */
+    rcs.setThreshold(60, false);
+    assertFalse(rcs.aboveThreshold('a', 0));
+    assertFalse(rcs.aboveThreshold('S', 0));
+    assertTrue(rcs.aboveThreshold('R', 1));
+    assertFalse(rcs.aboveThreshold('W', 2));
+    assertFalse(rcs.aboveThreshold('t', 3)); // 50% < 60%
+
+    /*
+     * with threshold, ignore gaps
+     */
+    rcs.setThreshold(60, true);
+    assertFalse(rcs.aboveThreshold('a', 0));
+    assertFalse(rcs.aboveThreshold('S', 0));
+    assertTrue(rcs.aboveThreshold('R', 1));
+    assertFalse(rcs.aboveThreshold('W', 2));
+    assertTrue(rcs.aboveThreshold('t', 3)); // 67% > 60%
+  }
+
+  /**
+   * Test colour bleaching based on conservation score and conservation slider.
+   * Scores of 10 or 11 should leave colours unchanged. Gap is always white.
+   */
+  @Test(groups = "Functional")
+  public void testApplyConservation()
+  {
+    ResidueColourScheme rcs = new ResidueColourScheme();
+
+    // no conservation present - no fading
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 12));
+    
+    // cheat by setting conservation sequence directly
+    // rather than calculating it - good enough for this test
+    String consensus = "0123456789+*-";
+    rcs.conservation = consensus.toCharArray();
+
+    // column out of range:
+    assertEquals(Color.RED,
+            rcs.applyConservation(Color.RED, consensus.length()));
+
+    /*
+     * with 100% threshold, 'fade factor' is 
+     * (11-score)/10 * 100/20 = (11-score)/2
+     * which is >= 1 for all scores i.e. all fade to white except +, *
+     */
+    rcs.setConservationInc(100);
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 0));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 1));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 2));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 3));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 4));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 5));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 6));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 7));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 8));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 9));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 10));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 11));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 12));
+
+    /*
+     * with 0% threshold, there should be no fading
+     */
+    rcs.setConservationInc(0);
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 0));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 1));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 2));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 3));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 4));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 5));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 6));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 7));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 8));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 9));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 10));
+    assertEquals(Color.RED, rcs.applyConservation(Color.RED, 11));
+    assertEquals(Color.WHITE, rcs.applyConservation(Color.RED, 12)); // gap
+
+    /*
+     * with 40% threshold, 'fade factor' is 
+     * (11-score)/10 * 40/20 = (11-score)/5
+     * which is {>1, >1, >1, >1, >1, >1, 1, 0.8, 0.6, 0.4} for score 0-9
+     * e.g. score 7 colour fades 80% of the way to white (255, 255, 255)
+     */
+    rcs.setConservationInc(40);
+    Color colour = new Color(155, 105, 55);
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 0));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 1));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 2));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 3));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 4));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 5));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 6));
+    assertEquals(new Color(235, 225, 215), rcs.applyConservation(colour, 7));
+    assertEquals(new Color(215, 195, 175), rcs.applyConservation(colour, 8));
+    assertEquals(new Color(195, 165, 135), rcs.applyConservation(colour, 9));
+    assertEquals(colour, rcs.applyConservation(colour, 10));
+    assertEquals(colour, rcs.applyConservation(colour, 11));
+    assertEquals(Color.WHITE, rcs.applyConservation(colour, 12));
+  }
+}