JAL-2629 storage and lifecycle of alignment/group HMM annotations revised
[jalview.git] / src / jalview / workers / InformationThread.java
1 package jalview.workers;
2
3 import jalview.analysis.AAFrequency;
4 import jalview.api.AlignViewportI;
5 import jalview.api.AlignmentViewPanel;
6 import jalview.datamodel.AlignmentAnnotation;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.Annotation;
9 import jalview.datamodel.HiddenMarkovModel;
10 import jalview.datamodel.ProfilesI;
11 import jalview.datamodel.SequenceGroup;
12 import jalview.datamodel.SequenceI;
13 import jalview.renderer.ResidueShaderI;
14 import jalview.util.MessageManager;
15
16 import java.util.List;
17
18 /**
19  * This class calculates HMM Information Content annotations, based on any HMM
20  * consensus sequences and their HMM models. HMM consensus sequences may be
21  * present for the whole alignment, or subgroups of it.
22  *
23  */
24 public class InformationThread extends AlignCalcWorker
25 {
26   public static final String HMM_CALC_ID = "HMM";
27
28   private float max = 0f;
29
30   /**
31    * Constructor
32    * 
33    * @param alignViewport
34    * @param alignPanel
35    */
36   public InformationThread(AlignViewportI alignViewport,
37           AlignmentViewPanel alignPanel)
38   {
39     super(alignViewport, alignPanel);
40   }
41
42   /**
43    * Recomputes Information annotations for any HMM consensus sequences (for
44    * alignment and/or groups)
45    */
46   @Override
47   public void run()
48   {
49     if (calcMan.isPending(this))
50     {
51       return;
52     }
53     calcMan.notifyStart(this);
54     // long started = System.currentTimeMillis();
55
56     try
57     {
58       if (calcMan.isPending(this))
59       {
60         // another instance of this is waiting to run
61         calcMan.workerComplete(this);
62         return;
63       }
64       while (!calcMan.notifyWorking(this))
65       {
66         // another thread in progress, wait my turn
67         try
68         {
69           if (ap != null)
70           {
71             ap.paintAlignment(false, false);
72           }
73           Thread.sleep(200);
74         } catch (Exception ex)
75         {
76           ex.printStackTrace();
77         }
78       }
79       if (alignViewport.isClosed())
80       {
81         abortAndDestroy();
82         return;
83       }
84       AlignmentI alignment = alignViewport.getAlignment();
85
86       int aWidth = alignment == null ? -1 : alignment.getWidth();
87
88       if (aWidth < 0)
89       {
90         calcMan.workerComplete(this);
91         return;
92       }
93
94       eraseAnnotations(alignment);
95       computeProfiles(alignment);
96       updateResultAnnotation(true);
97
98       if (ap != null)
99       {
100         ap.adjustAnnotationHeight();
101         ap.paintAlignment(true, true);
102       }
103     } catch (OutOfMemoryError error)
104     {
105       calcMan.disableWorker(this);
106       ap.raiseOOMWarning("calculating information", error);
107     } finally
108     {
109       calcMan.workerComplete(this);
110     }
111   }
112
113   /**
114    * Deletes any existing information annotations. These are sequence-related
115    * annotations which relate to HMM consensus sequences for either the
116    * alignment or a subgroup.
117    * 
118    * @param alignment
119    */
120   protected void eraseAnnotations(AlignmentI alignment)
121   {
122     Iterable<AlignmentAnnotation> anns = alignment
123             .findAnnotation(HMM_CALC_ID);
124     for (AlignmentAnnotation ann : anns)
125     {
126       alignment.deleteAnnotation(ann);
127     }
128   }
129
130   /**
131    * Computes HMM profiles for any HMM consensus sequences (for alignment or
132    * subgroups)
133    * 
134    * @param alignment
135    */
136   protected void computeProfiles(AlignmentI alignment)
137   {
138     int width = alignment.getWidth();
139
140     /*
141      * alignment HMM profile
142      */
143     SequenceI seq = alignment.getHmmConsensus();
144     if (seq != null)
145     {
146       HiddenMarkovModel hmm = seq.getHMM();
147       ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
148               0, width, true, alignViewport.isIgnoreBelowBackground(),
149               alignViewport.isInfoLetterHeight());
150       alignViewport.setHmmProfiles(hmmProfiles);
151       // setColourSchemeInformation(hmmProfiles);
152     }
153
154     /*
155      * group HMM profiles
156      */
157     List<SequenceGroup> groups = alignment.getGroups();
158     for (SequenceGroup group : groups)
159     {
160       seq = group.getHmmConsensus();
161       if (seq != null)
162       {
163         HiddenMarkovModel hmm = seq.getHMM();
164         ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
165                 0, width, true, group.isIgnoreBelowBackground(),
166                 group.isUseInfoLetterHeight());
167         group.setHmmProfiles(hmmProfiles);
168         // setColourSchemeInformation(hmmProfiles);
169       }
170     }
171   }
172
173   /**
174    * gets the sequences on the alignment on the viewport.
175    * 
176    * @return
177    */
178   protected SequenceI[] getSequences()
179   {
180     return alignViewport.getAlignment().getSequencesArray();
181   }
182
183   protected void setColourSchemeInformation(ProfilesI information)
184   {
185     ResidueShaderI cs = alignViewport.getResidueShading();
186     if (cs != null)
187     {
188       cs.setInformation(information);
189     }
190   }
191
192   /**
193    * Get the Gap annotation for the alignment
194    * 
195    * @return
196    */
197   protected AlignmentAnnotation getGapAnnotation()
198   {
199     return alignViewport.getOccupancyAnnotation();
200   }
201
202   /**
203    * Updates the information annotation from the sequence profile data using
204    * current visualisation settings
205    */
206   @Override
207   public void updateAnnotation()
208   {
209     updateResultAnnotation(false);
210   }
211
212   /**
213    * Constructs Information Content annotation for any HMM consensus sequences
214    * (for alignment or groups), and adds the annotation to the sequence and the
215    * alignment
216    * 
217    * @param immediate
218    */
219   public void updateResultAnnotation(boolean immediate)
220   {
221     AlignmentI alignment = alignViewport.getAlignment();
222
223     /*
224      * annotation for alignment HMM consensus if present
225      */
226     SequenceI hmmSeq = alignment.getHmmConsensus();
227     ProfilesI profile = alignViewport.getHmmProfiles();
228     AlignmentAnnotation ann = makeInformationAnnotation(hmmSeq, profile);
229     if (ann != null)
230     {
231       alignment.addAnnotation(ann);
232     }
233
234     /*
235      * annotation for group HMM consensus if present
236      */
237     for (SequenceGroup group : alignment.getGroups())
238     {
239       hmmSeq = group.getHmmConsensus();
240       ProfilesI profiles = group.getHmmProfiles();
241       ann = makeInformationAnnotation(hmmSeq, profiles);
242       if (ann != null)
243       {
244         ann.groupRef = group;
245         alignment.addAnnotation(ann);
246       }
247     }
248   }
249
250   /**
251    * Constructs an HMM Profile information content annotation for a sequence
252    * 
253    * @param seq
254    * @param profile
255    * @return
256    */
257   protected AlignmentAnnotation makeInformationAnnotation(SequenceI seq,
258           ProfilesI profile)
259   {
260     if (seq == null || profile == null)
261     {
262       return null;
263     }
264
265     AlignmentI alignment = alignViewport.getAlignment();
266     int aWidth = alignment == null ? 0 : alignment.getWidth();
267     AlignmentAnnotation ann = new AlignmentAnnotation(seq.getName(),
268             MessageManager.getString("label.information_description"),
269             new Annotation[aWidth], 0f, 6.52f,
270             AlignmentAnnotation.BAR_GRAPH);
271     ann.hasText = true;
272     ann.autoCalculated = false;
273     ann.sequenceRef = seq;
274     ann.setCalcId(InformationThread.HMM_CALC_ID);
275     seq.addAlignmentAnnotation(ann);
276
277     long nseq = getSequences().length;
278     max = AAFrequency.completeInformation(ann, profile,
279             profile.getStartColumn(), profile.getEndColumn() + 1, nseq,
280             max);
281
282     return ann;
283   }
284
285   /**
286    * Convert the computed information data into the desired annotation for
287    * display.
288    * 
289    * @param informationAnnotation
290    *          the annotation to be populated
291    * @param hinformation
292    *          the computed information data
293    */
294   protected void deriveInformation(
295           AlignmentAnnotation informationAnnotation, ProfilesI hinformation)
296   {
297     long nseq = getSequences().length;
298     max = AAFrequency.completeInformation(informationAnnotation,
299             hinformation, hinformation.getStartColumn(),
300             hinformation.getEndColumn() + 1, nseq, max);
301   }
302 }