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