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