JAL - 3690 AlignCalc rebuilt - FutureTask-based manager
[jalview.git] / src / jalview / workers / InformationThread.java
index 54e5198..c8084b9 100644 (file)
@@ -8,230 +8,277 @@ 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.renderer.ResidueShaderI;
+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);
-    }
-
-    @Override
-    public void run()
-    {
-      if (calcMan.isPending(this))
-      {
-        return;
-      }
-      calcMan.notifyStart(this);
-      long started = System.currentTimeMillis();
-
-    List<AlignmentAnnotation> information = getInformationAnnotations();
-      try
-      {
-      if ((information == null) || calcMan.isPending(this))
-        {
-          calcMan.workerComplete(this);
-          return;
-        }
-        while (!calcMan.notifyWorking(this))
-        {
-          // System.err.println("Thread
-        // (Information"+Thread.currentThread().getName()+") Waiting around.");
-          try
-          {
-            if (ap != null)
-            {
-              ap.paintAlignment(false);
-            }
-            Thread.sleep(200);
-          } catch (Exception ex)
-          {
-            ex.printStackTrace();
-          }
-        }
-        if (alignViewport.isClosed())
-        {
-          abortAndDestroy();
-          return;
-        }
-        AlignmentI alignment = alignViewport.getAlignment();
-
-        int aWidth = -1;
-
-        if (alignment == null || (aWidth = alignment.getWidth()) < 0)
-        {
-          calcMan.workerComplete(this);
-          return;
-        }
-
-      eraseInformation(aWidth);
-      computeInformation(alignment);
-        updateResultAnnotation(true);
+          AlignmentViewPanel alignPanel)
+  {
+    super(alignViewport, alignPanel);
+  }
 
-        if (ap != null)
-        {
-          ap.paintAlignment(true);
-        }
-      } catch (OutOfMemoryError error)
-      {
-        calcMan.disableWorker(this);
-      ap.raiseOOMWarning("calculating information", error);
-    } finally
+  /**
+   * Recomputes Information annotations for any HMM consensus sequences (for
+   * alignment and/or groups)
+   */
+  @Override
+  public void run()
+  {
+    if (alignViewport.getAlignment().getHmmSequences().isEmpty())
     {
-      calcMan.workerComplete(this);
-      }
-
-
-
+      return;
     }
-
-    /**
-   * Clear out any existing information annotations
-   * 
-   * @param aWidth
-   *          the width (number of columns) of the annotated alignment
-   */
-  protected void eraseInformation(int aWidth)
+    if (alignViewport.isClosed())
     {
+      abortAndDestroy();
+      return;
+    }
 
-    List<AlignmentAnnotation> information = getInformationAnnotations();
-    for (AlignmentAnnotation info : information)
+    AlignmentI alignment = alignViewport.getAlignment();
+    int aWidth = alignment == null ? -1 : alignment.getWidth();
+    if (aWidth < 0)
     {
-      info.annotations = new Annotation[aWidth];
-    }
+      return;
     }
 
+    /*
+     * compute information profiles for any HMM consensus sequences
+     * for the alignment or sub-groups
+     */
+    computeProfiles(alignment);
 
-    /**
-     * @param alignment
+    /*
+     * construct the corresponding annotations
      */
-  protected void computeInformation(AlignmentI alignment)
-    {
+    updateAnnotation();
 
-      int width = alignment.getWidth();
-    List<SequenceI> hmmSeqs = alignment.getHMMConsensusSequences();
-    int index = 0;
-    for (SequenceI seq : hmmSeqs)
+    if (ap != null)
     {
-      HiddenMarkovModel hmm = seq.getHMM();
-      ProfilesI hinformation = AAFrequency.calculateInformation(hmm, width,
-              0, width, true, alignViewport.isIgnoreBelowBackground());
-      alignViewport.setSequenceInformationHash(hinformation, index);
-      // setColourSchemeInformation(hinformation);
-      index++;
-    }
+      ap.adjustAnnotationHeight();
+      ap.paintAlignment(true, true);
     }
+  }
 
-    /**
-     * @return
+  /**
+   * 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
      */
-    protected SequenceI[] getSequences()
+    List<SequenceI> seqs = alignment.getHmmSequences();
+    if (!seqs.isEmpty())
     {
-      return alignViewport.getAlignment().getSequencesArray();
+      HiddenMarkovModel hmm = seqs.get(0).getHMM();
+      ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
+              0, width, alignViewport.isIgnoreBelowBackground(),
+              alignViewport.isInfoLetterHeight());
+      alignViewport.setHmmProfiles(hmmProfiles);
     }
 
