JAL-2360 refactoring for JalviewColourScheme enum,
[jalview.git] / src / jalview / schemes / ResidueColourScheme.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.schemes;
22
23 import jalview.analysis.Conservation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.AnnotatedCollectionI;
26 import jalview.datamodel.ProfileI;
27 import jalview.datamodel.ProfilesI;
28 import jalview.datamodel.SequenceCollectionI;
29 import jalview.datamodel.SequenceI;
30 import jalview.util.ColorUtils;
31 import jalview.util.Comparison;
32 import jalview.util.MessageManager;
33
34 import java.awt.Color;
35 import java.util.Map;
36
37 /**
38  * Base class for residue-based colour schemes
39  */
40 public class ResidueColourScheme implements ColourSchemeI
41 {
42   public static final String NONE = "None";
43
44   final int[] symbolIndex;
45
46   boolean conservationColouring = false;
47
48   Color[] colors = null;
49
50   int threshold = 0;
51
52   /* Set when threshold colouring to either pid_gaps or pid_nogaps */
53   protected boolean ignoreGaps = false;
54
55   /*
56    * Consensus data indexed by column
57    */
58   ProfilesI consensus;
59
60   /*
61    * Conservation string as a char array 
62    */
63   char[] conservation;
64
65   /*
66    * The conservation slider percentage setting 
67    */
68   int inc = 30;
69
70   /**
71    * Creates a new ResidueColourScheme object.
72    * 
73    * @param final int[] index table into colors (ResidueProperties.naIndex or
74    *        ResidueProperties.aaIndex)
75    * @param colors
76    *          colours for symbols in sequences
77    * @param threshold
78    *          threshold for conservation shading
79    */
80   public ResidueColourScheme(int[] aaOrnaIndex, Color[] colours,
81           int threshold)
82   {
83     symbolIndex = aaOrnaIndex;
84     this.colors = colours;
85     this.threshold = threshold;
86   }
87
88   /**
89    * Creates a new ResidueColourScheme object with a lookup table for indexing
90    * the colour map
91    */
92   public ResidueColourScheme(int[] aaOrNaIndex)
93   {
94     symbolIndex = aaOrNaIndex;
95   }
96
97   /**
98    * Creates a new ResidueColourScheme object - default constructor for
99    * non-sequence dependent colourschemes
100    */
101   public ResidueColourScheme()
102   {
103     symbolIndex = null;
104   }
105
106   /**
107    * Returns the colour for symbol 'A'. Intended for use in a 'fixed colour'
108    * colour scheme (for example a feature colour).
109    */
110   @Override
111   public Color findColour()
112   {
113     return findColour('A');
114   }
115
116   /**
117    * Find a colour without an index in a sequence
118    */
119   @Override
120   public Color findColour(char c)
121   {
122     return colors == null ? Color.white : colors[symbolIndex[c]];
123   }
124
125   @Override
126   public Color findColour(char c, int j, SequenceI seq)
127   {
128     Color currentColour;
129
130     if (colors != null && symbolIndex != null && (threshold == 0)
131             || aboveThreshold(c, j))
132     {
133       currentColour = colors[symbolIndex[c]];
134     }
135     else
136     {
137       currentColour = Color.white;
138     }
139
140     if (conservationColouring)
141     {
142       currentColour = applyConservation(currentColour, j);
143     }
144
145     return currentColour;
146   }
147
148   /**
149    * Get the percentage threshold for this colour scheme
150    * 
151    * @return Returns the percentage threshold
152    */
153   @Override
154   public int getThreshold()
155   {
156     return threshold;
157   }
158
159   /**
160    * Sets the percentage consensus threshold value, and whether gaps are ignored
161    * in percentage identity calculation
162    * 
163    * @param consensusThreshold
164    * @param ignoreGaps
165    */
166   @Override
167   public void setThreshold(int consensusThreshold, boolean ignoreGaps)
168   {
169     threshold = consensusThreshold;
170     this.ignoreGaps = ignoreGaps;
171   }
172
173   /**
174    * Answers true if there is a consensus profile for the specified column, and
175    * the given residue matches the consensus (or joint consensus) residue for
176    * the column, and the percentage identity for the profile is equal to or
177    * greater than the current threshold; else answers false. The percentage
178    * calculation depends on whether or not we are ignoring gapped sequences.
179    * 
180    * @param residue
181    * @param column
182    *          (index into consensus profiles)
183    * 
184    * @return
185    * @see #setThreshold(int, boolean)
186    */
187   public boolean aboveThreshold(char residue, int column)
188   {
189     if ('a' <= residue && residue <= 'z')
190     {
191       // TO UPPERCASE !!!
192       // Faster than toUpperCase
193       residue -= ('a' - 'A');
194     }
195
196     if (consensus == null)
197     {
198       return false;
199     }
200
201     ProfileI profile = consensus.get(column);
202
203     /*
204      * test whether this is the consensus (or joint consensus) residue
205      */
206     if (profile != null
207             && profile.getModalResidue().contains(String.valueOf(residue)))
208     {
209       if (profile.getPercentageIdentity(ignoreGaps) >= threshold)
210       {
211         return true;
212       }
213     }
214
215     return false;
216   }
217
218   @Override
219   public boolean conservationApplied()
220   {
221     return conservationColouring;
222   }
223
224   @Override
225   public void setConservationApplied(boolean conservationApplied)
226   {
227     conservationColouring = conservationApplied;
228   }
229
230   @Override
231   public void setConservationInc(int i)
232   {
233     inc = i;
234   }
235
236   @Override
237   public int getConservationInc()
238   {
239     return inc;
240   }
241
242   /**
243    * DOCUMENT ME!
244    * 
245    * @param consensus
246    *          DOCUMENT ME!
247    */
248   @Override
249   public void setConsensus(ProfilesI consensus)
250   {
251     if (consensus == null)
252     {
253       return;
254     }
255
256     this.consensus = consensus;
257   }
258
259   @Override
260   public void setConservation(Conservation cons)
261   {
262     if (cons == null)
263     {
264       conservationColouring = false;
265       conservation = null;
266     }
267     else
268     {
269       conservationColouring = true;
270       int iSize = cons.getConsSequence().getLength();
271       conservation = new char[iSize];
272       for (int i = 0; i < iSize; i++)
273       {
274         conservation[i] = cons.getConsSequence().getCharAt(i);
275       }
276     }
277
278   }
279
280   /**
281    * Applies a combination of column conservation score, and conservation
282    * percentage slider, to 'bleach' out the residue colours towards white.
283    * <p>
284    * If a column is fully conserved (identical residues, conservation score 11,
285    * shown as *), or all 10 physico-chemical properties are conserved
286    * (conservation score 10, shown as +), then the colour is left unchanged.
287    * <p>
288    * Otherwise a 'bleaching' factor is computed and applied to the colour. This
289    * is designed to fade colours for scores of 0-9 completely to white at slider
290    * positions ranging from 18% - 100% respectively.
291    * 
292    * @param currentColour
293    * @param column
294    * 
295    * @return bleached (or unmodified) colour
296    */
297   Color applyConservation(Color currentColour, int column)
298   {
299     if (conservation == null || conservation.length <= column)
300     {
301       return currentColour;
302     }
303     char conservationScore = conservation[column];
304
305     /*
306      * if residues are fully conserved (* or 11), or all properties
307      * are conserved (+ or 10), leave colour unchanged
308      */
309     if (conservationScore == '*' || conservationScore == '+'
310             || conservationScore == (char) 10
311             || conservationScore == (char) 11)
312     {
313       return currentColour;
314     }
315
316     if (Comparison.isGap(conservationScore))
317     {
318       return Color.white;
319     }
320
321     /*
322      * convert score 0-9 to a bleaching factor 1.1 - 0.2
323      */
324     float bleachFactor = (11 - (conservationScore - '0')) / 10f;
325
326     /*
327      * scale this up by 0-5 (percentage slider / 20)
328      * as a result, scores of:         0  1  2  3  4  5  6  7  8  9
329      * fade to white at slider value: 18 20 22 25 29 33 40 50 67 100%
330      */
331     bleachFactor *= (inc / 20f);
332
333     return ColorUtils.bleachColour(currentColour, bleachFactor);
334   }
335
336   @Override
337   public void alignmentChanged(AnnotatedCollectionI alignment,
338           Map<SequenceI, SequenceCollectionI> hiddenReps)
339   {
340   }
341
342   @Override
343   public ColourSchemeI applyTo(AnnotatedCollectionI sg,
344           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
345   {
346     try
347     {
348       return getClass().newInstance();
349     } catch (Exception q)
350     {
351       throw new Error(MessageManager.formatMessage(
352               "error.implementation_error_cannot_duplicate_colour_scheme",
353               new String[] { getClass().getName() }), q);
354     }
355   }
356
357   /**
358    * Answers false if the colour scheme is nucleotide or peptide specific, and
359    * the data does not match, else false. Override to modify or extend this test
360    * as required.
361    */
362   @Override
363   public boolean isApplicableTo(AnnotatedCollectionI ac)
364   {
365     if (!isPeptideSpecific() && !isNucleotideSpecific())
366     {
367       return true;
368     }
369
370     /*
371      * inspect the data context (alignment dataset) for residue type
372      */
373     boolean nucleotide = false;
374     AnnotatedCollectionI context = ac.getContext();
375     if (context != null)
376     {
377       if (context instanceof AlignmentI)
378       {
379         nucleotide = ((AlignmentI) context).isNucleotide();
380       }
381       else
382       {
383         // not sure what's going on, play safe
384         return true;
385       }
386     }
387     else if (ac instanceof AlignmentI)
388     {
389       nucleotide = ((AlignmentI) ac).isNucleotide();
390     }
391     else
392     {
393       return true;
394     }
395
396     /*
397      * does data type match colour scheme type?
398      */
399     return (nucleotide && isNucleotideSpecific())
400             || (!nucleotide && isPeptideSpecific());
401   }
402
403   /**
404    * Answers true if the colour scheme is normally only for peptide data
405    * 
406    * @return
407    */
408   public boolean isPeptideSpecific()
409   {
410     return false;
411   }
412
413   /**
414    * Answers true if the colour scheme is normally only for nucleotide data
415    * 
416    * @return
417    */
418   public boolean isNucleotideSpecific()
419   {
420     return false;
421   }
422
423   @Override
424   public String getSchemeName()
425   {
426     return "Residue";
427   }
428 }