JAL-2099 belated unit test
[jalview.git] / src / jalview / schemes / CollectionColourScheme.java
1 package jalview.schemes;
2
3 import jalview.analysis.Conservation;
4 import jalview.datamodel.AnnotatedCollectionI;
5 import jalview.datamodel.ProfileI;
6 import jalview.datamodel.ProfilesI;
7 import jalview.datamodel.SequenceCollectionI;
8 import jalview.datamodel.SequenceI;
9 import jalview.util.ColorUtils;
10 import jalview.util.Comparison;
11
12 import java.awt.Color;
13 import java.util.Map;
14
15 /**
16  * A class that computes the colouring of an alignment (or subgroup). Currently
17  * the factors that may influence residue colouring are
18  * <ul>
19  * <li>the colour scheme that provides a colour for each aligned residue</li>
20  * <li>any threshold for colour, based on percentage identity with consensus</li>
21  * <li>any graduation based on conservation of physico-chemical properties</li>
22  * </ul>
23  * 
24  * @author gmcarstairs
25  *
26  */
27 public class CollectionColourScheme implements CollectionColourSchemeI
28 {
29   private ColourSchemeI colourScheme;
30
31   private ProfilesI consensus;
32
33   private boolean conservationColouring;
34
35   private char[] conservation;
36
37   private int threshold;
38
39   private boolean ignoreGaps;
40
41   private int inc;
42
43   public CollectionColourScheme(ColourSchemeI cs)
44   {
45     colourScheme = cs;
46   }
47
48   /**
49    * Default constructor
50    */
51   public CollectionColourScheme()
52   {
53   }
54
55   /**
56    * @see jalview.schemes.CollectionColourSchemeI#setConsensus(jalview.datamodel.ProfilesI)
57    */
58   @Override
59   public void setConsensus(ProfilesI cons)
60   {
61     consensus = cons;
62   }
63
64   /**
65    * @see jalview.schemes.CollectionColourSchemeI#conservationApplied()
66    */
67   @Override
68   public boolean conservationApplied()
69   {
70     return conservationColouring;
71   }
72
73   /**
74    * @see jalview.schemes.CollectionColourSchemeI#setConservationApplied(boolean)
75    */
76   @Override
77   public void setConservationApplied(boolean conservationApplied)
78   {
79     conservationColouring = conservationApplied;
80   }
81
82   /**
83    * @see jalview.schemes.CollectionColourSchemeI#setConservation(jalview.analysis.Conservation)
84    */
85   @Override
86   public void setConservation(Conservation cons)
87   {
88     if (cons == null)
89     {
90       conservationColouring = false;
91       conservation = null;
92     }
93     else
94     {
95       conservationColouring = true;
96       conservation = cons.getConsSequence().getSequenceAsString()
97               .toCharArray();
98     }
99   
100   }
101
102   /**
103    * @see jalview.schemes.CollectionColourSchemeI#alignmentChanged(jalview.datamodel.AnnotatedCollectionI,
104    *      java.util.Map)
105    */
106   @Override
107   public void alignmentChanged(AnnotatedCollectionI alignment,
108           Map<SequenceI, SequenceCollectionI> hiddenReps)
109   {
110     if (colourScheme != null)
111     {
112       colourScheme.alignmentChanged(alignment, hiddenReps);
113     }
114   }
115
116   /**
117    * @see jalview.schemes.CollectionColourSchemeI#setThreshold(int, boolean)
118    */
119   @Override
120   public void setThreshold(int consensusThreshold, boolean ignoreGaps)
121   {
122     threshold = consensusThreshold;
123     this.ignoreGaps = ignoreGaps;
124   }
125
126   /**
127    * @see jalview.schemes.CollectionColourSchemeI#setConservationInc(int)
128    */
129   @Override
130   public void setConservationInc(int i)
131   {
132     inc = i;
133   }
134
135   /**
136    * @see jalview.schemes.CollectionColourSchemeI#getConservationInc()
137    */
138   @Override
139   public int getConservationInc()
140   {
141     return inc;
142   }
143
144   /**
145    * @see jalview.schemes.CollectionColourSchemeI#getThreshold()
146    */
147   @Override
148   public int getThreshold()
149   {
150     return threshold;
151   }
152
153   /**
154    * @see jalview.schemes.CollectionColourSchemeI#findColour(char, int,
155    *      jalview.datamodel.SequenceI)
156    */
157   @Override
158   public Color findColour(char symbol, int position, SequenceI seq)
159   {
160     /*
161      * get 'base' colour
162      */
163     ProfileI profile = consensus == null ? null : consensus.get(position);
164     String modalResidue = profile == null ? null : profile
165             .getModalResidue();
166     float pid = profile == null ? 0f : profile
167             .getPercentageIdentity(ignoreGaps);
168     Color colour = colourScheme == null ? Color.white : colourScheme
169             .findColour(symbol, position, seq, modalResidue, pid);
170
171     /*
172      * apply PID threshold and consensus fading if in force
173      */
174     colour = adjustColour(symbol, position, colour);
175
176     return colour;
177   }
178
179   /**
180    * Adjusts colour by applying thresholding or conservation shading, if in
181    * force. That is
182    * <ul>
183    * <li>if there is a threshold set for colouring, and the residue doesn't
184    * match the consensus (or a joint consensus) residue, or the consensus score
185    * is not above the threshold, then the colour is set to white</li>
186    * <li>if conservation colouring is selected, the colour is faded by an amount
187    * depending on the conservation score for the column, and the conservation
188    * colour threshold</li>
189    * </ul>
190    * 
191    * @param symbol
192    * @param column
193    * @param colour
194    * @return
195    */
196   protected Color adjustColour(char symbol, int column, Color colour)
197   {
198     if (!aboveThreshold(symbol, column))
199     {
200       colour = Color.white;
201     }
202   
203     if (conservationColouring)
204     {
205       colour = applyConservation(colour, column);
206     }
207     return colour;
208   }
209
210   /**
211    * Answers true if there is a consensus profile for the specified column, and
212    * the given residue matches the consensus (or joint consensus) residue for
213    * the column, and the percentage identity for the profile is equal to or
214    * greater than the current threshold; else answers false. The percentage
215    * calculation depends on whether or not we are ignoring gapped sequences.
216    * 
217    * @param residue
218    * @param column
219    *          (index into consensus profiles)
220    * 
221    * @return
222    * @see #setThreshold(int, boolean)
223    */
224   protected boolean aboveThreshold(char residue, int column)
225   {
226     if (threshold == 0)
227     {
228       return true;
229     }
230     if ('a' <= residue && residue <= 'z')
231     {
232       // TO UPPERCASE !!!
233       // Faster than toUpperCase
234       residue -= ('a' - 'A');
235     }
236   
237     if (consensus == null)
238     {
239       return false;
240     }
241   
242     ProfileI profile = consensus.get(column);
243   
244     /*
245      * test whether this is the consensus (or joint consensus) residue
246      */
247     if (profile != null
248             && profile.getModalResidue().contains(String.valueOf(residue)))
249     {
250       if (profile.getPercentageIdentity(ignoreGaps) >= threshold)
251       {
252         return true;
253       }
254     }
255   
256     return false;
257   }
258
259   /**
260    * Applies a combination of column conservation score, and conservation
261    * percentage slider, to 'bleach' out the residue colours towards white.
262    * <p>
263    * If a column is fully conserved (identical residues, conservation score 11,
264    * shown as *), or all 10 physico-chemical properties are conserved
265    * (conservation score 10, shown as +), then the colour is left unchanged.
266    * <p>
267    * Otherwise a 'bleaching' factor is computed and applied to the colour. This
268    * is designed to fade colours for scores of 0-9 completely to white at slider
269    * positions ranging from 18% - 100% respectively.
270    * 
271    * @param currentColour
272    * @param column
273    * 
274    * @return bleached (or unmodified) colour
275    */
276   protected Color applyConservation(Color currentColour, int column)
277   {
278     if (conservation == null || conservation.length <= column)
279     {
280       return currentColour;
281     }
282     char conservationScore = conservation[column];
283   
284     /*
285      * if residues are fully conserved (* or 11), or all properties
286      * are conserved (+ or 10), leave colour unchanged
287      */
288     if (conservationScore == '*' || conservationScore == '+'
289             || conservationScore == (char) 10
290             || conservationScore == (char) 11)
291     {
292       return currentColour;
293     }
294   
295     if (Comparison.isGap(conservationScore))
296     {
297       return Color.white;
298     }
299   
300     /*
301      * convert score 0-9 to a bleaching factor 1.1 - 0.2
302      */
303     float bleachFactor = (11 - (conservationScore - '0')) / 10f;
304   
305     /*
306      * scale this up by 0-5 (percentage slider / 20)
307      * as a result, scores of:         0  1  2  3  4  5  6  7  8  9
308      * fade to white at slider value: 18 20 22 25 29 33 40 50 67 100%
309      */
310     bleachFactor *= (inc / 20f);
311   
312     return ColorUtils.bleachColour(currentColour, bleachFactor);
313   }
314
315   /**
316    * @see jalview.schemes.CollectionColourSchemeI#getColourScheme()
317    */
318   @Override
319   public ColourSchemeI getColourScheme()
320   {
321     return this.colourScheme;
322   }
323
324   /**
325    * @see jalview.schemes.CollectionColourSchemeI#setColourScheme(jalview.schemes.ColourSchemeI)
326    */
327   @Override
328   public void setColourScheme(ColourSchemeI cs)
329   {
330     colourScheme = cs;
331   }
332 }