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