JAL-2629 tidy tests, refactor hmmer node mapping slightly
[jalview.git] / src / jalview / hmmer / HMMBuildThread.java
index e48dce3..c52fb86 100644 (file)
 package jalview.hmmer;
 
-import jalview.bin.Cache;
+import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenMarkovModel;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
-import jalview.gui.Preferences;
+import jalview.gui.AlignViewport;
+import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
-import jalview.io.FileFormat;
+import jalview.io.FileParse;
+import jalview.io.HMMFile;
 import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
 
-import java.io.FileNotFoundException;
+import java.io.File;
 import java.io.IOException;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
-public class HMMBuildThread implements Runnable
+import javax.swing.JOptionPane;
+
+public class HMMBuildThread extends HmmerCommand implements Runnable
 {
+  static final String ARG_AMINO = "--amino";
 
-  AlignFrame af;
-  AlignmentI alignment;
+  static final String ARG_DNA = "--dna";
 
-  long barID;
+  static final String ARG_RNA = "--rna";
+
+  AlignmentI alignment;
 
-  Map<Integer, SequenceI> hmmSeqs;
-  
-  public HMMBuildThread(AlignFrame af)
+  /**
+   * Constructor
+   * 
+   * @param alignFrame
+   * @param args
+   */
+  public HMMBuildThread(AlignFrame alignFrame, List<ArgumentI> args)
   {
-    this.af = af;
-    alignment = af.getViewport().getAlignment();
+    super(alignFrame, args);
   }
 
+  /**
+   * Builds a HMM from an alignment (and/or groups), then imports and adds it to
+   * the alignment (and/or groups)
+   */
   @Override
   public void run()
   {
-    barID = System.currentTimeMillis();
-    af.setProgressBar(MessageManager.getString("status.running_hmmbuild"),
-            barID);
-    HMMERCommands.HMMERFOLDER = Cache.getProperty(Preferences.HMMER_PATH);
-
-    try
+    long msgID = System.currentTimeMillis();
+    if (af != null)
     {
+      af.setProgressBar(MessageManager.getString("status.running_hmmbuild"),
+              msgID);
+    }
+
+    AlignViewportI viewport = af.getViewport();
     try
     {
-        hmmSeqs = alignment.getHMMConsensusSequences(true);
-        HMMERCommands.exportData(alignment, true, false,
-                new HiddenMarkovModel());
-    } catch (FileNotFoundException e)
+      if (viewport != null)
+      {
+        alignment = viewport.getAlignment();
+      }
+      List<SequenceGroup> groups = new ArrayList<>();
+      if (params != null)
+      {
+        for (ArgumentI arg : params)
+        {
+          String name = arg.getName();
+          if (MessageManager.getString("label.hmmbuild_for").equals(name))
+          {
+            String value = arg.getValue();
+            if (MessageManager.getString("label.alignment").equals(value))
+            {
+              alignment = viewport.getAlignment();
+            }
+            else if (MessageManager.getString("label.groups_and_alignment")
+                    .equals(value))
+            {
+              alignment = viewport.getAlignment();
+              groups.addAll(viewport.getAlignment().getGroups());
+            }
+            else if (MessageManager.getString("label.groups").equals(value))
+            {
+              alignment = null;
+              groups = viewport.getAlignment().getGroups();
+            }
+            else if ("label.selected_group".equals(value))
+            {
+              alignment = null;
+              groups.add(viewport.getSelectionGroup());
+            }
+          }
+          else if (MessageManager.getString("label.use_reference")
+                  .equals(name))
+          {
+            // todo disable this option if no RF annotation on alignment
+            if (!af.getViewport().hasReferenceAnnotation())
+            {
+              JvOptionPane.showInternalMessageDialog(af, MessageManager
+                      .getString("warn.no_reference_annotation"));
+              // return;
+            }
+          }
+        }
+      }
+
+      if (alignment != null)
+      {
+        runHMMBuild(null);
+      }
+
+      if (alignment == null)
+      {
+        alignment = viewport.getAlignment();
+      }
+
+      for (SequenceGroup grp : groups)
+      {
+        runHMMBuild(grp);
+      }
+    } finally
     {
-      // TODO Auto-generated catch block
-      e.printStackTrace();
+      if (af != null)
+      {
+        af.setProgressBar("", msgID);
+      }
+    }
+  }
 
