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