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