JAL-2629 manual curation of HMMs via reference annotation functional
[jalview.git] / src / jalview / hmmer / HMMAlignThread.java
1 package jalview.hmmer;
2
3 import jalview.bin.Cache;
4 import jalview.datamodel.Alignment;
5 import jalview.datamodel.AlignmentI;
6 import jalview.datamodel.AlignmentOrder;
7 import jalview.datamodel.AlignmentView;
8 import jalview.datamodel.HiddenColumns;
9 import jalview.datamodel.HiddenMarkovModel;
10 import jalview.datamodel.SequenceI;
11 import jalview.gui.AlignFrame;
12 import jalview.gui.Desktop;
13 import jalview.gui.JvOptionPane;
14 import jalview.gui.Preferences;
15 import jalview.gui.SplitFrame;
16 import jalview.io.DataSourceType;
17 import jalview.io.StockholmFile;
18 import jalview.util.MessageManager;
19 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
20 import jalview.ws.params.ArgumentI;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27
28 import javax.swing.JInternalFrame;
29
30 public class HMMAlignThread implements Runnable
31 {
32
33   /**
34    * feature settings from view that job was associated with
35    */
36   protected FeatureRendererSettings featureSettings = null;
37
38   /**
39    * Object containing frequently used commands.
40    */
41   HMMERCommands cmds = new HMMERCommands();
42
43   AlignFrame af;
44
45   AlignmentI alignment;
46
47   AlignmentI dataset;
48
49   List<AlignmentOrder> orders;
50
51   AlignmentView msa;
52
53   HiddenMarkovModel hmm;
54
55   List<ArgumentI> args;
56
57   boolean newFrame;
58
59   long barID;
60
61   Map<Integer, SequenceI> hmmSeqs;
62
63   File hmmTemp = null;
64
65   File outTemp = null;
66
67   File inputTemp = null;
68
69   List<AlignmentOrder> allOrders;
70
71   SequenceI[][] allResults;
72
73   /**
74    * Constructor for the HMMAlignThread. If create new frame is set to true, a
75    * new frame will be created.
76    * 
77    * @param af
78    * @param createNewFrame
79    */
80   public HMMAlignThread(AlignFrame af, boolean createNewFrame,
81           List<ArgumentI> args)
82   {
83     this.af = af;
84     alignment = af.getViewport().getAlignment();
85     if (alignment.getDataset() != null)
86     {
87       dataset = alignment.getDataset();
88     }
89     newFrame = createNewFrame;
90     featureSettings = af.getFeatureRenderer().getSettings();
91     this.args = args;
92   }
93
94   /**
95    * Runs the HMMAlignThread: the data on the alignment or group is exported,
96    * then the command is executed in the command line and then the data is
97    * imported and displayed in a new frame (if true). The command is executed
98    * for each segemtn of the alignment.
99    */
100   @Override
101   public void run()
102   {
103
104     hmm = af.getSelectedHMM();
105
106     barID = System.currentTimeMillis();
107     af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
108             barID);
109     cmds.HMMERFOLDER = Cache.getProperty(Preferences.HMMER_PATH);
110       prepareAlignment();
111       SequenceI[][] subAlignments = msa.getVisibleContigs('-');
112       allOrders = new ArrayList<>();
113       allResults = new SequenceI[subAlignments.length][];
114       int job = 0;
115       for (SequenceI[] seqs : subAlignments)
116       {
117         cmds.uniquifySequences(seqs);
118         try
119         {
120           createTemporaryFiles();
121         } catch (IOException e2)
122         {
123           e2.printStackTrace();
124         }
125         try
126         {
127           cmds.exportData(seqs, outTemp.getAbsoluteFile(), hmm,
128                 hmmTemp.getAbsoluteFile(), null);
129         } catch (IOException e1)
130         {
131           e1.printStackTrace();
132         }
133         try
134         {
135           boolean ran = runCommand();
136           if (!ran)
137           {
138             JvOptionPane.showInternalMessageDialog(af,
139                     MessageManager.getString("warn.hmmalign_failed"));
140             return;
141           }
142         } catch (IOException | InterruptedException e)
143         {
144           e.printStackTrace();
145         }
146         try
147         {
148           importData(job);
149         } catch (IOException | InterruptedException e)
150         {
151           // TODO Auto-generated catch block
152           e.printStackTrace();
153         }
154         job++;
155       }
156
157
158     displayResults(newFrame);
159
160     af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
161             barID);
162
163   }
164
165   /**
166    * Creates temporary files for exporting and importing the data.
167    * 
168    * @throws IOException
169    */
170   private void createTemporaryFiles() throws IOException
171   {
172     if (hmmTemp == null)
173     {
174       hmmTemp = File.createTempFile("hmm", ".hmm");
175       hmmTemp.deleteOnExit();
176     }
177     if (outTemp == null)
178     {
179       outTemp = File.createTempFile("output", ".sto");
180       outTemp.deleteOnExit();
181     }
182     inputTemp = File.createTempFile("input", ".sto");
183     inputTemp.deleteOnExit();
184   }
185
186   /**
187    * Executes the hmmalign command in the command line.
188    * 
189    * @return
190    * @throws IOException
191    * @throws InterruptedException
192    */
193   private boolean runCommand() throws IOException, InterruptedException
194   {
195     File file = new File(cmds.HMMERFOLDER + "/hmmalign");
196     if (!file.canExecute())
197     {
198       file = new File(cmds.HMMERFOLDER + "/hmmalign.exe");
199       {
200         if (!file.canExecute())
201         {
202           return false;
203         }
204       }
205     }
206     String command = cmds.HMMERFOLDER + cmds.HMMALIGN;
207     String version = Cache.getProperty("HMMER_VERSION");
208     if (!"3.1b2".equals(version))
209     {
210       command += cmds.ALLCOL;
211     }
212     if (args != null)
213     {
214       for (ArgumentI arg : args)
215       {
216         String name = arg.getName();
217         switch (name)
218         {
219         case "Trim Non-Matching Termini":
220           command += "--trim";
221         }
222       }
223     }
224     command += " -o " + inputTemp.getAbsolutePath() + cmds.SPACE
225             + hmmTemp.getAbsolutePath() + cmds.SPACE
226             + outTemp.getAbsolutePath();
227     return cmds.runCommand(command);
228   }
229
230   /**
231    * Imports the data from the temporary file to which the output of hmmalign is
232    * directed. this is used for an internal job.
233    * 
234    * @param index
235    *          The index of the 'job' (or region of an alignment).
236    * @throws IOException
237    * @throws InterruptedException
238    */
239   private void importData(int index)
240           throws IOException, InterruptedException
241   {
242     StockholmFile file = new StockholmFile(inputTemp.getAbsolutePath(),
243             DataSourceType.FILE);
244     SequenceI[] result = file.getSeqsAsArray();
245     AlignmentOrder msaorder = new AlignmentOrder(result);
246     jalview.analysis.AlignmentSorter.recoverOrder(result);
247     jalview.analysis.SeqsetUtils.deuniquify(cmds.hash, result);
248     allOrders.add(msaorder);
249     allResults[index] = result;
250     hmmTemp.delete();
251     outTemp.delete();
252     inputTemp.delete();
253   }
254
255   /**
256    * Gathers the sequences in preparation for the alignment.
257    */
258   private void prepareAlignment()
259   {
260     // hmmSeqs = alignment.getHMMConsensusSequences(true);
261     msa = af.gatherSequencesForAlignment();
262   }
263
264   /**
265    * Displays the results of all 'jobs'.
266    * 
267    * @param newFrame
268    */
269   private void displayResults(boolean newFrame)
270   {
271     AlignmentOrder[] arrOrders = allOrders
272             .toArray(new AlignmentOrder[allOrders.size()]);
273     Object[] newview = msa.getUpdatedView(allResults,
274             arrOrders, '-');
275     SequenceI[] alignment = (SequenceI[]) newview[0];
276     HiddenColumns hidden = (HiddenColumns) newview[1];
277     Alignment al = new Alignment(alignment);
278     al.setProperty("Alignment Program", "hmmalign");
279     if (dataset != null)
280     {
281       al.setDataset(dataset);
282     }
283
284     if (newFrame)
285     {
286       displayInNewFrame(al, allOrders, hidden);
287     }
288   }
289
290   /**
291    * Displays the results in a new frame.
292    * 
293    * @param al
294    *          The alignment containing the results.
295    * @param alorders
296    *          The order of the sequences in the alignment on which the jobs were
297    *          run.
298    * @param hidden
299    *          Hidden columns in the previous alignment.
300    */
301   private void displayInNewFrame(AlignmentI al,
302           List<AlignmentOrder> alorders, HiddenColumns hidden)
303   {
304     AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
305             AlignFrame.DEFAULT_HEIGHT);
306
307     // initialise with same renderer settings as in parent alignframe.
308     af.getFeatureRenderer().transferSettings(this.featureSettings);
309
310     if (allOrders.size() > 0)
311     {
312       addSortByMenuItems(af, allOrders);
313     }
314
315     // TODO: refactor retrieve and show as new splitFrame as Desktop method
316
317     /*
318      * If alignment was requested from one half of a SplitFrame, show in a
319      * SplitFrame with the other pane similarly aligned.
320      */
321     AlignFrame requestedBy = this.af;
322     if (requestedBy != null && requestedBy.getSplitViewContainer() != null
323             && requestedBy.getSplitViewContainer()
324                     .getComplement(requestedBy) != null)
325     {
326       AlignmentI complement = requestedBy.getSplitViewContainer()
327               .getComplement(requestedBy);
328       String complementTitle = requestedBy.getSplitViewContainer()
329               .getComplementTitle(requestedBy);
330       // becomes null if the alignment window was closed before the alignment
331       // job finished.
332       AlignmentI copyComplement = new Alignment(complement);
333       // todo should this be done by copy constructor?
334       copyComplement.setGapCharacter(complement.getGapCharacter());
335       // share the same dataset (and the mappings it holds)
336       copyComplement.setDataset(complement.getDataset());
337       copyComplement.alignAs(al);
338       if (copyComplement.getHeight() > 0)
339       {
340         af.setTitle(this.af.getTitle());
341         AlignFrame af2 = new AlignFrame(copyComplement,
342                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
343         af2.setTitle(complementTitle);
344         String linkedTitle = MessageManager
345                 .getString("label.linked_view_title");
346         JInternalFrame splitFrame = new SplitFrame(
347                 al.isNucleotide() ? af : af2, al.isNucleotide() ? af2 : af);
348         Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
349         return;
350       }
351     }
352
353     /*
354      * Not from SplitFrame, or failed to created a complementary alignment
355      */
356     Desktop.addInternalFrame(af, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
357             AlignFrame.DEFAULT_HEIGHT);
358   }
359
360   /**
361    * Add sort order options to the AlignFrame menus.
362    * 
363    * @param af
364    * @param alorders
365    */
366   protected void addSortByMenuItems(AlignFrame af,
367           List<AlignmentOrder> alorders)
368   {
369     // update orders
370     if (alorders.size() == 1)
371     {
372       af.addSortByOrderMenuItem("hmmalign" + " Ordering", alorders.get(0));
373     }
374     else
375     {
376       // construct a non-redundant ordering set
377       List<String> names = new ArrayList<>();
378       for (int i = 0, l = alorders.size(); i < l; i++)
379       {
380         String orderName = " Region " + i;
381         int j = i + 1;
382
383         while (j < l)
384         {
385           if (alorders.get(i).equals(alorders.get(j)))
386           {
387             alorders.remove(j);
388             l--;
389             orderName += "," + j;
390           }
391           else
392           {
393             j++;
394           }
395         }
396
397         if (i == 0 && j == 1)
398         {
399           names.add("");
400         }
401         else
402         {
403           names.add(orderName);
404         }
405       }
406       for (int i = 0, l = alorders.size(); i < l; i++)
407       {
408         af.addSortByOrderMenuItem("hmmalign" + (names.get(i)) + " Ordering",
409                 alorders.get(i));
410       }
411     }
412   }
413
414   /**
415    * Runs hmmalign, and waits for the results to be imported before continuing
416    */
417   public void hmmalignWaitTillComplete()
418   {
419     Thread loader = new Thread(this);
420     loader.start();
421
422     while (loader.isAlive())
423     {
424       try
425       {
426         Thread.sleep(500);
427       } catch (Exception ex)
428       {
429       }
430     }
431
432   }
433   }
434
435