JAL-2386 JAL-2570 use ResidueShader copy constructor in SequenceGroup
[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    * Copy constructor
100    */
101   public ResidueShader(ResidueShader rs)
102   {
103     this.colourScheme = rs.colourScheme;
104     this.consensus = rs.consensus;
105     this.conservation = rs.conservation;
106     this.conservationColouring = rs.conservationColouring;
107     this.conservationIncrement = rs.conservationIncrement;
108     this.ignoreGaps = rs.ignoreGaps;
109     this.pidThreshold = rs.pidThreshold;
110   }
111
112   /**
113    * @see jalview.renderer.ResidueShaderI#setConsensus(jalview.datamodel.ProfilesI)
114    */
115   @Override
116   public void setConsensus(ProfilesI cons)
117   {
118     consensus = cons;
119   }
120
121   /**
122    * @see jalview.renderer.ResidueShaderI#conservationApplied()
123    */
124   @Override
125   public boolean conservationApplied()
126   {
127     return conservationColouring;
128   }
129
130   /**
131    * @see jalview.renderer.ResidueShaderI#setConservationApplied(boolean)
132    */
133   @Override
134   public void setConservationApplied(boolean conservationApplied)
135   {
136     conservationColouring = conservationApplied;
137   }
138
139   /**
140    * @see jalview.renderer.ResidueShaderI#setConservation(jalview.analysis.Conservation)
141    */
142   @Override
143   public void setConservation(Conservation cons)
144   {
145     if (cons == null)
146     {
147       conservationColouring = false;
148       conservation = null;
149     }
150     else
151     {
152       conservationColouring = true;
153       conservation = cons.getConsSequence().getSequenceAsString()
154               .toCharArray();
155     }
156   
157   }
158
159   /**
160    * @see jalview.renderer.ResidueShaderI#alignmentChanged(jalview.datamodel.AnnotatedCollectionI,
161    *      java.util.Map)
162    */
163   @Override
164   public void alignmentChanged(AnnotatedCollectionI alignment,
165           Map<SequenceI, SequenceCollectionI> hiddenReps)
166   {
167     if (colourScheme != null)
168     {
169       colourScheme.alignmentChanged(alignment, hiddenReps);
170     }
171   }
172
173   /**
174    * @see jalview.renderer.ResidueShaderI#setThreshold(int, boolean)
175    */
176   @Override
177   public void setThreshold(int consensusThreshold, boolean ignoreGaps)
178   {
179     pidThreshold = consensusThreshold;
180     this.ignoreGaps = ignoreGaps;
181   }
182
183   /**
184    * @see jalview.renderer.ResidueShaderI#setConservationInc(int)
185    */
186   @Override
187   public void setConservationInc(int i)
188   {
189     conservationIncrement = i;
190   }
191
192   /**
193    * @see jalview.renderer.ResidueShaderI#getConservationInc()
194    */
195   @Override
196   public int getConservationInc()
197   {
198     return conservationIncrement;
199   }
200
201   /**
202    * @see jalview.renderer.ResidueShaderI#getThreshold()
203    */
204   @Override
205   public int getThreshold()
206   {
207     return pidThreshold;
208   }
209
210   /**
211    * @see jalview.renderer.ResidueShaderI#findColour(char, int,
212    *      jalview.datamodel.SequenceI)
213    */
214   @Override
215   public Color findColour(char symbol, int position, SequenceI seq)
216   {
217     /*
218      * get 'base' colour
219      */
220     ProfileI profile = consensus == null ? null : consensus.get(position);
221     String modalResidue = profile == null ? null : profile
222             .getModalResidue();
223     float pid = profile == null ? 0f : profile
224             .getPercentageIdentity(ignoreGaps);
225     Color colour = colourScheme == null ? Color.white : colourScheme
226             .findColour(symbol, position, seq, modalResidue, pid);
227
228     /*
229      * apply PID threshold and consensus fading if in force
230      */
231     colour = adjustColour(symbol, position, colour);
232
233     return colour;
234   }
235
236   /**
237    * Adjusts colour by applying thresholding or conservation shading, if in
238    * force. That is
239    * <ul>
240    * <li>if there is a threshold set for colouring, and the residue doesn't
241    * match the consensus (or a joint consensus) residue, or the consensus score
242    * is not above the threshold, then the colour is set to white</li>
243    * <li>if conservation colouring is selected, the colour is faded by an amount
244    * depending on the conservation score for the column, and the conservation
245    * colour threshold</li>
246    * </ul>
247    * 
248    * @param symbol
249    * @param column
250    * @param colour
251    * @return
252    */
253   protected Color adjustColour(char symbol, int column, Color colour)
254   {
255     if (!aboveThreshold(symbol, column))
256     {
257       colour = Color.white;
258     }
259   
260     if (conservationColouring)
261     {
262       colour = applyConservation(colour, column);
263     }
264     return colour;
265   }
266
267   /**
268    * Answers true if there is a consensus profile for the specified column, and
269    * the given residue matches the consensus (or joint consensus) residue for
270    * the column, and the percentage identity for the profile is equal to or
271    * greater than the current threshold; else answers false. The percentage
272    * calculation depends on whether or not we are ignoring gapped sequences.
273    * 
274    * @param residue
275    * @param column
276    *          (index into consensus profiles)
277    * 
278    * @return
279    * @see #setThreshold(int, boolean)
280    */
281   protected boolean aboveThreshold(char residue, int column)
282   {
283     if (pidThreshold == 0)
284     {
285       return true;
286     }
287     if ('a' <= residue && residue <= 'z')
288     {
289       // TO UPPERCASE !!!
290       // Faster than toUpperCase
291       residue -= ('a' - 'A');
292     }
293   
294     if (consensus == null)
295     {
296       return false;
297     }
298   
299     ProfileI profile = consensus.get(column);
300   
301     /*
302      * test whether this is the consensus (or joint consensus) residue
303      */
304     if (profile != null
305             && profile.getModalResidue().contains(String.valueOf(residue)))
306     {
307       if (profile.getPercentageIdentity(ignoreGaps) >= pidThreshold)
308       {
309         return true;
310       }
311     }
312   
313     return false;
314   }
315
316   /**
317    * Applies a combination of column conservation score, and conservation
318    * percentage slider, to 'bleach' out the residue colours towards white.
319    * <p>
320    * If a column is fully conserved (identical residues, conservation score 11,
321    * shown as *), or all 10 physico-chemical properties are conserved
322    * (conservation score 10, shown as +), then the colour is left unchanged.
323    * <p>
324    * Otherwise a 'bleaching' factor is computed and applied to the colour. This
325    * is designed to fade colours for scores of 0-9 completely to white at slider
326    * positions ranging from 18% - 100% respectively.
327    * 
328    * @param currentColour
329    * @param column
330    * 
331    * @return bleached (or unmodified) colour
332    */
333   protected Color applyConservation(Color currentColour, int column)
334   {
335     if (conservation == null || conservation.length <= column)
336     {
337       return currentColour;
338     }
339     char conservationScore = conservation[column];
340   
341     /*
342      * if residues are fully conserved (* or 11), or all properties
343      * are conserved (+ or 10), leave colour unchanged
344      */
345     if (conservationScore == '*' || conservationScore == '+'
346             || conservationScore == (char) 10
347             || conservationScore == (char) 11)
348     {
349       return currentColour;
350     }
351   
352     if (Comparison.isGap(conservationScore))
353     {
354       return Color.white;
355     }
356   
357     /*
358      * convert score 0-9 to a bleaching factor 1.1 - 0.2
359      */
360     float bleachFactor = (11 - (conservationScore - '0')) / 10f;
361   
362     /*
363      * scale this up by 0-5 (percentage slider / 20)
364      * as a result, scores of:         0  1  2  3  4  5  6  7  8  9
365      * fade to white at slider value: 18 20 22 25 29 33 40 50 67 100%
366      */
367     bleachFactor *= (conservationIncrement / 20f);
368   
369     return ColorUtils.bleachColour(currentColour, bleachFactor);
370   }
371
372   /**
373    * @see jalview.renderer.ResidueShaderI#getColourScheme()
374    */
375   @Override
376   public ColourSchemeI getColourScheme()
377   {
378     return this.colourScheme;
379   }
380
381   /**
382    * @see jalview.renderer.ResidueShaderI#setColourScheme(jalview.schemes.ColourSchemeI)
383    */
384   @Override
385   public void setColourScheme(ColourSchemeI cs)
386   {
387     colourScheme = cs;
388   }
389 }