7dbccedf52679cbe973741642787ad15472e99c2
[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    * Returns the colour for symbol 'A'. Intended for use in a 'fixed colour'
116    * colour scheme (for example a feature colour).
117    */
118   @Override
119   public Color findColour()
120   {
121     // TODO delete this method in favour of ColorUtils.parseColourString()?
122     return findColour('A');
123   }
124
125   /**
126    * Find a colour without an index in a sequence
127    */
128   @Override
129   public Color findColour(char c)
130   {
131     return colors == null ? Color.white : colors[symbolIndex[c]];
132   }
133
134   @Override
135   public Color findColour(char c, int j, SequenceI seq)
136   {
137     Color colour = Color.white;
138
139     if (colors != null && symbolIndex != null)
140     {
141       colour = colors[symbolIndex[c]];
142     }
143     colour = adjustColour(c, j, colour);
144
145     return colour;
146   }
147
148   /**
149    * Adjusts colour by applying thresholding or conservation shading, if in
150    * force. That is
151    * <ul>
152    * <li>if there is a threshold set for colouring, and the residue doesn't
153    * match the consensus (or a joint consensus) residue, or the consensus score
154    * is not above the threshold, then the colour is set to white</li>
155    * <li>if conservation colouring is selected, the colour is faded by an amount
156    * depending on the conservation score for the column, and the conservation
157    * colour threshold</li>
158    * </ul>
159    * 
160    * @param symbol
161    * @param column
162    * @param colour
163    * @return
164    */
165   protected Color adjustColour(char symbol, int column, Color colour)
166   {
167     if (!aboveThreshold(symbol, column))
168     {
169       colour = Color.white;
170     }
171
172     if (conservationColouring)
173     {
174       colour = applyConservation(colour, column);
175     }
176     return colour;
177   }
178
179   /**
180    * Get the percentage threshold for this colour scheme
181    * 
182    * @return Returns the percentage threshold
183    */
184   @Override
185   public int getThreshold()
186   {
187     return threshold;
188   }
189
190   /**
191    * Sets the percentage consensus threshold value, and whether gaps are ignored
192    * in percentage identity calculation
193    * 
194    * @param consensusThreshold
195    * @param ignoreGaps
196    */
197   @Override
198   public void setThreshold(int consensusThreshold, boolean ignoreGaps)
199   {
200     threshold = consensusThreshold;
201     this.ignoreGaps = ignoreGaps;
202   }
203
204   /**
205    * Answers true if there is a consensus profile for the specified column, and
206    * the given residue matches the consensus (or joint consensus) residue for
207    * the column, and the percentage identity for the profile is equal to or
208    * greater than the current threshold; else answers false. The percentage
209    * calculation depends on whether or not we are ignoring gapped sequences.
210    * 
211    * @param residue
212    * @param column
213    *          (index into consensus profiles)
214    * 
215    * @return
216    * @see #setThreshold(int, boolean)
217    */
218   public boolean aboveThreshold(char residue, int column)
219   {
220     if (threshold == 0)
221     {
222       return true;
223     }
224     if ('a' <= residue && residue <= 'z')
225     {
226       // TO UPPERCASE !!!
227       // Faster than toUpperCase
228       residue -= ('a' - 'A');
229     }
230
231     if (consensus == null)
232     {
233       return false;
234     }
235
236     ProfileI profile = consensus.get(column);
237
238     /*
239      * test whether this is the consensus (or joint consensus) residue
240      */
241     if (profile != null
242             && profile.getModalResidue().contains(String.valueOf(residue)))
243     {
244       if (profile.getPercentageIdentity(ignoreGaps) >= threshold)
245       {
246         return true;
247       }
248     }
249
250     return false;
251   }
252
253   @Override
254   public boolean conservationApplied()
255   {
256     return conservationColouring;
257   }
258
259   @Override
260   public void setConservationApplied(boolean conservationApplied)
261   {
262     conservationColouring = conservationApplied;
263   }
264
265   @Override
266   public void setConservationInc(int i)
267   {
268     inc = i;
269   }
270
271   @Override
272   public int getConservationInc()
273   {
274     return inc;
275   }
276
277   /**
278    * DOCUMENT ME!
279    * 
280    * @param consensus
281    *          DOCUMENT ME!
282    */
283   @Override
284   public void setConsensus(ProfilesI consensus)
285   {
286     if (consensus == null)
287     {
288       return;
289     }
290
291     this.consensus = consensus;
292   }
293
294   @Override
295   public void setConservation(Conservation cons)
296   {
297     if (cons == null)
298     {
299       conservationColouring = false;
300       conservation = null;
301     }
302     else
303     {
304       conservationColouring = true;
305       int iSize = cons.getConsSequence().getLength();
306       conservation = new char[iSize];
307       for (int i = 0; i < iSize; i++)
308       {
309         conservation[i] = cons.getConsSequence().getCharAt(i);
310       }
311     }
312
313   }
314
315   /**
316    * Applies a combination of column conservation score, and conservation
317    * percentage slider, to 'bleach' out the residue colours towards white.
318    * <p>
319    * If a column is fully conserved (identical residues, conservation score 11,
320    * shown as *), or all 10 physico-chemical properties are conserved
321    * (conservation score 10, shown as +), then the colour is left unchanged.
322    * <p>
323    * Otherwise a 'bleaching' factor is computed and applied to the colour. This
324    * is designed to fade colours for scores of 0-9 completely to white at slider
325    * positions ranging from 18% - 100% respectively.
326    * 
327    * @param currentColour
328    * @param column
329    * 
330    * @return bleached (or unmodified) colour
331    */
332   Color applyConservation(Color currentColour, int column)
333   {
334     if (conservation == null || conservation.length <= column)
335     {
336       return currentColour;
337     }
338     char conservationScore = conservation[column];
339
340     /*
341      * if residues are fully conserved (* or 11), or all properties
342      * are conserved (+ or 10), leave colour unchanged
343      */
344     if (conservationScore == '*' || conservationScore == '+'
345             || conservationScore == (char) 10
346             || conservationScore == (char) 11)
347     {
348       return currentColour;
349     }
350
351     if (Comparison.isGap(conservationScore))
352     {
353       return Color.white;
354     }
355
356     /*
357      * convert score 0-9 to a bleaching factor 1.1 - 0.2
358      */
359     float bleachFactor = (11 - (conservationScore - '0')) / 10f;
360
361     /*
362      * scale this up by 0-5 (percentage slider / 20)
363      * as a result, scores of:         0  1  2  3  4  5  6  7  8  9
364      * fade to white at slider value: 18 20 22 25 29 33 40 50 67 100%
365      */
366     bleachFactor *= (inc / 20f);
367
368     return ColorUtils.bleachColour(currentColour, bleachFactor);
369   }
370
371   @Override
372   public void alignmentChanged(AnnotatedCollectionI alignment,
373           Map<SequenceI, SequenceCollectionI> hiddenReps)
374   {
375   }
376
377   /**
378    * Answers false if the colour scheme is nucleotide or peptide specific, and
379    * the data does not match, else true. Override to modify or extend this test
380    * as required.
381    */
382   @Override
383   public boolean isApplicableTo(AnnotatedCollectionI ac)
384   {
385     if (!isPeptideSpecific() && !isNucleotideSpecific())
386     {
387       return true;
388     }
389
390     /*
391      * inspect the data context (alignment) for residue type
392      */
393     boolean nucleotide = false;
394     if (ac instanceof AlignmentI)
395     {
396       nucleotide = ((AlignmentI) ac).isNucleotide();
397     }
398     else
399     {
400       AnnotatedCollectionI context = ac.getContext();
401       if (context instanceof AlignmentI)
402       {
403         nucleotide = ((AlignmentI) context).isNucleotide();
404       }
405       else
406       {
407         // not sure what's going on, play safe
408         return true;
409       }
410     }
411
412     /*
413      * does data type match colour scheme type?
414      */
415     return (nucleotide && isNucleotideSpecific())
416             || (!nucleotide && isPeptideSpecific());
417   }
418
419   /**
420    * Answers true if the colour scheme is normally only for peptide data
421    * 
422    * @return
423    */
424   public boolean isPeptideSpecific()
425   {
426     return false;
427   }
428
429   /**
430    * Answers true if the colour scheme is normally only for nucleotide data
431    * 
432    * @return
433    */
434   public boolean isNucleotideSpecific()
435   {
436     return false;
437   }
438
439   /**
440    * Default method returns true. Override this to return false in colour
441    * schemes that are not determined solely by the sequence symbol.
442    */
443   @Override
444   public boolean isSimple()
445   {
446     return true;
447   }
448 }