JAL-2068 framework and example scripts for pluggable alignment
[jalview.git] / src / jalview / workers / ColumnCounterWorker.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.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.Annotation;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.AlignFrame;
29 import jalview.gui.AlignmentPanel;
30 import jalview.gui.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 an alignment annotation with column counts of any
40  * properties of interest of positions in an alignment. <br>
41  * This is designed to be extensible, by supplying to the constructor an object
42  * that computes a count for each residue position, based on the residue value
43  * and any sequence features at that position.
44  * 
45  */
46 class ColumnCounterWorker extends AlignCalcWorker
47 {
48   FeatureCounterI counter;
49
50   /**
51    * Constructor registers the annotation for the given alignment frame
52    * 
53    * @param af
54    * @param counter
55    */
56   public ColumnCounterWorker(AlignFrame af, FeatureCounterI counter)
57   {
58     super(af.getViewport(), af.alignPanel);
59     ourAnnots = new ArrayList<AlignmentAnnotation>();
60     this.counter = counter;
61     calcMan.registerWorker(this);
62   }
63
64   /**
65    * method called under control of AlignCalcManager to recompute the annotation
66    * when the alignment changes
67    */
68   @Override
69   public void run()
70   {
71     try
72     {
73       calcMan.notifyStart(this);
74
75       while (!calcMan.notifyWorking(this))
76       {
77         try
78         {
79           Thread.sleep(200);
80         } catch (InterruptedException ex)
81         {
82           ex.printStackTrace();
83         }
84       }
85       if (alignViewport.isClosed())
86       {
87         abortAndDestroy();
88         return;
89       }
90
91       removeAnnotation();
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.workerCannotRun(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((AlignmentPanel) ap);
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 width = alignmentView.getWidth();
134     int width = alignment.getWidth();
135     int height = alignment.getHeight();
136     int[] counts = new int[width];
137     int max = 0;
138
139     for (int col = 0; col < width; col++)
140     {
141       int count = 0;
142       for (int row = 0; row < height; row++)
143       {
144         count += countFeaturesAt(alignment, col, row, fr);
145       }
146       counts[col] = count;
147       max = Math.max(count, max);
148     }
149
150     Annotation[] anns = new Annotation[width];
151     /*
152      * add non-zero counts as annotations
153      */
154     for (int i = 0; i < counts.length; i++)
155     {
156       int count = counts[i];
157       if (count > 0)
158       {
159         Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan,
160                 max, Color.blue);
161         anns[i] = new Annotation(String.valueOf(count),
162                 String.valueOf(count), '0', count, color);
163       }
164     }
165
166     /*
167      * construct the annotation, save it and add it to the displayed alignment
168      */
169     AlignmentAnnotation ann = new AlignmentAnnotation(counter.getName(),
170             counter.getDescription(), anns);
171     ann.showAllColLabels = true;
172     ann.graph = AlignmentAnnotation.BAR_GRAPH;
173     ourAnnots.add(ann);
174     alignViewport.getAlignment().addAnnotation(ann);
175   }
176
177   /**
178    * Returns a count of any feature types present at the specified position of
179    * the alignment
180    * 
181    * @param alignment
182    * @param col
183    * @param row
184    * @param fr
185    */
186   int countFeaturesAt(AlignmentI alignment, int col, int row,
187           FeatureRenderer fr)
188   {
189     SequenceI seq = alignment.getSequenceAt(row);
190     if (seq == null)
191     {
192       return 0;
193     }
194     if (col >= seq.getLength())
195     {
196       return 0;// sequence doesn't extend this far
197     }
198     char res = seq.getCharAt(col);
199     if (Comparison.isGap(res))
200     {
201       return 0;
202     }
203     int pos = seq.findPosition(col);
204
205     /*
206      * compute a count for any displayed features at residue
207      */
208     // NB have to adjust pos if using AlignmentView.getVisibleAlignment
209     // see JAL-2075
210     List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
211     int count = this.counter.count(String.valueOf(res), features);
212     return count;
213   }
214
215   /**
216    * Method called when the user changes display options that may affect how the
217    * annotation is rendered, but do not change its values. Currently no such
218    * options affect user-defined annotation, so this method does nothing.
219    */
220   @Override
221   public void updateAnnotation()
222   {
223     // do nothing
224   }
225 }