+  /**
+   * Runs hmmbuild on the alignment, or on the group if one is specified
+   * 
+   * @param grp
+   */
+  private void runHMMBuild(SequenceGroup group)
+  {
+    if (alignment == null && group == null)
+    {
+      JOptionPane.showMessageDialog(af,
+              MessageManager.getString("warn.no_sequence_data"));
     }
+    File hmmFile = null;
+    File alignmentFile = null;
     try
     {
-      runCommand();
-    } catch (IOException | InterruptedException e)
+      hmmFile = createTempFile("hmm", ".hmm");
+      alignmentFile = createTempFile("output", ".sto");
+      SequenceI[] array;
+      List<SequenceI> hmmSeqs = null;
+      if (group != null)
+      {
+        hmmSeqs = group.getHMMConsensusSequences();
+        array = group.getSelectionAsNewSequences(alignment);
+      }
+      else
+      {
+        hmmSeqs = alignment.getHMMConsensusSequences();
+        // todo pad gaps in an unaligned SequenceGroup as well?
+        if (!alignment.isAligned())
+        {
+          alignment.padGaps();
+        }
+        array = alignment.getSequencesArray();
+      }
+
+      if (array.length < 1)
+      {
+        if (af != null)
+        {
+          JOptionPane.showMessageDialog(af,
+                  MessageManager.getString("warn.no_sequence_data"));
+        }
+        return;
+      }
+
+      /*
+       * copy over sequences excluding hmm consensus sequences
+       */
+      SequenceI[] newArr = new SequenceI[array.length - hmmSeqs.size()];
+      int index = 0;
+      for (SequenceI seq : array)
+      {
+        if (seq.isHMMConsensusSequence())
+        {
+          alignment.deleteSequence(seq);
+        }
+        else
+        {
+          newArr[index] = new Sequence(seq);
+          index++;
+        }
+      }
+
+      stashSequences(newArr);
+
+      exportStockholm(newArr, alignmentFile,
+              group != null ? group : alignment);
+
+      recoverSequences(array);
+
+      boolean ran = runCommand(alignmentFile, hmmFile, group);
+      if (!ran)
+      {
+        return;
+      }
+      importData(hmmFile, group);
+    } catch (Exception e)
     {
-      // TODO Auto-generated catch block
       e.printStackTrace();
+    } finally
+    {
+      if (hmmFile != null)
+      {
+        hmmFile.delete();
+      }
+      if (alignmentFile != null)
+      {
+        alignmentFile.delete();
+      }
     }
