JAL-2629 add basic parameter adjustment to hmmsearch/align
[jalview.git] / src / jalview / hmmer / HMMAlignThread.java
index 8cd4270..532271b 100644 (file)
 package jalview.hmmer;
 
+import jalview.bin.Cache;
+import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenMarkovModel;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.Preferences;
+import jalview.gui.SplitFrame;
 import jalview.io.DataSourceType;
-import jalview.io.FileFormat;
-import jalview.io.FileLoader;
+import jalview.io.StockholmFile;
 import jalview.util.MessageManager;
+import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
+import jalview.ws.params.ArgumentI;
 
-import java.io.FileNotFoundException;
+import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
+import javax.swing.JInternalFrame;
+
 public class HMMAlignThread implements Runnable
 {
 
+  /**
+   * feature settings from view that job was associated with
+   */
+  protected FeatureRendererSettings featureSettings = null;
+
+  /**
+   * Object containing frequently used commands.
+   */
+  HMMERCommands cmds = new HMMERCommands();
+
   AlignFrame af;
 
   AlignmentI alignment;
 
+  AlignmentI dataset;
+
+  List<AlignmentOrder> orders;
+
+  AlignmentView msa;
+
   HiddenMarkovModel hmm;
 
+  List<ArgumentI> args;
+
   boolean newFrame;
 
   long barID;
 
   Map<Integer, SequenceI> hmmSeqs;
 
-  public HMMAlignThread(AlignFrame af, boolean createNewFrame)
+  File hmmTemp = null;
+
+  File outTemp = null;
+
+  File inputTemp = null;
+
+  List<AlignmentOrder> allOrders;
+
+  SequenceI[][] allResults;
+
+  /**
+   * Constructor for the HMMAlignThread. If create new frame is set to true, a
+   * new frame will be created.
+   * 
+   * @param af
+   * @param createNewFrame
+   */
+  public HMMAlignThread(AlignFrame af, boolean createNewFrame,
+          List<ArgumentI> args)
   {
     this.af = af;
     alignment = af.getViewport().getAlignment();
-    hmm = alignment.getSequenceAt(0).getHMM();
+    if (alignment.getDataset() != null)
+    {
+      dataset = alignment.getDataset();
+    }
     newFrame = createNewFrame;
+    featureSettings = af.getFeatureRenderer().getSettings();
+    this.args = args;
   }
 
+  /**
+   * Runs the HMMAlignThread: the data on the alignment or group is exported,
+   * then the command is executed in the command line and then the data is
+   * imported and displayed in a new frame (if true). The command is executed
+   * for each segemtn of the alignment.
+   */
   @Override
   public void run()
   {
+
+    hmm = af.getSelectedHMM();
+
     barID = System.currentTimeMillis();
-    af.setProgressBar(MessageManager.getString("status.running_hmmbuild"),
+    af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
             barID);
+    cmds.HMMERFOLDER = Cache.getProperty(Preferences.HMMER_PATH);
+      prepareAlignment();
+      SequenceI[][] subAlignments = msa.getVisibleContigs('-');
+      allOrders = new ArrayList<>();
+      allResults = new SequenceI[subAlignments.length][];
+      int job = 0;
+      for (SequenceI[] seqs : subAlignments)
+      {
+        cmds.uniquifySequences(seqs);
+        try
+        {
+          createTemporaryFiles();
+        } catch (IOException e2)
+        {
+          e2.printStackTrace();
+        }
+        try
+        {
+          cmds.exportData(seqs, outTemp.getAbsoluteFile(), hmm,
+                  hmmTemp.getAbsoluteFile());
+        } catch (IOException e1)
+        {
+          e1.printStackTrace();
+        }
+        try
+        {
+          boolean ran = runCommand();
+          if (!ran)
+          {
+            JvOptionPane.showInternalMessageDialog(af,
+                    MessageManager.getString("warn.hmmalign_failed"));
+            return;
+          }
+        } catch (IOException | InterruptedException e)
+        {
+          e.printStackTrace();
+        }
+        try
+        {
+          importData(job);
+        } catch (IOException | InterruptedException e)
+        {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+        }
+        job++;
+      }
+
+
+    displayResults(newFrame);
 
-    try
+    af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
+            barID);
+
+  }
+
+  /**
+   * Creates temporary files for exporting and importing the data.
+   * 
+   * @throws IOException
+   */
+  private void createTemporaryFiles() throws IOException
+  {
+    if (hmmTemp == null)
     {
-      try
-      {
-        hmmSeqs = alignment.getHMMConsensusSequences(true);
-        HMMERCommands.exportData(alignment, true, true, hmm);
-      } catch (FileNotFoundException e)
-      {
-        // TODO Auto-generated catch block
-        e.printStackTrace();
+      hmmTemp = File.createTempFile("hmm", ".hmm");
+      hmmTemp.deleteOnExit();
+    }
+    if (outTemp == null)
+    {
+      outTemp = File.createTempFile("output", ".sto");
+      outTemp.deleteOnExit();
+    }
+    inputTemp = File.createTempFile("input", ".sto");
+    inputTemp.deleteOnExit();
+  }
 
-      }
-      try
-      {
-        runCommand();
-      } catch (IOException | InterruptedException e)
+  /**
+   * Executes the hmmalign command in the command line.
+   * 
+   * @return
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  private boolean runCommand() throws IOException, InterruptedException
+  {
+    File file = new File(cmds.HMMERFOLDER + "/hmmalign");
+    if (!file.canExecute())
+    {
+      file = new File(cmds.HMMERFOLDER + "/hmmalign.exe");
       {
-        // TODO Auto-generated catch block
-        e.printStackTrace();
+        if (!file.canExecute())
+        {
+          return false;
+        }
       }
-      try
-      {
-        importData();
-      } catch (IOException | InterruptedException e)
+    }
+    String command = cmds.HMMERFOLDER + cmds.HMMALIGN;
+    String version = Cache.getProperty("HMMER_VERSION");
+    if (!"3.1b2".equals(version))
+    {
+      command += cmds.ALLCOL;
+    }
+    if (args != null)
+    {
+      for (ArgumentI arg : args)
       {
-        // TODO Auto-generated catch block
-        e.printStackTrace();
+        String name = arg.getName();
+        switch (name)
+        {
+        case "Trim Non-Matching Termini":
+          command += "--trim";
+        }
       }
-    } catch (Exception e)
+    }
+    command += " -o " + inputTemp.getAbsolutePath() + cmds.SPACE
+            + hmmTemp.getAbsolutePath() + cmds.SPACE
+            + outTemp.getAbsolutePath();
+    return cmds.runCommand(command);
+  }
+
+  /**
+   * Imports the data from the temporary file to which the output of hmmalign is
+   * directed. this is used for an internal job.
+   * 
+   * @param index
+   *          The index of the 'job' (or region of an alignment).
+   * @throws IOException
+   * @throws InterruptedException
+   */
+  private void importData(int index)
+          throws IOException, InterruptedException
+  {
+    StockholmFile file = new StockholmFile(inputTemp.getAbsolutePath(),
+            DataSourceType.FILE);
+    SequenceI[] result = file.getSeqsAsArray();
+    AlignmentOrder msaorder = new AlignmentOrder(result);
+    jalview.analysis.AlignmentSorter.recoverOrder(result);
+    jalview.analysis.SeqsetUtils.deuniquify(cmds.hash, result);
+    allOrders.add(msaorder);
+    allResults[index] = result;
+    hmmTemp.delete();
+    outTemp.delete();
+    inputTemp.delete();
+  }
+
+  /**
+   * Gathers the sequences in preparation for the alignment.
+   */
+  private void prepareAlignment()
+  {
+    // hmmSeqs = alignment.getHMMConsensusSequences(true);
+    msa = af.gatherSequencesForAlignment();
+  }
+
+  /**
+   * Displays the results of all 'jobs'.
+   * 
+   * @param newFrame
+   */
+  private void displayResults(boolean newFrame)
+  {
+    AlignmentOrder[] arrOrders = allOrders
+            .toArray(new AlignmentOrder[allOrders.size()]);
+    Object[] newview = msa.getUpdatedView(allResults,
+            arrOrders, '-');
+    SequenceI[] alignment = (SequenceI[]) newview[0];
+    HiddenColumns hidden = (HiddenColumns) newview[1];
+    Alignment al = new Alignment(alignment);
+    al.setProperty("Alignment Program", "hmmalign");
+    if (dataset != null)
     {
+      al.setDataset(dataset);
+    }
 
-    } finally
+    if (newFrame)
     {
-      af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
-              barID);
+      displayInNewFrame(al, allOrders, hidden);
     }
   }
 
