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