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