-  private void runCommand() throws IOException, InterruptedException
+  /**
+   * Displays the results in a new frame.
+   * 
+   * @param al
+   *          The alignment containing the results.
+   * @param alorders
+   *          The order of the sequences in the alignment on which the jobs were
+   *          run.
+   * @param hidden
+   *          Hidden columns in the previous alignment.
+   */
+  private void displayInNewFrame(AlignmentI al,
+          List<AlignmentOrder> alorders, HiddenColumns hidden)
   {
-    String command = HMMERCommands.HMMALIGN;
-    if (!hmm.getFileHeader().contains("HMMER3/f"))
+    AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
+
+    // initialise with same renderer settings as in parent alignframe.
+    af.getFeatureRenderer().transferSettings(this.featureSettings);
+
+    if (allOrders.size() > 0)
     {
-      command += HMMERCommands.ALLCOL;
+      addSortByMenuItems(af, allOrders);
     }
-    command += HMMERCommands.TRIM + HMMERCommands.OUTPUTALIGNMENT
-            + HMMERCommands.JALVIEWDIRECTORY + HMMERCommands.HMMBUFFER
-            + HMMERCommands.JALVIEWDIRECTORY
-            + HMMERCommands.ALIGNMENTBUFFER;
-    HMMERCommands.runCommand(command);
+
+    // TODO: refactor retrieve and show as new splitFrame as Desktop method
+
+    /*
+     * If alignment was requested from one half of a SplitFrame, show in a
+     * SplitFrame with the other pane similarly aligned.
+     */
+    AlignFrame requestedBy = this.af;
+    if (requestedBy != null && requestedBy.getSplitViewContainer() != null
+            && requestedBy.getSplitViewContainer()
+                    .getComplement(requestedBy) != null)
+    {
+      AlignmentI complement = requestedBy.getSplitViewContainer()
+              .getComplement(requestedBy);
+      String complementTitle = requestedBy.getSplitViewContainer()
+              .getComplementTitle(requestedBy);
+      // becomes null if the alignment window was closed before the alignment
+      // job finished.
+      AlignmentI copyComplement = new Alignment(complement);
+      // todo should this be done by copy constructor?
+      copyComplement.setGapCharacter(complement.getGapCharacter());
+      // share the same dataset (and the mappings it holds)
+      copyComplement.setDataset(complement.getDataset());
+      copyComplement.alignAs(al);
+      if (copyComplement.getHeight() > 0)
+      {
+        af.setTitle(this.af.getTitle());
+        AlignFrame af2 = new AlignFrame(copyComplement,
+                AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+        af2.setTitle(complementTitle);
+        String linkedTitle = MessageManager
+                .getString("label.linked_view_title");
+        JInternalFrame splitFrame = new SplitFrame(
+                al.isNucleotide() ? af : af2, al.isNucleotide() ? af2 : af);
+        Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
+        return;
+      }
+    }
+
+    /*
+     * Not from SplitFrame, or failed to created a complementary alignment
+     */
+    Desktop.addInternalFrame(af, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT);
   }
 
-  private void importData() throws IOException, InterruptedException
+  /**
+   * Add sort order options to the AlignFrame menus.
+   * 
+   * @param af
+   * @param alorders
+   */
+  protected void addSortByMenuItems(AlignFrame af,
+          List<AlignmentOrder> alorders)
   {
-    if (newFrame)
+    // update orders
+    if (alorders.size() == 1)
     {
-      FileLoader loader = new FileLoader();
-      AlignFrame newAFrame = loader.LoadFileWaitTillLoaded(
-              HMMERCommands.ALIGNMENTINPUT, DataSourceType.FILE);
-      addSeqs(newAFrame);
+      af.addSortByOrderMenuItem("hmmalign" + " Ordering", alorders.get(0));
     }
     else
     {
-      af.getViewport().getAlignment().getSequences().clear();
-      af.loadJalviewDataFile(HMMERCommands.ALIGNMENTBUFFER,
-              DataSourceType.FILE, FileFormat.Stockholm, null);
-      addSeqs(af);
+      // construct a non-redundant ordering set
+      List<String> names = new ArrayList<>();
+      for (int i = 0, l = alorders.size(); i < l; i++)
+      {
+        String orderName = " Region " + i;
+        int j = i + 1;
+
+        while (j < l)
+        {
+          if (alorders.get(i).equals(alorders.get(j)))
+          {
+            alorders.remove(j);
+            l--;
+            orderName += "," + j;
+          }
+          else
+          {
+            j++;
+          }
+        }
+
+        if (i == 0 && j == 1)
+        {
+          names.add("");
+        }
+        else
+        {
+          names.add(orderName);
+        }
+      }
+      for (int i = 0, l = alorders.size(); i < l; i++)
+      {
+        af.addSortByOrderMenuItem("hmmalign" + (names.get(i)) + " Ordering",
+                alorders.get(i));
+      }
     }
   }
 
-  private void addSeqs(AlignFrame alignFrame)
+  /**
+   * Runs hmmalign, and waits for the results to be imported before continuing
+   */
+  public void hmmalignWaitTillComplete()
   {
-    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(alignFrame, seq, pos);
+      try
+      {
+        Thread.sleep(500);
+      } catch (Exception ex)
+      {
+      }
     }
-  }
 
   }
+  }