38ab62279e7171c06705a7c593b5a195c6811fec
[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   /*
45    * lookup up by character value e.g. 'G' to the colors array index
46    * e.g. if symbolIndex['K'] = 11 then colors[11] is the colour for K
47    */
48   final int[] symbolIndex;
49
50   boolean conservationColouring = false;
51
52   /*
53    * colour for residue characters as indexed by symbolIndex
54    */
55   Color[] colors = null;
56
57   int threshold = 0;
58
59   /* Set when threshold colouring to either pid_gaps or pid_nogaps */
60   protected boolean ignoreGaps = false;
61
62   /*
63    * Consensus data indexed by column
64    */
65   ProfilesI consensus;
66
67   /*
68    * Conservation string as a char array 
69    */
70   char[] conservation;
71
72   /*
73    * The conservation slider percentage setting 
74    */
75   int inc = 30;
76
77   /**
78    * Creates a new ResidueColourScheme object.
79    * 
80    * @param final int[] index table into colors (ResidueProperties.naIndex or
81    *        ResidueProperties.aaIndex)
82    * @param colors
83    *          colours for symbols in sequences
84    * @param threshold
85    *          threshold for conservation shading
86    */
87   public ResidueColourScheme(int[] aaOrnaIndex, Color[] colours,
88           int threshold)
89   {
90     symbolIndex = aaOrnaIndex;
91     this.colors = colours;
92     this.threshold = threshold;
93   }
94
95   /**
96    * Creates a new ResidueColourScheme object with a lookup table for indexing
97    * the colour map
98    */
99   public ResidueColourScheme(int[] aaOrNaIndex)
100   {
101     symbolIndex = aaOrNaIndex;
102   }
103
104   /**
105    * Creates a new ResidueColourScheme object - default constructor for
106    * non-sequence dependent colourschemes
107    */
108   public ResidueColourScheme()
109   {
110     symbolIndex = null;
111   }
112
113   /**
114    * Returns the colour for symbol 'A'. Intended for use in a 'fixed colour'
115    * colour scheme (for example a feature colour).
116    */
117   @Override
118   public Color findColour()
119   {
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   @Override
350   public ColourSchemeI applyTo(AnnotatedCollectionI sg,
351           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
352   {
353     try
354     {
355       return getClass().newInstance();
356     } catch (Exception q)
357     {
358       throw new Error(MessageManager.formatMessage(
359               "error.implementation_error_cannot_duplicate_colour_scheme",
360               new String[] { getClass().getName() }), q);
361     }
362   }
363
364   /**
365    * Answers false if the colour scheme is nucleotide or peptide specific, and
366    * the data does not match, else false. Override to modify or extend this test
367    * as required.
368    */
369   @Override
370   public boolean isApplicableTo(AnnotatedCollectionI ac)
371   {
372     if (!isPeptideSpecific() && !isNucleotideSpecific())
373     {
374       return true;
375     }
376
377     /*
378      * inspect the data context (alignment dataset) for residue type
379      */
380     boolean nucleotide = false;
381     AnnotatedCollectionI context = ac.getContext();
382     if (context != null)
383     {
384       if (context instanceof AlignmentI)
385       {
386         nucleotide = ((AlignmentI) context).isNucleotide();
387       }
388       else
389       {
390         // not sure what's going on, play safe
391         return true;
392       }
393     }
394     else if (ac instanceof AlignmentI)
395     {
396       nucleotide = ((AlignmentI) ac).isNucleotide();
397     }
398     else
399     {
400       return true;
401     }
402
403     /*
404      * does data type match colour scheme type?
405      */
406     return (nucleotide && isNucleotideSpecific())
407             || (!nucleotide && isPeptideSpecific());
408   }
409
410   /**
411    * Answers true if the colour scheme is normally only for peptide data
412    * 
413    * @return
414    */
415   public boolean isPeptideSpecific()
416   {
417     return false;
418   }
419
420   /**
421    * Answers true if the colour scheme is normally only for nucleotide data
422    * 
423    * @return
424    */
425   public boolean isNucleotideSpecific()
426   {
427     return false;
428   }
429
430   @Override
431   public String getSchemeName()
432   {
433     return "Residue";
434   }
435 }