Merge branch 'develop' into features/mchmmer
[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     boolean foundArg = false;
100
101     for (ArgumentI arg : params)
102     {
103       String name = arg.getName();
104       if (MessageManager.getString("label.hmmbuild_for").equals(name))
105       {
106         foundArg = true;
107         String value = arg.getValue();
108         if (MessageManager.getString("label.alignment").equals(value))
109         {
110           runBuildFor.add(alignment);
111         }
112         else if (MessageManager.getString("label.groups_and_alignment")
113                 .equals(value))
114         {
115           runBuildFor.add(alignment);
116           runBuildFor.addAll(viewport.getAlignment().getGroups());
117         }
118         else if (MessageManager.getString("label.groups").equals(value))
119         {
120           runBuildFor.addAll(viewport.getAlignment().getGroups());
121         }
122         else if (MessageManager.getString("label.selected_group")
123                 .equals(value))
124         {
125           runBuildFor.add(viewport.getSelectionGroup());
126         }
127       }
128       else if (MessageManager.getString("label.use_reference")
129               .equals(name))
130       {
131         // todo disable this option if no RF annotation on alignment
132         if (!af.getViewport().hasReferenceAnnotation())
133         {
134           JvOptionPane.showInternalMessageDialog(af, MessageManager
135                   .getString("warn.no_reference_annotation"));
136           // return;
137         }
138       }
139     }
140
141     /*
142      * default is to build for the whole alignment
143      */
144     if (!foundArg)
145     {
146       runBuildFor.add(alignment);
147     }
148
149     return runBuildFor;
150   }
151
152   /**
153    * Runs hmmbuild on the given sequences (alignment or group)
154    * 
155    * @param grp
156    */
157   private void runHMMBuild(AnnotatedCollectionI ac)
158   {
159     File hmmFile = null;
160     File alignmentFile = null;
161     try
162     {
163       hmmFile = FileUtils.createTempFile("hmm", ".hmm");
164       alignmentFile = FileUtils.createTempFile("output", ".sto");
165
166       if (ac instanceof Alignment)
167       {
168         AlignmentI al = (Alignment) ac;
169         // todo pad gaps in an unaligned SequenceGroup as well?
170         if (!al.isAligned())
171         {
172           al.padGaps();
173         }
174       }
175
176       deleteHmmSequences(ac);
177
178       List<SequenceI> copy = new ArrayList<>();
179       if (ac instanceof Alignment)
180       {
181         copy.addAll(ac.getSequences());
182       }
183       else
184       {
185         SequenceI[] sel = ((SequenceGroup) ac)
186                 .getSelectionAsNewSequences((AlignmentI) ac.getContext());
187         for (SequenceI seq : sel)
188         {
189           copy.add(seq);
190         }
191       }
192
193       SequenceI[] copyArray = copy.toArray(new SequenceI[copy.size()]);
194       Hashtable sequencesHash = stashSequences(copyArray);
195
196       exportStockholm(copyArray, alignmentFile, ac);
197
198       recoverSequences(sequencesHash, copy.toArray(new SequenceI[] {}));
199
200       boolean ran = runCommand(alignmentFile, hmmFile, ac);
201       if (!ran)
202       {
203         JvOptionPane.showInternalMessageDialog(af, MessageManager
204                 .formatMessage("warn.command_failed", "hmmbuild"));
205         return;
206       }
207       importData(hmmFile, ac);
208     } catch (Exception e)
209     {
210       e.printStackTrace();
211     } finally
212     {
213       if (hmmFile != null)
214       {
215         hmmFile.delete();
216       }
217       if (alignmentFile != null)
218       {
219         alignmentFile.delete();
220       }
221     }
222   }
223
224   /**
225    * A helper method that deletes any HMM consensus sequence from the given
226    * collection, and from the parent alignment if <code>ac</code> is a subgroup
227    * 
228    * @param ac
229    */
230   void deleteHmmSequences(AnnotatedCollectionI ac)
231   {
232     List<SequenceI> hmmSeqs = ac.getHmmSequences();
233     for (SequenceI hmmSeq : hmmSeqs)
234     {
235       if (ac instanceof SequenceGroup)
236       {
237         ((SequenceGroup) ac).deleteSequence(hmmSeq, false);
238         AnnotatedCollectionI context = ac.getContext();
239         if (context != null && context instanceof AlignmentI)
240         {
241           ((AlignmentI) context).deleteSequence(hmmSeq);
242         }
243       }
244       else
245       {
246         ((AlignmentI) ac).deleteSequence(hmmSeq);
247       }
248     }
249   }
250
251   /**
252    * Constructs and executes the hmmbuild command as a separate process
253    * 
254    * @param sequencesFile
255    *          the alignment from which the HMM is built
256    * @param hmmFile
257    *          the output file to which the HMM is written
258    * @param group
259    *          alignment or group for which the hmm is generated
260    * 
261    * @return
262    * @throws IOException
263    */
264   private boolean runCommand(File sequencesFile, File hmmFile,
265           AnnotatedCollectionI group) throws IOException
266   {
267     String cmd = getCommandPath(HMMBUILD);
268     if (cmd == null)
269     {
270       return false; // executable not found
271     }
272     List<String> args = new ArrayList<>();
273     args.add(cmd);
274
275     /*
276      * HMM name (will be given to consensus sequence) is
277      * - as specified by an input parameter if set
278      * - else group name with _HMM appended (if for a group)
279      * - else align frame title with _HMM appended (if title is not too long)
280      * - else "Alignment_HMM" 
281      */
282     String name = "";
283
284     if (params != null)
285     {
286       for (ArgumentI arg : params)
287       {
288         String argName = arg.getName();
289         switch (argName)
290         {
291         case "HMM Name":
292           name = arg.getValue().trim();
293           break;
294         case "Use Reference Annotation":
295           args.add("--hand");
296           break;
297         }
298       }
299     }
300
301     if (group instanceof SequenceGroup)
302     {
303       name = ((SequenceGroup) group).getName() + "_HMM";
304     }
305
306     if ("".equals(name))
307     {
308       if (af != null && af.getTitle().length() < 15)
309       {
310         name = af.getTitle();
311       }
312       else
313       {
314         name = "Alignment_HMM";
315       }
316     }
317
318     args.add("-n");
319     args.add(name.replace(' ', '_'));
320     if (!alignment.isNucleotide())
321     {
322       args.add(ARG_AMINO); // TODO check for rna
323     }
324     else
325     {
326       args.add(ARG_DNA);
327     }
328
329     args.add(getFilePath(hmmFile));
330     args.add(getFilePath(sequencesFile));
331
332     return runCommand(args);
333   }
334
335   /**
336    * Imports the .hmm file produced by hmmbuild, and inserts the HMM consensus
337    * sequence (with attached HMM profile) as the first sequence in the alignment
338    * or group for which it was generated
339    * 
340    * @param hmmFile
341    * @param ac
342    *          (optional) the group for which the hmm was generated
343    * @throws IOException
344    */
345   private void importData(File hmmFile, AnnotatedCollectionI ac)
346           throws IOException
347   {
348     if (hmmFile.length() == 0L)
349     {
350       Cache.log.error("Error: hmmbuild produced empty hmm file");
351       return;
352     }
353
354     HMMFile file = new HMMFile(
355             new FileParse(hmmFile.getAbsolutePath(), DataSourceType.FILE));
356     SequenceI hmmSeq = file.getHMM().getConsensusSequence();
357
358     if (hmmSeq == null)
359     {
360       // hmmbuild failure not detected earlier
361       return;
362     }
363
364     if (ac instanceof SequenceGroup)
365     {
366       SequenceGroup grp = (SequenceGroup) ac;
367       char gapChar = alignment.getGapCharacter();
368       hmmSeq.insertCharAt(0, ac.getStartRes(), gapChar);
369       hmmSeq.insertCharAt(ac.getEndRes() + 1,
370               alignment.getWidth() - ac.getEndRes() - 1, gapChar);
371       SequenceI topSeq = grp.getSequencesInOrder(alignment)[0];
372       int topIndex = alignment.findIndex(topSeq);
373       alignment.insertSequenceAt(topIndex, hmmSeq);
374       ac.setSeqrep(hmmSeq);
375       grp.addSequence(hmmSeq, false);
376     }
377     else
378     {
379       alignment.insertSequenceAt(0, hmmSeq);
380     }
381   }
382 }