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