JAL-2286 javadoc, refactor, unit tests for threshold and conservation
[jalview.git] / src / jalview / schemes / ResidueColourScheme.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.schemes;
22
23 import jalview.analysis.Conservation;
24 import jalview.analysis.Profile;
25 import jalview.datamodel.AnnotatedCollectionI;
26 import jalview.datamodel.SequenceCollectionI;
27 import jalview.datamodel.SequenceI;
28 import jalview.util.Comparison;
29 import jalview.util.MessageManager;
30
31 import java.awt.Color;
32 import java.util.Map;
33
34 /**
35  * DOCUMENT ME!
36  * 
37  * @author $author$
38  * @version $Revision$
39  */
40 public class ResidueColourScheme implements ColourSchemeI
41 {
42   final int[] symbolIndex;
43
44   boolean conservationColouring = false;
45
46   Color[] colors = null;
47
48   int threshold = 0;
49
50   /* Set when threshold colouring to either pid_gaps or pid_nogaps */
51   protected boolean ignoreGaps = false;
52
53   /*
54    * Consensus data indexed by column
55    */
56   Profile[] consensus;
57
58   /*
59    * Conservation string as a char array 
60    */
61   char[] conservation;
62
63   /*
64    * The conservation slider percentage setting 
65    */
66   int inc = 30;
67
68   /**
69    * Creates a new ResidueColourScheme object.
70    * 
71    * @param final int[] index table into colors (ResidueProperties.naIndex or
72    *        ResidueProperties.aaIndex)
73    * @param colors
74    *          colours for symbols in sequences
75    * @param threshold
76    *          threshold for conservation shading
77    */
78   public ResidueColourScheme(int[] aaOrnaIndex, Color[] colours,
79           int threshold)
80   {
81     symbolIndex = aaOrnaIndex;
82     this.colors = colours;
83     this.threshold = threshold;
84   }
85
86   /**
87    * Creates a new ResidueColourScheme object with a lookup table for indexing
88    * the colour map
89    */
90   public ResidueColourScheme(int[] aaOrNaIndex)
91   {
92     symbolIndex = aaOrNaIndex;
93   }
94
95   /**
96    * Creates a new ResidueColourScheme object - default constructor for
97    * non-sequence dependent colourschemes
98    */
99   public ResidueColourScheme()
100   {
101     symbolIndex = null;
102   }
103
104   /**
105    * Find a colour without an index in a sequence
106    */
107   @Override
108   public Color findColour(char c)
109   {
110     return colors == null ? Color.white : colors[symbolIndex[c]];
111   }
112
113   @Override
114   public Color findColour(char c, int j, SequenceI seq)
115   {
116     Color currentColour;
117
118     if (colors != null && symbolIndex != null && (threshold == 0)
119             || aboveThreshold(c, j))
120     {
121       currentColour = colors[symbolIndex[c]];
122     }
123     else
124     {
125       currentColour = Color.white;
126     }
127
128     if (conservationColouring)
129     {
130       currentColour = applyConservation(currentColour, j);
131     }
132
133     return currentColour;
134   }
135
136   /**
137    * Get the percentage threshold for this colour scheme
138    * 
139    * @return Returns the percentage threshold
140    */
141   @Override
142   public int getThreshold()
143   {
144     return threshold;
145   }
146
147   /**
148    * Sets the percentage consensus threshold value, and whether gaps are ignored
149    * in percentage identity calculation
150    * 
151    * @param consensusThreshold
152    * @param ignoreGaps
153    */
154   @Override
155   public void setThreshold(int consensusThreshold, boolean ignoreGaps)
156   {
157     threshold = consensusThreshold;
158     this.ignoreGaps = ignoreGaps;
159   }
160
161   /**
162    * Answers true if there is a consensus profile for the specified column, and
163    * the given residue matches the consensus (or joint consensus) residue for
164    * the column, and the percentage identity for the profile is equal to or
165    * greater than the current threshold; else answers false. The percentage
166    * calculation depends on whether or not we are ignoring gapped sequences.
167    * 
168    * @param residue
169    * @param column
170    *          (index into consensus profiles)
171    * 
172    * @return
173    * @see #setThreshold(int, boolean)
174    */
175   public boolean aboveThreshold(char residue, int column)
176   {
177     if ('a' <= residue && residue <= 'z')
178     {
179       // TO UPPERCASE !!!
180       // Faster than toUpperCase
181       residue -= ('a' - 'A');
182     }
183
184     if (consensus == null || consensus.length < column
185             || consensus[column] == null)
186     {
187       return false;
188     }
189
190     /*
191      * test whether this is the consensus (or joint consensus) residue
192      */
193     if (consensus[column].getModalResidue().contains(
194             String.valueOf(residue)))
195     {
196       if (consensus[column].getPercentageIdentity(ignoreGaps) >= threshold)
197       {
198         return true;
199       }
200     }
201
202     return false;
203   }
204
205   @Override
206   public boolean conservationApplied()
207   {
208     return conservationColouring;
209   }
210
211   @Override
212   public void setConservationApplied(boolean conservationApplied)
213   {
214     conservationColouring = conservationApplied;
215   }
216
217   @Override
218   public void setConservationInc(int i)
219   {
220     inc = i;
221   }
222
223   @Override
224   public int getConservationInc()
225   {
226     return inc;
227   }
228
229   /**
230    * DOCUMENT ME!
231    * 
232    * @param consensus
233    *          DOCUMENT ME!
234    */
235   @Override
236   public void setConsensus(Profile[] consensus)
237   {
238     if (consensus == null)
239     {
240       return;
241     }
242
243     this.consensus = consensus;
244   }
245
246   @Override
247   public void setConservation(Conservation cons)
248   {
249     if (cons == null)
250     {
251       conservationColouring = false;
252       conservation = null;
253     }
254     else
255     {
256       conservationColouring = true;
257       int iSize = cons.getConsSequence().getLength();
258       conservation = new char[iSize];
259       for (int i = 0; i < iSize; i++)
260       {
261         conservation[i] = cons.getConsSequence().getCharAt(i);
262       }
263     }
264
265   }
266
267   /**
268    * Applies a combination of column conservation score, and conservation
269    * percentage slider, to 'bleach' out the residue colours towards white.
270    * <p>
271    * If a column is fully conserved (identical residues, conservation score 11,
272    * shown as *), or all 10 physico-chemical properties are conserved
273    * (conservation score 10, shown as +), then the colour is left unchanged.
274    * <p>
275    * Otherwise a 'bleaching' factor is computed and applied to the colour. This
276    * is designed to fade colours for scores of 0-9 completely to white at slider
277    * positions ranging from 18% - 100% respectively.
278    * 
279    * @param currentColour
280    * @param column
281    * 
282    * @return bleached (or unmodified) colour
283    */
284   Color applyConservation(Color currentColour, int column)
285   {
286     if (conservation == null || conservation.length <= column)
287     {
288       return currentColour;
289     }
290     char conservationScore = conservation[column];
291
292     /*
293      * if residues are fully conserved (* or 11), or all properties
294      * are conserved (+ or 10), leave colour unchanged
295      */
296     if (conservationScore == '*' || conservationScore == '+'
297             || conservationScore == (char) 10
298             || conservationScore == (char) 11)
299     {
300       return currentColour;
301     }
302
303     if (Comparison.isGap(conservationScore))
304     {
305       return Color.white;
306     }
307
308     /*
309      * convert score 0-9 to a bleaching factor 1.1 - 0.2
310      */
311     float bleachFactor = (11 - (conservationScore - '0')) / 10f;
312
313     /*
314      * scale this by the percentage slider / 20
315      */
316     bleachFactor *= (inc / 20f);
317
318     int red = currentColour.getRed();
319     int green = currentColour.getGreen();
320     int blue = currentColour.getBlue();
321
322     /*
323      * bleach colours towards white (255, 255, 255),
324      * depending on the consensus score and the conservation slider value
325      * scores of:                      0  1  2  3  4  5  6  7  8  9
326      * fade to white at slider value: 18 20 22 25 29 33 40 50 67 100%
327      */
328     red += (255 - red) * bleachFactor;
329     green += (255 - green) * bleachFactor;
330     blue += (255 - blue) * bleachFactor;
331
332     if (red > 255 || green > 255 || blue > 255)
333     {
334       currentColour = Color.white;
335     }
336     else
337     {
338       currentColour = new Color(red, green, blue);
339     }
340     return currentColour;
341   }
342
343   @Override
344   public void alignmentChanged(AnnotatedCollectionI alignment,
345           Map<SequenceI, SequenceCollectionI> hiddenReps)
346   {
347   }
348
349   @Override
350   public ColourSchemeI applyTo(AnnotatedCollectionI sg,
351           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
352   {
353     try
354     {
355       return getClass().newInstance();
356     } catch (Exception q)
357     {
358       throw new Error(MessageManager.formatMessage(
359               "error.implementation_error_cannot_duplicate_colour_scheme",
360               new String[] { getClass().getName() }), q);
361     }
362   }
363 }