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