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