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