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