cf6a229ba91c677fb60277ef0cc62f06f05ec29e
[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 jalview.api.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.Annotation;
28 import jalview.datamodel.SequenceFeature;
29 import jalview.datamodel.SequenceI;
30 import jalview.renderer.seqfeatures.FeatureRenderer;
31 import jalview.util.ColorUtils;
32 import jalview.util.Comparison;
33
34 import java.awt.Color;
35 import java.util.ArrayList;
36 import java.util.List;
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<AlignmentAnnotation>();
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     try
73     {
74       calcMan.notifyStart(this);
75
76       while (!calcMan.notifyWorking(this))
77       {
78         try
79         {
80           Thread.sleep(200);
81         } catch (InterruptedException ex)
82         {
83           ex.printStackTrace();
84         }
85       }
86       if (alignViewport.isClosed())
87       {
88         abortAndDestroy();
89         return;
90       }
91
92       if (alignViewport.getAlignment() != null)
93       {
94         try
95         {
96           computeAnnotations();
97         } catch (IndexOutOfBoundsException x)
98         {
99           // probable race condition. just finish and return without any fuss.
100           return;
101         }
102       }
103     } catch (OutOfMemoryError error)
104     {
105       ap.raiseOOMWarning("calculating feature counts", error);
106       calcMan.disableWorker(this);
107     } finally
108     {
109       calcMan.workerComplete(this);
110     }
111
112     if (ap != null)
113     {
114       ap.adjustAnnotationHeight();
115       ap.paintAlignment(true);
116     }
117
118   }
119
120   /**
121    * Scan each column of the alignment to calculate a count by feature type. Set
122    * the count as the value of the alignment annotation for that feature type.
123    */
124   void computeAnnotations()
125   {
126     FeatureRenderer fr = new FeatureRenderer(alignViewport);
127     // TODO use the commented out code once JAL-2075 is fixed
128     // to get adequate performance on genomic length sequence
129     AlignmentI alignment = alignViewport.getAlignment();
130     // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
131     // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
132
133     int rows = counter.getNames().length;
134
135     int width = alignment.getWidth();
136     int height = alignment.getHeight();
137     int[][] counts = new int[width][rows];
138     int max[] = new int[rows];
139     for (int crow = 0; crow < rows; crow++)
140     {
141       max[crow] = 0;
142     }
143     for (int col = 0; col < width; col++)
144     {
145       int[] count = counts[col];
146       for (int crow = 0; crow < rows; crow++)
147       {
148         count[crow] = 0;
149       }
150       for (int row = 0; row < height; row++)
151       {
152         int[] colcount = countFeaturesAt(alignment, col, row, fr);
153         if (colcount != null)
154         {
155           for (int crow = 0; crow < rows; crow++)
156           {
157             count[crow] += colcount[crow];
158           }
159         }
160       }
161       counts[col] = count;
162       for (int crow = 0; crow < rows; crow++)
163       {
164         max[crow] = Math.max(count[crow], max[crow]);
165       }
166     }
167     for (int anrow = 0; anrow < rows; anrow++)
168     {
169       Annotation[] anns = new Annotation[width];
170       /*
171        * add non-zero counts as annotations
172        */
173       for (int i = 0; i < counts.length; i++)
174       {
175         int count = counts[i][anrow];
176         if (count > 0)
177         {
178           Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan,
179                   max[anrow], Color.blue);
180           String str = String.valueOf(count);
181           anns[i] = new Annotation(str, str, '0', count, color);
182         }
183       }
184
185       /*
186        * construct or update the annotation
187        */
188       AlignmentAnnotation ann = alignViewport.getAlignment()
189               .findOrCreateAnnotation(counter.getNames()[anrow],
190                       counter.getDescriptions()[anrow], false, null,
191                       null);
192       ann.description = counter.getDescriptions()[anrow];
193       ann.showAllColLabels = true;
194       ann.scaleColLabel = true;
195       ann.graph = AlignmentAnnotation.BAR_GRAPH;
196       ann.annotations = anns;
197       setGraphMinMax(ann, anns);
198       ann.validateRangeAndDisplay();
199       if (!ourAnnots.contains(ann))
200       {
201         ourAnnots.add(ann);
202       }
203     }
204   }
205
206   /**
207    * Returns a count of any feature types present at the specified position of
208    * the alignment
209    * 
210    * @param alignment
211    * @param col
212    * @param row
213    * @param fr
214    */
215   int[] countFeaturesAt(AlignmentI alignment, int col, int row,
216           FeatureRenderer fr)
217   {
218     SequenceI seq = alignment.getSequenceAt(row);
219     if (seq == null)
220     {
221       return null;
222     }
223     if (col >= seq.getLength())
224     {
225       return null;// sequence doesn't extend this far
226     }
227     char res = seq.getCharAt(col);
228     if (Comparison.isGap(res))
229     {
230       return null;
231     }
232     int pos = seq.findPosition(col);
233
234     /*
235      * compute a count for any displayed features at residue
236      */
237     // NB have to adjust pos if using AlignmentView.getVisibleAlignment
238     // see JAL-2075
239     List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
240     int[] count = this.counter.count(String.valueOf(res), features);
241     return count;
242   }
243
244   /**
245    * Method called when the user changes display options that may affect how the
246    * annotation is rendered, but do not change its values. Currently no such
247    * options affect user-defined annotation, so this method does nothing.
248    */
249   @Override
250   public void updateAnnotation()
251   {
252     // do nothing
253   }
254
255   /**
256    * Answers true to indicate that if this worker's annotation is deleted from
257    * the display, the worker should also be removed. This prevents it running
258    * and recreating the annotation when the alignment changes.
259    */
260   @Override
261   public boolean isDeletable()
262   {
263     return true;
264   }
265 }