JAL-4071 colour rows according to simple/graduated feature colour, beginning of suppo...
[jalview.git] / src / jalview / workers / ColumnCounterSetWorker.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.workers;
22
23 import java.awt.Color;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import jalview.api.AlignViewportI;
28 import jalview.api.AlignmentViewPanel;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.Annotation;
32 import jalview.datamodel.MappedFeatures;
33 import jalview.datamodel.SequenceFeature;
34 import jalview.datamodel.SequenceI;
35 import jalview.renderer.seqfeatures.FeatureRenderer;
36 import jalview.util.ColorUtils;
37 import jalview.util.Comparison;
38
39 /**
40  * A class to compute alignment annotations with column counts for a set of
41  * properties of interest on positions in an alignment. <br>
42  * This is designed to be extensible, by supplying to the constructor an object
43  * that computes a vector of counts for each residue position, based on the
44  * residue and and sequence features at that position.
45  * 
46  */
47 class ColumnCounterSetWorker extends AlignCalcWorker
48 {
49   FeatureSetCounterI counter;
50
51   /**
52    * Constructor registers the annotation for the given alignment frame
53    * 
54    * @param af
55    * @param counter
56    */
57   public ColumnCounterSetWorker(AlignViewportI viewport,
58           AlignmentViewPanel panel, FeatureSetCounterI counter)
59   {
60     super(viewport, panel);
61     ourAnnots = new ArrayList<>();
62     this.counter = counter;
63     calcMan.registerWorker(this);
64   }
65
66   /**
67    * method called under control of AlignCalcManager to recompute the annotation
68    * when the alignment changes
69    */
70   @Override
71   public void run()
72   {
73     boolean annotationAdded = false;
74     try
75     {
76       calcMan.notifyStart(this);
77
78       while (!calcMan.notifyWorking(this))
79       {
80         try
81         {
82           Thread.sleep(200);
83         } catch (InterruptedException ex)
84         {
85           ex.printStackTrace();
86         }
87       }
88       if (alignViewport.isClosed())
89       {
90         abortAndDestroy();
91         return;
92       }
93
94       if (alignViewport.getAlignment() != null)
95       {
96         try
97         {
98           annotationAdded = computeAnnotations();
99         } catch (IndexOutOfBoundsException x)
100         {
101           // probable race condition. just finish and return without any fuss.
102           return;
103         }
104       }
105     } catch (OutOfMemoryError error)
106     {
107       ap.raiseOOMWarning("calculating feature counts", error);
108       calcMan.disableWorker(this);
109     } finally
110     {
111       calcMan.workerComplete(this);
112     }
113
114     if (ap != null)
115     {
116       if (annotationAdded)
117       {
118         ap.adjustAnnotationHeight();
119       }
120       ap.paintAlignment(true, true);
121     }
122
123   }
124
125   /**
126    * Scan each column of the alignment to calculate a count by feature type. Set
127    * the count as the value of the alignment annotation for that feature type.
128    * 
129    * @return
130    */
131   boolean computeAnnotations()
132   {
133     FeatureRenderer fr = new FeatureRenderer(alignViewport);
134     // TODO use the commented out code once JAL-2075 is fixed
135     // to get adequate performance on genomic length sequence
136     AlignmentI alignment = alignViewport.getAlignment();
137     // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
138     // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
139
140     int rows = counter.getNames().length;
141
142     int width = alignment.getWidth();
143     int height = alignment.getHeight();
144     int[][] counts = new int[width][rows];
145     int max[] = new int[rows];
146     for (int crow = 0; crow < rows; crow++)
147     {
148       max[crow] = 0;
149     }
150
151     for (int col = 0; col < width; col++)
152     {
153       int[] count = counts[col];
154       for (int crow = 0; crow < rows; crow++)
155       {
156         count[crow] = 0;
157       }
158       for (int row = 0; row < height; row++)
159       {
160         int[] colcount = countFeaturesAt(alignment, col, row, fr);
161         if (colcount != null)
162         {
163           for (int crow = 0; crow < rows; crow++)
164           {
165             count[crow] += colcount[crow];
166           }
167         }
168       }
169       counts[col] = count;
170       for (int crow = 0; crow < rows; crow++)
171       {
172         max[crow] = Math.max(count[crow], max[crow]);
173       }
174     }
175
176     boolean annotationAdded = false;
177
178     for (int anrow = 0; anrow < rows; anrow++)
179     {
180       int[] minC = counter.getMinColour(anrow);
181       int[] maxC = counter.getMaxColour(anrow);
182       Color minColour = new Color(minC[0], minC[1], minC[2]);
183       Color maxColour = new Color(maxC[0], maxC[1], maxC[2]);
184
185       Annotation[] anns = new Annotation[width];
186       long rmax = 0;
187       /*
188        * add counts as annotations. zeros are needed since select-by-annotation ignores empty annotation positions
189        */
190       for (int i = 0; i < counts.length; i++)
191       {
192         int count = counts[i][anrow];
193
194         Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
195                 max[anrow], maxColour);
196         String str = String.valueOf(count);
197         anns[i] = new Annotation(str, str, '0', count, color);
198         rmax = Math.max(count, rmax);
199       }
200
201       /*
202        * construct or update the annotation
203        */
204       String description = counter.getDescriptions()[anrow];
205       if (!alignment.findAnnotation(description).iterator().hasNext())
206       {
207         annotationAdded = true;
208       }
209       AlignmentAnnotation ann = alignment.findOrCreateAnnotation(
210               counter.getNames()[anrow], description, false, null, null);
211       ann.description = description;
212       ann.showAllColLabels = true;
213       ann.scaleColLabel = true;
214       ann.graph = AlignmentAnnotation.BAR_GRAPH;
215       ann.annotations = anns;
216       ann.graphMin = 0f; // minimum always zero count
217       ann.graphMax = rmax; // maximum count from loop over feature columns
218       ann.validateRangeAndDisplay();
219       if (!ourAnnots.contains(ann))
220       {
221         ourAnnots.add(ann);
222       }
223     }
224     return annotationAdded;
225   }
226
227   void removeOldAnnotations(String[] annotDescs)
228   {
229     // TODO use the commented out code once JAL-2075 is fixed
230     // to get adequate performance on genomic length sequence
231     AlignmentI alignment = alignViewport.getAlignment();
232     ArrayList<AlignmentAnnotation> toRemove = new ArrayList<AlignmentAnnotation>();
233     for (String toRemoveDesc : annotDescs)
234     {
235       for (AlignmentAnnotation aa : ourAnnots)
236       {
237         if (toRemoveDesc.equals(aa.getCalcId()))
238           toRemove.add(aa);
239       }
240     }
241     for (AlignmentAnnotation deleted : toRemove)
242     {
243       alignment.deleteAnnotation(deleted);
244     }
245   }
246
247   /**
248    * Returns a count of any feature types present at the specified position of
249    * the alignment
250    * 
251    * @param alignment
252    * @param col
253    *          (0..)
254    * @param row
255    * @param fr
256    */
257   int[] countFeaturesAt(AlignmentI alignment, int col, int row,
258           FeatureRenderer fr)
259   {
260     SequenceI seq = alignment.getSequenceAt(row);
261     if (seq == null)
262     {
263       return null;
264     }
265     if (col >= seq.getLength())
266     {
267       return null;// sequence doesn't extend this far
268     }
269     char res = seq.getCharAt(col);
270     if (Comparison.isGap(res))
271     {
272       return null;
273     }
274
275     /*
276      * compute a count for any displayed features at residue
277      */
278     // see JAL-2075
279     List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, col + 1);
280     if (fr.hasRenderOrder())
281     {
282       MappedFeatures mappedFeatres = fr.findComplementFeaturesAtResidue(seq,
283               seq.findPosition(col));
284       features.addAll(mappedFeatres.features);
285     }
286     int[] count = this.counter.count(String.valueOf(res), features);
287     return count;
288   }
289
290   /**
291    * Method called when the user changes display options that may affect how the
292    * annotation is rendered, but do not change its values. Currently no such
293    * options affect user-defined annotation, so this method does nothing.
294    */
295   @Override
296   public void updateAnnotation()
297   {
298     // do nothing
299   }
300
301   /**
302    * Answers true to indicate that if this worker's annotation is deleted from
303    * the display, the worker should also be removed. This prevents it running
304    * and recreating the annotation when the alignment changes.
305    */
306   @Override
307   public boolean isDeletable()
308   {
309     return true;
310   }
311 }