Merge branch 'bug/JAL-3120restoreFeatureColour' into merge/JAL-3120
[jalview.git] / src / jalview / workers / ConsensusThread.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.analysis.AAFrequency;
24 import jalview.api.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.datamodel.AlignmentAnnotation;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.Annotation;
29 import jalview.datamodel.ProfilesI;
30 import jalview.datamodel.SequenceI;
31 import jalview.renderer.ResidueShaderI;
32
33 public class ConsensusThread extends AlignCalcWorker
34 {
35   public ConsensusThread(AlignViewportI alignViewport,
36           AlignmentViewPanel alignPanel)
37   {
38     super(alignViewport, alignPanel);
39   }
40
41   @Override
42   public void run()
43   {
44     if (calcMan.isPending(this))
45     {
46       return;
47     }
48     calcMan.notifyStart(this);
49     long started = System.currentTimeMillis();
50     try
51     {
52       AlignmentAnnotation consensus = getConsensusAnnotation();
53       AlignmentAnnotation gap = getGapAnnotation();
54       if ((consensus == null && gap == null) || calcMan.isPending(this))
55       {
56         calcMan.workerComplete(this);
57         return;
58       }
59       while (!calcMan.notifyWorking(this))
60       {
61         // System.err.println("Thread
62         // (Consensus"+Thread.currentThread().getName()+") Waiting around.");
63         try
64         {
65           if (ap != null)
66           {
67             ap.paintAlignment(false, false);
68           }
69           Thread.sleep(200);
70         } catch (Exception ex)
71         {
72           ex.printStackTrace();
73         }
74       }
75       if (alignViewport.isClosed())
76       {
77         abortAndDestroy();
78         return;
79       }
80       AlignmentI alignment = alignViewport.getAlignment();
81
82       int aWidth = -1;
83
84       if (alignment == null || (aWidth = alignment.getWidth()) < 0)
85       {
86         calcMan.workerComplete(this);
87         return;
88       }
89
90       eraseConsensus(aWidth);
91       computeConsensus(alignment);
92       updateResultAnnotation(true);
93
94       if (ap != null)
95       {
96         ap.paintAlignment(true, true);
97       }
98     } catch (OutOfMemoryError error)
99     {
100       calcMan.disableWorker(this);
101       ap.raiseOOMWarning("calculating consensus", error);
102     } finally
103     {
104       /*
105        * e.g. ArrayIndexOutOfBoundsException can happen due to a race condition
106        * - alignment was edited at same time as calculation was running
107        */
108       calcMan.workerComplete(this);
109     }
110   }
111
112   /**
113    * Clear out any existing consensus annotations
114    * 
115    * @param aWidth
116    *          the width (number of columns) of the annotated alignment
117    */
118   protected void eraseConsensus(int aWidth)
119   {
120     AlignmentAnnotation consensus = getConsensusAnnotation();
121     if (consensus != null)
122     {
123       consensus.annotations = new Annotation[aWidth];
124     }
125     AlignmentAnnotation gap = getGapAnnotation();
126     if (gap != null)
127     {
128       gap.annotations = new Annotation[aWidth];
129     }
130   }
131
132   /**
133    * @param alignment
134    */
135   protected void computeConsensus(AlignmentI alignment)
136   {
137
138     SequenceI[] aseqs = getSequences();
139     int width = alignment.getWidth();
140     ProfilesI hconsensus = AAFrequency.calculate(aseqs, width, 0, width,
141             true);
142
143     alignViewport.setSequenceConsensusHash(hconsensus);
144     setColourSchemeConsensus(hconsensus);
145   }
146
147   /**
148    * @return
149    */
150   protected SequenceI[] getSequences()
151   {
152     return alignViewport.getAlignment().getSequencesArray();
153   }
154
155   /**
156    * @param hconsensus
157    */
158   protected void setColourSchemeConsensus(ProfilesI hconsensus)
159   {
160     ResidueShaderI cs = alignViewport.getResidueShading();
161     if (cs != null)
162     {
163       cs.setConsensus(hconsensus);
164     }
165   }
166
167   /**
168    * Get the Consensus annotation for the alignment
169    * 
170    * @return
171    */
172   protected AlignmentAnnotation getConsensusAnnotation()
173   {
174     return alignViewport.getAlignmentConsensusAnnotation();
175   }
176
177   /**
178    * Get the Gap annotation for the alignment
179    * 
180    * @return
181    */
182   protected AlignmentAnnotation getGapAnnotation()
183   {
184     return alignViewport.getAlignmentGapAnnotation();
185   }
186
187   /**
188    * update the consensus annotation from the sequence profile data using
189    * current visualization settings.
190    */
191   @Override
192   public void updateAnnotation()
193   {
194     updateResultAnnotation(false);
195   }
196
197   public void updateResultAnnotation(boolean immediate)
198   {
199     AlignmentAnnotation consensus = getConsensusAnnotation();
200     ProfilesI hconsensus = (ProfilesI) getViewportConsensus();
201     if (immediate || !calcMan.isWorking(this) && consensus != null
202             && hconsensus != null)
203     {
204       deriveConsensus(consensus, hconsensus);
205       AlignmentAnnotation gap = getGapAnnotation();
206       if (gap != null)
207       {
208         deriveGap(gap, hconsensus);
209       }
210     }
211   }
212
213   /**
214    * Convert the computed consensus data into the desired annotation for
215    * display.
216    * 
217    * @param consensusAnnotation
218    *          the annotation to be populated
219    * @param hconsensus
220    *          the computed consensus data
221    */
222   protected void deriveConsensus(AlignmentAnnotation consensusAnnotation,
223           ProfilesI hconsensus)
224   {
225
226     long nseq = getSequences().length;
227     AAFrequency.completeConsensus(consensusAnnotation, hconsensus,
228             hconsensus.getStartColumn(), hconsensus.getEndColumn() + 1,
229             alignViewport.isIgnoreGapsConsensus(),
230             alignViewport.isShowSequenceLogo(), nseq);
231   }
232
233   /**
234    * Convert the computed consensus data into a gap annotation row for display.
235    * 
236    * @param gapAnnotation
237    *          the annotation to be populated
238    * @param hconsensus
239    *          the computed consensus data
240    */
241   protected void deriveGap(AlignmentAnnotation gapAnnotation,
242           ProfilesI hconsensus)
243   {
244     long nseq = getSequences().length;
245     AAFrequency.completeGapAnnot(gapAnnotation, hconsensus,
246             hconsensus.getStartColumn(), hconsensus.getEndColumn() + 1,
247             nseq);
248   }
249
250   /**
251    * Get the consensus data stored on the viewport.
252    * 
253    * @return
254    */
255   protected Object getViewportConsensus()
256   {
257     // TODO convert ComplementConsensusThread to use Profile
258     return alignViewport.getSequenceConsensusHash();
259   }
260 }