-    /**
-   * @param hinformation
-   */
-  protected void setColourSchemeInformation(ProfilesI information)
+    /*
+     * group HMM profiles
+     */
+    List<SequenceGroup> groups = alignment.getGroups();
+    for (SequenceGroup group : groups)
     {
-      ResidueShaderI cs = alignViewport.getResidueShading();
-      if (cs != null)
+      seqs = group.getHmmSequences();
+      if (!seqs.isEmpty())
       {
-      cs.setInformation(information);
+        HiddenMarkovModel hmm = seqs.get(0).getHMM();
+        ProfilesI hmmProfiles = AAFrequency.calculateHMMProfiles(hmm, width,
+                0, width, group.isIgnoreBelowBackground(),
+                group.isUseInfoLetterHeight());
+        group.setHmmProfiles(hmmProfiles);
       }
     }
+  }
 
-    /**
-   * Get the Information annotation for the alignment
+  /**
+   * gets the sequences on the alignment on the viewport.
    * 
    * @return
    */
-  protected List<AlignmentAnnotation> getInformationAnnotations()
-    {
-    return alignViewport.getInformationAnnotations();
-    }
-
-    /**
-     * Get the Gap annotation for the alignment
-     * 
-     * @return
-     */
-    protected AlignmentAnnotation getGapAnnotation()
-    {
-      return alignViewport.getAlignmentGapAnnotation();
-    }
+  protected SequenceI[] getSequences()
+  {
+    return alignViewport.getAlignment().getSequencesArray();
+  }
 
-    /**
-   * update the information annotation from the sequence profile data using
-   * current visualization settings.
+  /**
+   * Get the Gap annotation for the alignment
+   * 
+   * @return
    */
-    @Override
-    public void updateAnnotation()
-    {
+  protected AlignmentAnnotation getGapAnnotation()
+  {
+               return alignViewport.getAlignmentGapAnnotation();
+  }
 
-      updateResultAnnotation(false);
-    }
+  /**
+   * 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<>();
 
-    public void updateResultAnnotation(boolean immediate)
+    /*
+     * annotation for alignment HMM consensus if present
+     */
+    List<SequenceI> hmmSeqs = alignment.getHmmSequences();
+    if (!hmmSeqs.isEmpty())
     {
-    List<AlignmentAnnotation> annots = getInformationAnnotations();
-    int index = 0;
-    for (AlignmentAnnotation information : annots)
+      ProfilesI profile = alignViewport.getHmmProfiles();
+      float m = updateInformationAnnotation(hmmSeqs.get(0), profile, null,
+              infos);
+      maxInformation = Math.max(maxInformation, m);
+    }
+
+    /*
+     * annotation for group HMM consensus if present
+     */
+    for (SequenceGroup group : alignment.getGroups())
     {
-      ProfilesI hinformation = (ProfilesI) getSequenceInformation(index);
-      if (immediate || !calcMan.isWorking(this) && information != null
-            && hinformation != null)
+      hmmSeqs = group.getHmmSequences();
+      if (!hmmSeqs.isEmpty())
       {
-        deriveInformation(information, hinformation);
-      }
-      index++;
+        ProfilesI profiles = group.getHmmProfiles();
+        float m = updateInformationAnnotation(hmmSeqs.get(0), profiles,
+                group, infos);
+        maxInformation = Math.max(maxInformation, m);
       }
     }
 
-    /**
-   * Convert the computed information data into the desired annotation for
-   * display.
+    /*
+     * 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 informationAnnotation
-   *          the annotation to be populated
-   * @param hinformation
-   *          the computed information data
+   * @param seq
+   * @param profile
+   * @param group
+   * @param infos
+   * @return
    */
-  protected void deriveInformation(
-          AlignmentAnnotation informationAnnotation, ProfilesI hinformation)
+  protected float updateInformationAnnotation(SequenceI seq,
+          ProfilesI profile, SequenceGroup group,
+          List<AlignmentAnnotation> infos)
+  {
+    if (seq == null || profile == null)
     {
-
-      long nseq = getSequences().length;
-    AAFrequency.completeInformation(informationAnnotation, hinformation,
-            hinformation.getStartColumn(), hinformation.getEndColumn() + 1,
-            alignViewport.isIgnoreBelowBackground(),
-              alignViewport.isShowHMMSequenceLogo(), nseq);
+      return 0f;
     }
 
+    AlignmentAnnotation ann = findOrCreateAnnotation(seq, group);
+
+    seq.addAlignmentAnnotation(ann);
+    infos.add(ann);
 
+    float max = AAFrequency.completeInformation(ann, profile,
+            profile.getStartColumn(), profile.getEndColumn() + 1);
 
-    /**
-   * Get the information data stored on the viewport.
+    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
    */
-  protected Object getSequenceInformation(int index)
+  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)
     {
-    // TODO convert ComplementInformationThread to use Profile
-    return alignViewport.getSequenceInformationHash(index);
+      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;
+  }
+}