85cd92faa4e40f3b221881720ef60dac26da7281
[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.util.MessageManager;
14
15 import java.util.ArrayList;
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   /**
29    * Constructor
30    * 
31    * @param alignViewport
32    * @param alignPanel
33    */
34   public InformationThread(AlignViewportI alignViewport,
35           AlignmentViewPanel alignPanel)
36   {
37     super(alignViewport, alignPanel);
38   }
39
40   /**
41    * Recomputes Information annotations for any HMM consensus sequences (for
42    * alignment and/or groups)
43    */
44   @Override
45   public void run()
46   {
47     if (alignViewport.getAlignment().getHmmSequences().isEmpty())
48     {
49       return;
50     }
51     if (calcMan.isPending(this))
52     {
53       return;
54     }
55     calcMan.notifyStart(this);
56     // long started = System.currentTimeMillis();
57
58     try
59     {
60       if (calcMan.isPending(this))
61       {
62         // another instance of this is waiting to run
63         calcMan.workerComplete(this);
64         return;
65       }
66       while (!calcMan.notifyWorking(this))
67       {
68         // another thread in progress, wait my turn
69         try
70         {
71           if (ap != null)
72           {
73             ap.paintAlignment(false, false);
74           }
75           Thread.sleep(200);
76         } catch (Exception ex)
77         {
78           ex.printStackTrace();
79         }
80       }
81       if (alignViewport.isClosed())
82       {
83         abortAndDestroy();
84         return;
85       }
86
87       AlignmentI alignment = alignViewport.getAlignment();
88       int aWidth = alignment == null ? -1 : alignment.getWidth();
89       if (aWidth < 0)
90       {
91         calcMan.workerComplete(this);
92         return;
93       }
94
95       /*
96        * compute information profiles for any HMM consensus sequences
97        * for the alignment or sub-groups
98        */
99       computeProfiles(alignment);
100
101       /*
102        * construct the corresponding annotations
103        */
104       updateAnnotation();
105
106       if (ap != null)
107       {
108         ap.adjustAnnotationHeight();
109         ap.paintAlignment(true, true);
110       }
111     } catch (OutOfMemoryError error)
112     {
113       calcMan.disableWorker(this);
114       ap.raiseOOMWarning("calculating information", error);
115     } finally
116     {
117       calcMan.workerComplete(this);
118     }
119   }
120
121   /**
122    * Computes HMM profiles for any HMM consensus sequences (for alignment or
123    * subgroups). Any alignment profile computed is stored on the viewport, any
124    * group profile computed is stored on the respective sequence group.
125    * 
126    * @param alignment
127    * @see AlignViewportI#setHmmProfiles(ProfilesI)
128    */
129   protected void computeProfiles(AlignmentI alignment)
130   {
131     int width = alignment.getWidth();
132
133     /*
134      * alignment HMM profile
135      */
136     List<SequenceI> seqs = alignment.getHmmSequences();
137     if (!seqs.isEmpty())
138     {
139       HiddenMarkovModel hmm = seqs.get(0).getHMM();
140       ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
141               0, width, alignViewport.isIgnoreBelowBackground(),
142               alignViewport.isInfoLetterHeight());
143       alignViewport.setHmmProfiles(hmmProfiles);
144     }
145
146     /*
147      * group HMM profiles
148      */
149     List<SequenceGroup> groups = alignment.getGroups();
150     for (SequenceGroup group : groups)
151     {
152       seqs = group.getHmmSequences();
153       if (!seqs.isEmpty())
154       {
155         HiddenMarkovModel hmm = seqs.get(0).getHMM();
156         ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
157                 0, width, group.isIgnoreBelowBackground(),
158                 group.isUseInfoLetterHeight());
159         group.setHmmProfiles(hmmProfiles);
160       }
161     }
162   }
163
164   /**
165    * gets the sequences on the alignment on the viewport.
166    * 
167    * @return
168    */
169   protected SequenceI[] getSequences()
170   {
171     return alignViewport.getAlignment().getSequencesArray();
172   }
173
174   /**
175    * Get the Gap annotation for the alignment
176    * 
177    * @return
178    */
179   protected AlignmentAnnotation getGapAnnotation()
180   {
181                 return alignViewport.getAlignmentGapAnnotation();
182   }
183
184   /**
185    * Computes Information Content annotation for any HMM consensus sequences
186    * (for alignment or groups), and updates (or adds) the annotation to the
187    * sequence and the alignment
188    */
189   @Override
190   public void updateAnnotation()
191   {
192     AlignmentI alignment = alignViewport.getAlignment();
193
194     float maxInformation = 0f;
195     List<AlignmentAnnotation> infos = new ArrayList<>();
196
197     /*
198      * annotation for alignment HMM consensus if present
199      */
200     List<SequenceI> hmmSeqs = alignment.getHmmSequences();
201     if (!hmmSeqs.isEmpty())
202     {
203       ProfilesI profile = alignViewport.getHmmProfiles();
204       float m = updateInformationAnnotation(hmmSeqs.get(0), profile, null,
205               infos);
206       maxInformation = Math.max(maxInformation, m);
207     }
208
209     /*
210      * annotation for group HMM consensus if present
211      */
212     for (SequenceGroup group : alignment.getGroups())
213     {
214       hmmSeqs = group.getHmmSequences();
215       if (!hmmSeqs.isEmpty())
216       {
217         ProfilesI profiles = group.getHmmProfiles();
218         float m = updateInformationAnnotation(hmmSeqs.get(0), profiles,
219                 group, infos);
220         maxInformation = Math.max(maxInformation, m);
221       }
222     }
223
224     /*
225      * maxInformation holds the maximum value of information score;
226      * set this as graphMax in all annotations to scale them all the same
227      */
228     for (AlignmentAnnotation ann : infos)
229     {
230       ann.graphMax = maxInformation;
231     }
232   }
233
234   /**
235    * Updates (and first constructs if necessary) an HMM Profile information
236    * content annotation for a sequence. The <code>group</code> argument is null
237    * for the whole alignment annotation, not null for a subgroup annotation. The
238    * updated annotation is added to the <code>infos</code> list. Answers the
239    * maximum information content value of any annotation (for use as a scaling
240    * factor for display).
241    * 
242    * @param seq
243    * @param profile
244    * @param group
245    * @param infos
246    * @return
247    */
248   protected float updateInformationAnnotation(SequenceI seq,
249           ProfilesI profile, SequenceGroup group,
250           List<AlignmentAnnotation> infos)
251   {
252     if (seq == null || profile == null)
253     {
254       return 0f;
255     }
256
257     AlignmentAnnotation ann = findOrCreateAnnotation(seq, group);
258
259     seq.addAlignmentAnnotation(ann);
260     infos.add(ann);
261
262     float max = AAFrequency.completeInformation(ann, profile,
263             profile.getStartColumn(), profile.getEndColumn() + 1);
264
265     return max;
266   }
267
268   /**
269    * A helper method that first searches for the HMM annotation that matches the
270    * group reference (null for the whole alignment annotation). If found, its
271    * sequence reference is updated to the given sequence (the recomputed HMM
272    * consensus sequence). If not found, it is created. This supports both
273    * creating the annotation the first time hmmbuild is run, and updating it if
274    * hmmbuild is re-run.
275    * 
276    * @param seq
277    * @param group
278    * @return
279    */
280   AlignmentAnnotation findOrCreateAnnotation(SequenceI seq,
281           SequenceGroup group)
282   {
283     /*
284      * can't use Alignment.findOrCreateAnnotation here because we 
285      * want to update, rather than match on, the sequence ref
286      */
287     AlignmentAnnotation info = null;
288
289     AlignmentI alignment = alignViewport.getAlignment();
290     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
291     if (anns != null)
292     {
293       for (AlignmentAnnotation ann : anns)
294       {
295         if (HMM_CALC_ID.equals(ann.getCalcId()) && group == ann.groupRef)
296         {
297           info = ann;
298           info.setSequenceRef(seq);
299           info.label = seq.getName(); // in case group name changed!
300           break;
301         }
302       }
303     }
304
305     if (info == null)
306     {
307       int aWidth = alignment.getWidth();
308       String desc = MessageManager
309               .getString("label.information_description");
310       float graphMax = 6.52f; // todo where does this value derive from?
311       info = new AlignmentAnnotation(seq.getName(), desc,
312               new Annotation[aWidth], 0f, graphMax,
313               AlignmentAnnotation.BAR_GRAPH);
314       info.setCalcId(HMM_CALC_ID);
315       info.setSequenceRef(seq);
316       info.groupRef = group;
317       info.hasText = true;
318       alignment.addAnnotation(info);
319     }
320
321     return info;
322   }
323 }