Updated with latest from mchmmer branch
[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     SequenceI seq = alignment.getHmmConsensus();
133     if (seq != null)
134     {
135       HiddenMarkovModel hmm = seq.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       seq = group.getHmmConsensus();
149       if (seq != null)
150       {
151         HiddenMarkovModel hmm = seq.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     SequenceI hmmSeq = alignment.getHmmConsensus();
197     ProfilesI profile = alignViewport.getHmmProfiles();
198     float m = updateInformationAnnotation(hmmSeq, profile, null, infos);
199     maxInformation = Math.max(maxInformation, m);
200
201     /*
202      * annotation for group HMM consensus if present
203      */
204     for (SequenceGroup group : alignment.getGroups())
205     {
206       hmmSeq = group.getHmmConsensus();
207       ProfilesI profiles = group.getHmmProfiles();
208       m = updateInformationAnnotation(hmmSeq, profiles, group, infos);
209       maxInformation = Math.max(maxInformation, m);
210     }
211
212     /*
213      * maxInformation holds the maximum value of information score;
214      * set this as graphMax in all annotations to scale them all the same
215      */
216     for (AlignmentAnnotation ann : infos)
217     {
218       ann.graphMax = maxInformation;
219     }
220   }
221
222   /**
223    * Updates (and first constructs if necessary) an HMM Profile information
224    * content annotation for a sequence. The <code>group</code> argument is null
225    * for the whole alignment annotation, not null for a subgroup annotation. The
226    * updated annotation is added to the <code>infos</code> list. Answers the
227    * maximum information content value of any annotation (for use as a scaling
228    * factor for display).
229    * 
230    * @param seq
231    * @param profile
232    * @param group
233    * @param infos
234    * @return
235    */
236   protected float updateInformationAnnotation(SequenceI seq,
237           ProfilesI profile, SequenceGroup group,
238           List<AlignmentAnnotation> infos)
239   {
240     if (seq == null || profile == null)
241     {
242       return 0f;
243     }
244
245     AlignmentAnnotation ann = findOrCreateAnnotation(seq, group);
246
247     seq.addAlignmentAnnotation(ann);
248     infos.add(ann);
249
250     float max = AAFrequency.completeInformation(ann, profile,
251             profile.getStartColumn(), profile.getEndColumn() + 1);
252
253     return max;
254   }
255
256   /**
257    * A helper method that first searches for the HMM annotation that matches the
258    * group reference (null for the whole alignment annotation). If found, its
259    * sequence reference is updated to the given sequence (the recomputed HMM
260    * consensus sequence). If not found, it is created. This supports both
261    * creating the annotation the first time hmmbuild is run, and updating it if
262    * hmmbuild is re-run.
263    * 
264    * @param seq
265    * @param group
266    * @return
267    */
268   AlignmentAnnotation findOrCreateAnnotation(SequenceI seq,
269           SequenceGroup group)
270   {
271     /*
272      * can't use Alignment.findOrCreateAnnotation here because we 
273      * want to update, rather than match on, the sequence ref
274      */
275     AlignmentAnnotation info = null;
276
277     AlignmentI alignment = alignViewport.getAlignment();
278     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
279     if (anns != null)
280     {
281       for (AlignmentAnnotation ann : anns)
282       {
283         if (HMM_CALC_ID.equals(ann.getCalcId()) && group == ann.groupRef)
284         {
285           info = ann;
286           info.setSequenceRef(seq);
287           info.label = seq.getName(); // in case group name changed!
288           break;
289         }
290       }
291     }
292
293     if (info == null)
294     {
295       int aWidth = alignment.getWidth();
296       String desc = MessageManager
297               .getString("label.information_description");
298       float graphMax = 6.52f; // todo where does this value derive from?
299       info = new AlignmentAnnotation(seq.getName(), desc,
300               new Annotation[aWidth], 0f, graphMax,
301               AlignmentAnnotation.BAR_GRAPH);
302       info.setCalcId(HMM_CALC_ID);
303       info.setSequenceRef(seq);
304       info.groupRef = group;
305       info.hasText = true;
306       alignment.addAnnotation(info);
307     }
308
309     return info;
310   }
311 }