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