--- /dev/null
+package jalview.workers;
+
+import jalview.analysis.AAFrequency;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenMarkovModel;
+import jalview.datamodel.ProfilesI;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.util.MessageManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class calculates HMM Information Content annotations, based on any HMM
+ * consensus sequences and their HMM models. HMM consensus sequences may be
+ * present for the whole alignment, or subgroups of it.
+ *
+ */
+public class InformationThread extends AlignCalcWorker
+{
+ public static final String HMM_CALC_ID = "HMM";
+
+ /**
+ * Constructor
+ *
+ * @param alignViewport
+ * @param alignPanel
+ */
+ public InformationThread(AlignViewportI alignViewport,
+ AlignmentViewPanel alignPanel)
+ {
+ super(alignViewport, alignPanel);
+ }
+
+ /**
+ * Recomputes Information annotations for any HMM consensus sequences (for
+ * alignment and/or groups)
+ */
+ @Override
+ public void run()
+ {
+ if (calcMan.isPending(this))
+ {
+ return;
+ }
+ calcMan.notifyStart(this);
+ // long started = System.currentTimeMillis();
+
+ try
+ {
+ if (calcMan.isPending(this))
+ {
+ // another instance of this is waiting to run
+ calcMan.workerComplete(this);
+ return;
+ }
+ while (!calcMan.notifyWorking(this))
+ {
+ // another thread in progress, wait my turn
+ try
+ {
+ if (ap != null)
+ {
+ ap.paintAlignment(false, false);
+ }
+ Thread.sleep(200);
+ } catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ if (alignViewport.isClosed())
+ {
+ abortAndDestroy();
+ return;
+ }
+
+ AlignmentI alignment = alignViewport.getAlignment();
+ int aWidth = alignment == null ? -1 : alignment.getWidth();
+ if (aWidth < 0)
+ {
+ calcMan.workerComplete(this);
+ return;
+ }
+
+ /*
+ * compute information profiles for any HMM consensus sequences
+ * for the alignment or sub-groups
+ */
+ computeProfiles(alignment);
+
+ /*
+ * construct the corresponding annotations
+ */
+ updateAnnotation();
+
+ if (ap != null)
+ {
+ ap.adjustAnnotationHeight();
+ ap.paintAlignment(true, true);
+ }
+ } catch (OutOfMemoryError error)
+ {
+ calcMan.disableWorker(this);
+ ap.raiseOOMWarning("calculating information", error);
+ } finally
+ {
+ calcMan.workerComplete(this);
+ }
+ }
+
+ /**
+ * Computes HMM profiles for any HMM consensus sequences (for alignment or
+ * subgroups). Any alignment profile computed is stored on the viewport, any
+ * group profile computed is stored on the respective sequence group.
+ *
+ * @param alignment
+ * @see AlignViewportI#setHmmProfiles(ProfilesI)
+ */
+ protected void computeProfiles(AlignmentI alignment)
+ {
+ int width = alignment.getWidth();
+
+ /*
+ * alignment HMM profile
+ */
+ SequenceI seq = alignment.getHmmConsensus();
+ if (seq != null)
+ {
+ HiddenMarkovModel hmm = seq.getHMM();
+ ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
+ 0, width, alignViewport.isIgnoreBelowBackground(),
+ alignViewport.isInfoLetterHeight());
+ alignViewport.setHmmProfiles(hmmProfiles);
+ }
+
+ /*
+ * group HMM profiles
+ */
+ List<SequenceGroup> groups = alignment.getGroups();
+ for (SequenceGroup group : groups)
+ {
+ seq = group.getHmmConsensus();
+ if (seq != null)
+ {
+ HiddenMarkovModel hmm = seq.getHMM();
+ ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
+ 0, width, group.isIgnoreBelowBackground(),
+ group.isUseInfoLetterHeight());
+ group.setHmmProfiles(hmmProfiles);
+ }
+ }
+ }
+
+ /**
+ * gets the sequences on the alignment on the viewport.
+ *
+ * @return
+ */
+ protected SequenceI[] getSequences()
+ {
+ return alignViewport.getAlignment().getSequencesArray();
+ }
+
+ /**
+ * Get the Gap annotation for the alignment
+ *
+ * @return
+ */
+ protected AlignmentAnnotation getGapAnnotation()
+ {
+ return alignViewport.getOccupancyAnnotation();
+ }
+
+ /**
+ * Computes Information Content annotation for any HMM consensus sequences
+ * (for alignment or groups), and updates (or adds) the annotation to the
+ * sequence and the alignment
+ */
+ @Override
+ public void updateAnnotation()
+ {
+ AlignmentI alignment = alignViewport.getAlignment();
+
+ float maxInformation = 0f;
+ List<AlignmentAnnotation> infos = new ArrayList<>();
+
+ /*
+ * annotation for alignment HMM consensus if present
+ */
+ SequenceI hmmSeq = alignment.getHmmConsensus();
+ ProfilesI profile = alignViewport.getHmmProfiles();
+ float m = updateInformationAnnotation(hmmSeq, profile, null, infos);
+ maxInformation = Math.max(maxInformation, m);
+
+ /*
+ * annotation for group HMM consensus if present
+ */
+ for (SequenceGroup group : alignment.getGroups())
+ {
+ hmmSeq = group.getHmmConsensus();
+ ProfilesI profiles = group.getHmmProfiles();
+ m = updateInformationAnnotation(hmmSeq, profiles, group, infos);
+ maxInformation = Math.max(maxInformation, m);
+ }
+
+ /*
+ * maxInformation holds the maximum value of information score;
+ * set this as graphMax in all annotations to scale them all the same
+ */
+ for (AlignmentAnnotation ann : infos)
+ {
+ ann.graphMax = maxInformation;
+ }
+ }
+
+ /**
+ * Updates (and first constructs if necessary) an HMM Profile information
+ * content annotation for a sequence. The <code>group</code> argument is null
+ * for the whole alignment annotation, not null for a subgroup annotation. The
+ * updated annotation is added to the <code>infos</code> list. Answers the
+ * maximum information content value of any annotation (for use as a scaling
+ * factor for display).
+ *
+ * @param seq
+ * @param profile
+ * @param group
+ * @param infos
+ * @return
+ */
+ protected float updateInformationAnnotation(SequenceI seq,
+ ProfilesI profile, SequenceGroup group,
+ List<AlignmentAnnotation> infos)
+ {
+ if (seq == null || profile == null)
+ {
+ return 0f;
+ }
+
+ AlignmentAnnotation ann = findOrCreateAnnotation(seq, group);
+
+ seq.addAlignmentAnnotation(ann);
+ infos.add(ann);
+
+ float max = AAFrequency.completeInformation(ann, profile,
+ profile.getStartColumn(), profile.getEndColumn() + 1);
+
+ return max;
+ }
+
+ /**
+ * A helper method that first searches for the HMM annotation that matches the
+ * group reference (null for the whole alignment annotation). If found, its
+ * sequence reference is updated to the given sequence (the recomputed HMM
+ * consensus sequence). If not found, it is created. This supports both
+ * creating the annotation the first time hmmbuild is run, and updating it if
+ * hmmbuild is re-run.
+ *
+ * @param seq
+ * @param group
+ * @return
+ */
+ AlignmentAnnotation findOrCreateAnnotation(SequenceI seq,
+ SequenceGroup group)
+ {
+ /*
+ * can't use Alignment.findOrCreateAnnotation here because we
+ * want to update, rather than match on, the sequence ref
+ */
+ AlignmentAnnotation info = null;
+
+ AlignmentI alignment = alignViewport.getAlignment();
+ AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
+ if (anns != null)
+ {
+ for (AlignmentAnnotation ann : anns)
+ {
+ if (HMM_CALC_ID.equals(ann.getCalcId()) && group == ann.groupRef)
+ {
+ info = ann;
+ info.setSequenceRef(seq);
+ info.label = seq.getName(); // in case group name changed!
+ break;
+ }
+ }
+ }
+
+ if (info == null)
+ {
+ int aWidth = alignment.getWidth();
+ String desc = MessageManager
+ .getString("label.information_description");
+ float graphMax = 6.52f; // todo where does this value derive from?
+ info = new AlignmentAnnotation(seq.getName(), desc,
+ new Annotation[aWidth], 0f, graphMax,
+ AlignmentAnnotation.BAR_GRAPH);
+ info.setCalcId(HMM_CALC_ID);
+ info.setSequenceRef(seq);
+ info.groupRef = group;
+ info.hasText = true;
+ alignment.addAnnotation(info);
+ }
+
+ return info;
+ }
+}