JAL-2346 restore choice of annotation for colouring faithfully
[jalview.git] / src / jalview / schemes / AnnotationColourGradient.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 java.awt.Color;
24 import java.util.IdentityHashMap;
25 import java.util.Map;
26
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AnnotatedCollectionI;
30 import jalview.datamodel.Annotation;
31 import jalview.datamodel.GraphLine;
32 import jalview.datamodel.SequenceCollectionI;
33 import jalview.datamodel.SequenceI;
34
35 public class AnnotationColourGradient extends FollowerColourScheme
36 {
37   public static final int NO_THRESHOLD = -1;
38
39   public static final int BELOW_THRESHOLD = 0;
40
41   public static final int ABOVE_THRESHOLD = 1;
42
43   private final AlignmentAnnotation annotation;
44
45   private final int aboveAnnotationThreshold;
46
47   private boolean thresholdIsMinMax = false;
48
49   private GraphLine annotationThreshold;
50
51   private int redMin;
52
53   private int greenMin;
54
55   private int blueMin;
56
57   private int redRange;
58
59   private int greenRange;
60
61   private int blueRange;
62
63   private boolean predefinedColours = false;
64
65   private boolean seqAssociated = false;
66
67   /**
68    * false if the scheme was constructed without a minColour and maxColour used
69    * to decide if existing colours should be taken from annotation elements when
70    * they exist
71    */
72   private boolean noGradient = false;
73
74   private IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
75
76   @Override
77   public ColourSchemeI applyTo(AnnotatedCollectionI sg,
78           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
79   {
80     AnnotationColourGradient acg = new AnnotationColourGradient(annotation,
81             colourScheme, aboveAnnotationThreshold);
82     acg.thresholdIsMinMax = thresholdIsMinMax;
83     acg.annotationThreshold = (annotationThreshold == null) ? null
84             : new GraphLine(annotationThreshold);
85     acg.redMin = redMin;
86     acg.greenMin = greenMin;
87     acg.blueMin = blueMin;
88     acg.redRange = redRange;
89     acg.greenRange = greenRange;
90     acg.blueRange = blueRange;
91     acg.predefinedColours = predefinedColours;
92     acg.seqAssociated = seqAssociated;
93     acg.noGradient = noGradient;
94     return acg;
95   }
96
97   /**
98    * Creates a new AnnotationColourGradient object.
99    */
100   public AnnotationColourGradient(AlignmentAnnotation annotation,
101           ColourSchemeI originalColour, int aboveThreshold)
102   {
103     if (originalColour instanceof AnnotationColourGradient)
104     {
105       colourScheme = ((AnnotationColourGradient) originalColour).colourScheme;
106     }
107     else
108     {
109       colourScheme = originalColour;
110     }
111
112     this.annotation = annotation;
113
114     aboveAnnotationThreshold = aboveThreshold;
115
116     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
117     {
118       annotationThreshold = annotation.threshold;
119     }
120     // clear values so we don't get weird black bands...
121     redMin = 254;
122     greenMin = 254;
123     blueMin = 254;
124     redRange = 0;
125     greenRange = 0;
126     blueRange = 0;
127
128     noGradient = true;
129     checkLimits();
130   }
131
132   /**
133    * Creates a new AnnotationColourGradient object.
134    */
135   public AnnotationColourGradient(AlignmentAnnotation annotation,
136           Color minColour, Color maxColour, int aboveThreshold)
137   {
138     this.annotation = annotation;
139
140     aboveAnnotationThreshold = aboveThreshold;
141
142     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
143     {
144       annotationThreshold = annotation.threshold;
145     }
146
147     redMin = minColour.getRed();
148     greenMin = minColour.getGreen();
149     blueMin = minColour.getBlue();
150
151     redRange = maxColour.getRed() - redMin;
152     greenRange = maxColour.getGreen() - greenMin;
153     blueRange = maxColour.getBlue() - blueMin;
154
155     noGradient = false;
156     checkLimits();
157   }
158
159   private void checkLimits()
160   {
161     aamax = annotation.graphMax;
162     aamin = annotation.graphMin;
163     if (annotation.isRNA())
164     {
165       // reset colour palette
166       ColourSchemeProperty.resetRnaHelicesShading();
167       ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
168     }
169   }
170
171   @Override
172   public void alignmentChanged(AnnotatedCollectionI alignment,
173           Map<SequenceI, SequenceCollectionI> hiddenReps)
174   {
175     super.alignmentChanged(alignment, hiddenReps);
176
177     if (seqAssociated && annotation.getCalcId() != null)
178     {
179       if (seqannot != null)
180       {
181         seqannot.clear();
182       }
183       else
184       {
185         seqannot = new IdentityHashMap<SequenceI, AlignmentAnnotation>();
186       }
187       // resolve the context containing all the annotation for the sequence
188       AnnotatedCollectionI alcontext = alignment instanceof AlignmentI ? alignment
189               : alignment.getContext();
190       boolean f = true, rna = false;
191       for (AlignmentAnnotation alan : alcontext.findAnnotation(annotation
192               .getCalcId()))
193       {
194         if (alan.sequenceRef != null
195                 && (alan.label != null && annotation != null && alan.label
196                         .equals(annotation.label)))
197         {
198           if (!rna && alan.isRNA())
199           {
200             rna = true;
201           }
202           seqannot.put(alan.sequenceRef, alan);
203           if (f || alan.graphMax > aamax)
204           {
205             aamax = alan.graphMax;
206           }
207           if (f || alan.graphMin < aamin)
208           {
209             aamin = alan.graphMin;
210           }
211           f = false;
212         }
213       }
214       if (rna)
215       {
216         ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
217       }
218     }
219   }
220
221   float aamin = 0f, aamax = 0f;
222
223   public AlignmentAnnotation getAnnotation()
224   {
225     return annotation;
226   }
227
228   public int getAboveThreshold()
229   {
230     return aboveAnnotationThreshold;
231   }
232
233   public float getAnnotationThreshold()
234   {
235     if (annotationThreshold == null)
236     {
237       return 0;
238     }
239     else
240     {
241       return annotationThreshold.value;
242     }
243   }
244
245   public Color getMinColour()
246   {
247     return new Color(redMin, greenMin, blueMin);
248   }
249
250   public Color getMaxColour()
251   {
252     return new Color(redMin + redRange, greenMin + greenRange, blueMin
253             + blueRange);
254   }
255
256   /**
257    * DOCUMENT ME!
258    * 
259    * @param n
260    *          DOCUMENT ME!
261    * 
262    * @return DOCUMENT ME!
263    */
264   @Override
265   public Color findColour(char c)
266   {
267     return Color.red;
268   }
269
270   /**
271    * DOCUMENT ME!
272    * 
273    * @param n
274    *          DOCUMENT ME!
275    * @param j
276    *          DOCUMENT ME!
277    * 
278    * @return DOCUMENT ME!
279    */
280   @Override
281   public Color findColour(char c, int j, SequenceI seq)
282   {
283     Color currentColour = Color.white;
284     AlignmentAnnotation ann = (seqAssociated && seqannot != null ? seqannot
285             .get(seq) : this.annotation);
286     if (ann == null)
287     {
288       return currentColour;
289     }
290     if ((threshold == 0) || aboveThreshold(c, j))
291     {
292       if (ann.annotations != null
293               && j < ann.annotations.length
294               && ann.annotations[j] != null
295               && !jalview.util.Comparison.isGap(c))
296       {
297         Annotation aj = ann.annotations[j];
298         // 'use original colours' => colourScheme != null
299         // -> look up colour to be used
300         // predefined colours => preconfigured shading
301         // -> only use original colours reference if thresholding enabled &
302         // minmax exists
303         // annotation.hasIcons => null or black colours replaced with glyph
304         // colours
305         // -> reuse original colours if present
306         // -> if thresholding enabled then return colour on non-whitespace glyph
307
308         if (aboveAnnotationThreshold == NO_THRESHOLD
309                 || (annotationThreshold != null && (aboveAnnotationThreshold == ABOVE_THRESHOLD ? aj.value >= annotationThreshold.value
310                         : aj.value <= annotationThreshold.value)))
311         {
312           if (predefinedColours && aj.colour != null
313                   && !aj.colour.equals(Color.black))
314           {
315             currentColour = aj.colour;
316           }
317           else if (ann.hasIcons
318                   && ann.graph == AlignmentAnnotation.NO_GRAPH)
319           {
320             if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
321                     && aj.secondaryStructure != '-')
322             {
323               if (colourScheme != null)
324               {
325                 currentColour = colourScheme.findColour(c, j, seq);
326               }
327               else
328               {
329                 if (ann.isRNA())
330                 {
331                   currentColour = ColourSchemeProperty.rnaHelices[(int) aj.value];
332                 }
333                 else
334                 {
335                   currentColour = ann.annotations[j].secondaryStructure == 'H' ? jalview.renderer.AnnotationRenderer.HELIX_COLOUR
336                           : ann.annotations[j].secondaryStructure == 'E' ? jalview.renderer.AnnotationRenderer.SHEET_COLOUR
337                                   : jalview.renderer.AnnotationRenderer.STEM_COLOUR;
338                 }
339               }
340             }
341             else
342             {
343               //
344               return Color.white;
345             }
346           }
347           else if (noGradient)
348           {
349             if (colourScheme != null)
350             {
351               currentColour = colourScheme.findColour(c, j, seq);
352             }
353             else
354             {
355               if (aj.colour != null)
356               {
357                 currentColour = aj.colour;
358               }
359             }
360           }
361           else
362           {
363             currentColour = shadeCalculation(ann, j);
364           }
365         }
366         if (conservationColouring)
367         {
368           currentColour = applyConservation(currentColour, j);
369         }
370       }
371     }
372     return currentColour;
373   }
374
375   private Color shadeCalculation(AlignmentAnnotation ann, int j)
376   {
377
378     // calculate a shade
379     float range = 1f;
380     if (thresholdIsMinMax && ann.threshold != null
381             && aboveAnnotationThreshold == ABOVE_THRESHOLD
382             && ann.annotations[j].value >= ann.threshold.value)
383     {
384       range = (ann.annotations[j].value - ann.threshold.value)
385               / (ann.graphMax - ann.threshold.value);
386     }
387     else if (thresholdIsMinMax && ann.threshold != null
388             && aboveAnnotationThreshold == BELOW_THRESHOLD
389             && ann.annotations[j].value >= ann.graphMin)
390     {
391       range = (ann.annotations[j].value - ann.graphMin)
392               / (ann.threshold.value - ann.graphMin);
393     }
394     else
395     {
396       if (ann.graphMax != ann.graphMin)
397       {
398         range = (ann.annotations[j].value - ann.graphMin)
399                 / (ann.graphMax - ann.graphMin);
400       }
401       else
402       {
403         range = 0f;
404       }
405     }
406
407     int dr = (int) (redRange * range + redMin);
408     int dg = (int) (greenRange * range + greenMin);
409     int db = (int) (blueRange * range + blueMin);
410
411     return new Color(dr, dg, db);
412
413   }
414
415   public boolean isPredefinedColours()
416   {
417     return predefinedColours;
418   }
419
420   public void setPredefinedColours(boolean predefinedColours)
421   {
422     this.predefinedColours = predefinedColours;
423   }
424
425   public boolean isSeqAssociated()
426   {
427     return seqAssociated;
428   }
429
430   public void setSeqAssociated(boolean sassoc)
431   {
432     seqAssociated = sassoc;
433   }
434
435   public boolean isThresholdIsMinMax()
436   {
437     return thresholdIsMinMax;
438   }
439
440   public void setThresholdIsMinMax(boolean thresholdIsMinMax)
441   {
442     this.thresholdIsMinMax = thresholdIsMinMax;
443   }
444 }