-    try
+  }
+
+  /**
+   * Constructs and executes the hmmbuild command as a separate process
+   * 
+   * @param sequences
+   *          the alignment from which the HMM is built
+   * @param hmm
+   *          the output file to which the HMM is written
+   * @param group
+   *          (optional) group for which the hmm is generated
+   * 
+   * @return
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  private boolean runCommand(File sequences, File hmm, SequenceGroup group)
+          throws IOException, InterruptedException
+  {
+
+    String cmd = getCommandPath(HMMBUILD);
+    if (cmd == null)
     {
-      importData();
-    } catch (IOException | InterruptedException e)
+      return false;
+    }
+    List<String> args = new ArrayList<>();
+    args.add(cmd);
+    String name = null;
+
+    if (params != null)
     {
-      // TODO Auto-generated catch block
-      e.printStackTrace();
+      for (ArgumentI arg : params)
+      {
+        String argName = arg.getName();
+        switch (argName)
+        {
+        case "HMM Name":
+          name = arg.getValue();
+          name = name.trim();
+          break;
+        case "Use Reference Annotation":
+          args.add("--hand");
+          break;
+        }
+      }
     }
-    } catch (Exception e)
+
+    if (group != null)
     {
+      name = group.getName() + "_HMM";
+    }
 
-    } finally
+    if (name == null || "".equals(name))
     {
-      af.setProgressBar(MessageManager.getString("status.running_hmmbuild"),
-              barID);
+      if (af != null)
+      {
+        if (af.getTitle().length() < 15)
+        {
+          name = af.getTitle();
+        }
+      }
+      if (name == null || "".equals(name))
+      {
+        name = "Alignment";
+      }
+
     }
-  }
 
-  
+    args.add("-n");
+    args.add(name.replace(' ', '_'));
+    if (!alignment.isNucleotide())
+    {
+      args.add(ARG_AMINO); // TODO check for rna
+    }
+    else
+    {
+      args.add(ARG_DNA);
+    }
+
+    args.add(hmm.getAbsolutePath());
+    args.add(sequences.getAbsolutePath());
+
+    return runCommand(args);
+  }
 
-  
-  private void runCommand() throws IOException, InterruptedException
+  /**
+   * Imports the .hmm file produced by hmmbuild, and inserts the HMM consensus
+   * sequence (with attached HMM profile) as the first sequence in the alignment
+   * or group for which it was generated
+   * 
+   * @param hmmFile
+   * @oparam group (optional) the group for which the hmm was generated
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  private void importData(File hmmFile, SequenceGroup group)
+          throws IOException, InterruptedException
   {
-    final String command = HMMERCommands.HMMERFOLDER
-            + HMMERCommands.HMMBUILD + HMMERCommands.NAME
-            + af.getName() + HMMERCommands.SPACE
-            + HMMERCommands.JALVIEWDIRECTORY + HMMERCommands.HMMBUFFER
-            + HMMERCommands.JALVIEWDIRECTORY + HMMERCommands.ALIGNMENTBUFFER;
-    HMMERCommands.runCommand(command);
+    HMMFile file = new HMMFile(
+            new FileParse(hmmFile.getAbsolutePath(), DataSourceType.FILE));
+    SequenceI[] seqs = file.getSeqsAsArray();
+    SequenceI seq = seqs[0];
+    seq.createDatasetSequence();
+    if (group != null)
+    {
+      seq.insertCharAt(0, group.getStartRes(), '-');
+      seq.insertCharAt(group.getEndRes() + 1,
+              alignment.getWidth() - group.getEndRes() - 1, '-');
+      seq.updateHMMMapping();
+      SequenceI topSeq = group.getSequencesInOrder(alignment)[0];
+      int topIndex = alignment.findIndex(topSeq);
+      alignment.insertSequenceAt(topIndex, seq);
+      group.setSeqrep(seq);
+      group.addSequence(seq, false);
+    }
+    else
+    {
+      alignment.insertSequenceAt(0, seq);
+    }
+
+    AlignViewport viewport = af.getViewport();
+    if (viewport != null)
+    {
+      viewport.alignmentChanged(viewport.getAlignPanel());
+      viewport.getAlignPanel().adjustAnnotationHeight();
+      viewport.updateSequenceIdColours();
+
+      if (viewport.getAlignPanel().alignFrame.getSelectedHMM() == null)
+      {
+        viewport.getAlignPanel().alignFrame.setSelectedHMMSequence(seq);
+      }
+    }
   }
-  
-  private void importData() throws IOException, InterruptedException
+
+  /**
+   * Runs hmmbuild, and waits for the results to be imported before continuing
+   */
+  public void hmmbuildWaitTillComplete()
   {
-    af.loadJalviewDataFile(HMMERCommands.HMMBUFFER, DataSourceType.FILE,
-            FileFormat.HMMER3, null);
-    for (Map.Entry<Integer, SequenceI> entry : hmmSeqs.entrySet())
+    Thread loader = new Thread(this);
+    loader.start();
+
+    while (loader.isAlive())
     {
-      SequenceI seq = entry.getValue();
-      Integer pos = entry.getKey();
-      HMMERCommands.addHMMConsensusSequence(af, seq, pos);
+      try
+      {
+        Thread.sleep(500);
+      } catch (Exception ex)
+      {
+      }
     }
-    af.alignPanel.alignmentChanged();
   }
-  
-  
-  
 }