JAL-2937 working Cygwin bash execution of hmmbuild on Windows
[jalview.git] / src / jalview / hmmer / HMMBuild.java
1 package jalview.hmmer;
2
3 import jalview.api.AlignViewportI;
4 import jalview.bin.Cache;
5 import jalview.datamodel.Alignment;
6 import jalview.datamodel.AlignmentI;
7 import jalview.datamodel.AnnotatedCollectionI;
8 import jalview.datamodel.SequenceGroup;
9 import jalview.datamodel.SequenceI;
10 import jalview.gui.AlignFrame;
11 import jalview.gui.JvOptionPane;
12 import jalview.io.DataSourceType;
13 import jalview.io.FileParse;
14 import jalview.io.HMMFile;
15 import jalview.util.FileUtils;
16 import jalview.util.MessageManager;
17 import jalview.ws.params.ArgumentI;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Hashtable;
23 import java.util.List;
24
25 /**
26  * A class that runs the hmmbuild command as a separate process.
27  * 
28  * @author gmcarstairs
29  *
30  */
31 public class HMMBuild extends HmmerCommand
32 {
33   static final String ARG_AMINO = "--amino";
34
35   static final String ARG_DNA = "--dna";
36
37   static final String ARG_RNA = "--rna";
38
39   /**
40    * Constructor
41    * 
42    * @param alignFrame
43    * @param args
44    */
45   public HMMBuild(AlignFrame alignFrame, List<ArgumentI> args)
46   {
47     super(alignFrame, args);
48   }
49
50   /**
51    * Builds a HMM from an alignment (and/or groups), then imports and adds it to
52    * the alignment (and/or groups). Call this method directly to execute
53    * synchronously, or via start() in a new Thread for asynchronously.
54    */
55   @Override
56   public void run()
57   {
58     if (params == null || params.isEmpty())
59     {
60       Cache.log.error("No parameters to HMMBuild!|");
61       return;
62     }
63
64     long msgID = System.currentTimeMillis();
65     af.setProgressBar(MessageManager.getString("status.running_hmmbuild"),
66             msgID);
67
68     AlignViewportI viewport = af.getViewport();
69     try
70     {
71       /*
72        * run hmmbuild for alignment and/or groups as selected
73        */
74       List<AnnotatedCollectionI> runBuildFor = parseParameters(viewport);
75
76       for (AnnotatedCollectionI grp : runBuildFor)
77       {
78         runHMMBuild(grp);
79       }
80     } finally
81     {
82       af.setProgressBar("", msgID);
83       viewport.alignmentChanged(af.alignPanel);
84       af.buildColourMenu(); // to enable HMMER colour schemes
85     }
86   }
87
88   /**
89    * Scans the parameters to determine whether to run hmmmbuild for the whole
90    * alignment or specified subgroup(s) or both
91    * 
92    * @param viewport
93    * @return
94    */
95   protected List<AnnotatedCollectionI> parseParameters(
96           AlignViewportI viewport)
97   {
98     List<AnnotatedCollectionI> runBuildFor = new ArrayList<>();
99     for (ArgumentI arg : params)
100     {
101       String name = arg.getName();
102       if (MessageManager.getString("label.hmmbuild_for").equals(name))
103       {
104         String value = arg.getValue();
105         if (MessageManager.getString("label.alignment").equals(value))
106         {
107           runBuildFor.add(alignment);
108         }
109         else if (MessageManager.getString("label.groups_and_alignment")
110                 .equals(value))
111         {
112           runBuildFor.add(alignment);
113           runBuildFor.addAll(viewport.getAlignment().getGroups());
114         }
115         else if (MessageManager.getString("label.groups").equals(value))
116         {
117           runBuildFor.addAll(viewport.getAlignment().getGroups());
118         }
119         else if (MessageManager.getString("label.selected_group")
120                 .equals(value))
121         {
122           runBuildFor.add(viewport.getSelectionGroup());
123         }
124       }
125       else if (MessageManager.getString("label.use_reference")
126               .equals(name))
127       {
128         // todo disable this option if no RF annotation on alignment
129         if (!af.getViewport().hasReferenceAnnotation())
130         {
131           JvOptionPane.showInternalMessageDialog(af, MessageManager
132                   .getString("warn.no_reference_annotation"));
133           // return;
134         }
135       }
136     }
137     return runBuildFor;
138   }
139
140   /**
141    * Runs hmmbuild on the given sequences (alignment or group)
142    * 
143    * @param grp
144    */
145   private void runHMMBuild(AnnotatedCollectionI ac)
146   {
147     File hmmFile = null;
148     File alignmentFile = null;
149     try
150     {
151       hmmFile = FileUtils.createTempFile("hmm", ".hmm");
152       alignmentFile = FileUtils.createTempFile("output", ".sto");
153
154       if (ac instanceof Alignment)
155       {
156         AlignmentI al = (Alignment) ac;
157         // todo pad gaps in an unaligned SequenceGroup as well?
158         if (!al.isAligned())
159         {
160           al.padGaps();
161         }
162       }
163
164       deleteHmmSequences(ac);
165
166       List<SequenceI> copy = new ArrayList<>();
167       if (ac instanceof Alignment)
168       {
169         copy.addAll(ac.getSequences());
170       }
171       else
172       {
173         SequenceI[] sel = ((SequenceGroup) ac)
174                 .getSelectionAsNewSequences((AlignmentI) ac.getContext());
175         for (SequenceI seq : sel)
176         {
177           copy.add(seq);
178         }
179       }
180
181       SequenceI[] copyArray = copy.toArray(new SequenceI[copy.size()]);
182       Hashtable sequencesHash = stashSequences(copyArray);
183
184       exportStockholm(copyArray, alignmentFile, ac);
185
186       recoverSequences(sequencesHash, copy.toArray(new SequenceI[] {}));
187
188       boolean ran = runCommand(alignmentFile, hmmFile, ac);
189       if (!ran)
190       {
191         return;
192       }
193       importData(hmmFile, ac);
194     } catch (Exception e)
195     {
196       e.printStackTrace();
197     } finally
198     {
199       if (hmmFile != null)
200       {
201         hmmFile.delete();
202       }
203       if (alignmentFile != null)
204       {
205         alignmentFile.delete();
206       }
207     }
208   }
209
210   /**
211    * A helper method that deletes any HMM consensus sequence from the given
212    * collection, and from the parent alignment if <code>ac</code> is a subgroup
213    * 
214    * @param ac
215    */
216   void deleteHmmSequences(AnnotatedCollectionI ac)
217   {
218     SequenceI hmmSeq = ac.getHmmConsensus();
219     if (hmmSeq != null)
220     {
221       if (ac instanceof SequenceGroup)
222       {
223         ((SequenceGroup) ac).deleteSequence(hmmSeq, false);
224         AnnotatedCollectionI context = ac.getContext();
225         if (context != null && context instanceof AlignmentI)
226         {
227           ((AlignmentI) context).deleteSequence(hmmSeq);
228         }
229       }
230       else
231       {
232         ((AlignmentI) ac).deleteSequence(hmmSeq);
233       }
234     }
235   }
236
237   /**
238    * Constructs and executes the hmmbuild command as a separate process
239    * 
240    * @param sequencesFile
241    *          the alignment from which the HMM is built
242    * @param hmmFile
243    *          the output file to which the HMM is written
244    * @param group
245    *          alignment or group for which the hmm is generated
246    * 
247    * @return
248    * @throws IOException
249    */
250   private boolean runCommand(File sequencesFile, File hmmFile,
251           AnnotatedCollectionI group) throws IOException
252   {
253     String cmd = getCommandPath(HMMBUILD);
254     if (cmd == null)
255     {
256       return false; // executable not found
257     }
258     List<String> args = new ArrayList<>();
259     args.add(cmd);
260
261     /*
262      * HMM name (will be given to consensus sequence) is
263      * - as specified by an input parameter if set
264      * - else group name with _HMM appended (if for a group)
265      * - else align frame title with _HMM appended (if title is not too long)
266      * - else "Alignment_HMM" 
267      */
268     String name = "";
269
270     if (params != null)
271     {
272       for (ArgumentI arg : params)
273       {
274         String argName = arg.getName();
275         switch (argName)
276         {
277         case "HMM Name":
278           name = arg.getValue().trim();
279           break;
280         case "Use Reference Annotation":
281           args.add("--hand");
282           break;
283         }
284       }
285     }
286
287     if (group instanceof SequenceGroup)
288     {
289       name = ((SequenceGroup) group).getName() + "_HMM";
290     }
291
292     if ("".equals(name))
293     {
294       if (af != null && af.getTitle().length() < 15)
295       {
296         name = af.getTitle();
297       }
298       else
299       {
300         name = "Alignment_HMM";
301       }
302     }
303
304     args.add("-n");
305     args.add(name.replace(' ', '_'));
306     if (!alignment.isNucleotide())
307     {
308       args.add(ARG_AMINO); // TODO check for rna
309     }
310     else
311     {
312       args.add(ARG_DNA);
313     }
314
315     args.add(getFilePath(hmmFile));
316     args.add(getFilePath(sequencesFile));
317
318     return runCommand(args);
319   }
320
321   /**
322    * Imports the .hmm file produced by hmmbuild, and inserts the HMM consensus
323    * sequence (with attached HMM profile) as the first sequence in the alignment
324    * or group for which it was generated
325    * 
326    * @param hmmFile
327    * @param ac
328    *          (optional) the group for which the hmm was generated
329    * @throws IOException
330    */
331   private void importData(File hmmFile, AnnotatedCollectionI ac)
332           throws IOException
333   {
334     if (hmmFile.length() == 0L)
335     {
336       Cache.log.error("Error: hmmbuild produced empty hmm file");
337       return;
338     }
339
340     HMMFile file = new HMMFile(
341             new FileParse(hmmFile.getAbsolutePath(), DataSourceType.FILE));
342     SequenceI hmmSeq = file.getHMM().getConsensusSequence();
343
344     if (hmmSeq == null)
345     {
346       // hmmbuild failure not detected earlier
347       return;
348     }
349
350     if (ac instanceof SequenceGroup)
351     {
352       SequenceGroup grp = (SequenceGroup) ac;
353       char gapChar = alignment.getGapCharacter();
354       hmmSeq.insertCharAt(0, ac.getStartRes(), gapChar);
355       hmmSeq.insertCharAt(ac.getEndRes() + 1,
356               alignment.getWidth() - ac.getEndRes() - 1, gapChar);
357       SequenceI topSeq = grp.getSequencesInOrder(alignment)[0];
358       int topIndex = alignment.findIndex(topSeq);
359       alignment.insertSequenceAt(topIndex, hmmSeq);
360       ac.setSeqrep(hmmSeq);
361       grp.addSequence(hmmSeq, false);
362       grp.setHmmConsensus(hmmSeq);
363     }
364     else
365     {
366       alignment.insertSequenceAt(0, hmmSeq);
367       alignment.setHmmConsensus(hmmSeq);
368     }
369
370     if (af.getSelectedHMM() == null)
371     {
372       af.setSelectedHMMSequence(hmmSeq);
373     }
374   }
375 }