3c3bd4089543367e81fe591cf6ee4621e6ca480a
[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         JvOptionPane.showInternalMessageDialog(af, MessageManager
192                 .formatMessage("warn.command_failed", "hmmbuild"));
193         return;
194       }
195       importData(hmmFile, ac);
196     } catch (Exception e)
197     {
198       e.printStackTrace();
199     } finally
200     {
201       if (hmmFile != null)
202       {
203         hmmFile.delete();
204       }
205       if (alignmentFile != null)
206       {
207         alignmentFile.delete();
208       }
209     }
210   }
211
212   /**
213    * A helper method that deletes any HMM consensus sequence from the given
214    * collection, and from the parent alignment if <code>ac</code> is a subgroup
215    * 
216    * @param ac
217    */
218   void deleteHmmSequences(AnnotatedCollectionI ac)
219   {
220     List<SequenceI> hmmSeqs = ac.getHmmSequences();
221     for (SequenceI hmmSeq : hmmSeqs)
222     {
223       if (ac instanceof SequenceGroup)
224       {
225         ((SequenceGroup) ac).deleteSequence(hmmSeq, false);
226         AnnotatedCollectionI context = ac.getContext();
227         if (context != null && context instanceof AlignmentI)
228         {
229           ((AlignmentI) context).deleteSequence(hmmSeq);
230         }
231       }
232       else
233       {
234         ((AlignmentI) ac).deleteSequence(hmmSeq);
235       }
236     }
237   }
238
239   /**
240    * Constructs and executes the hmmbuild command as a separate process
241    * 
242    * @param sequencesFile
243    *          the alignment from which the HMM is built
244    * @param hmmFile
245    *          the output file to which the HMM is written
246    * @param group
247    *          alignment or group for which the hmm is generated
248    * 
249    * @return
250    * @throws IOException
251    */
252   private boolean runCommand(File sequencesFile, File hmmFile,
253           AnnotatedCollectionI group) throws IOException
254   {
255     String cmd = getCommandPath(HMMBUILD);
256     if (cmd == null)
257     {
258       return false; // executable not found
259     }
260     List<String> args = new ArrayList<>();
261     args.add(cmd);
262
263     /*
264      * HMM name (will be given to consensus sequence) is
265      * - as specified by an input parameter if set
266      * - else group name with _HMM appended (if for a group)
267      * - else align frame title with _HMM appended (if title is not too long)
268      * - else "Alignment_HMM" 
269      */
270     String name = "";
271
272     if (params != null)
273     {
274       for (ArgumentI arg : params)
275       {
276         String argName = arg.getName();
277         switch (argName)
278         {
279         case "HMM Name":
280           name = arg.getValue().trim();
281           break;
282         case "Use Reference Annotation":
283           args.add("--hand");
284           break;
285         }
286       }
287     }
288
289     if (group instanceof SequenceGroup)
290     {
291       name = ((SequenceGroup) group).getName() + "_HMM";
292     }
293
294     if ("".equals(name))
295     {
296       if (af != null && af.getTitle().length() < 15)
297       {
298         name = af.getTitle();
299       }
300       else
301       {
302         name = "Alignment_HMM";
303       }
304     }
305
306     args.add("-n");
307     args.add(name.replace(' ', '_'));
308     if (!alignment.isNucleotide())
309     {
310       args.add(ARG_AMINO); // TODO check for rna
311     }
312     else
313     {
314       args.add(ARG_DNA);
315     }
316
317     args.add(getFilePath(hmmFile));
318     args.add(getFilePath(sequencesFile));
319
320     return runCommand(args);
321   }
322
323   /**
324    * Imports the .hmm file produced by hmmbuild, and inserts the HMM consensus
325    * sequence (with attached HMM profile) as the first sequence in the alignment
326    * or group for which it was generated
327    * 
328    * @param hmmFile
329    * @param ac
330    *          (optional) the group for which the hmm was generated
331    * @throws IOException
332    */
333   private void importData(File hmmFile, AnnotatedCollectionI ac)
334           throws IOException
335   {
336     if (hmmFile.length() == 0L)
337     {
338       Cache.log.error("Error: hmmbuild produced empty hmm file");
339       return;
340     }
341
342     HMMFile file = new HMMFile(
343             new FileParse(hmmFile.getAbsolutePath(), DataSourceType.FILE));
344     SequenceI hmmSeq = file.getHMM().getConsensusSequence();
345
346     if (hmmSeq == null)
347     {
348       // hmmbuild failure not detected earlier
349       return;
350     }
351
352     if (ac instanceof SequenceGroup)
353     {
354       SequenceGroup grp = (SequenceGroup) ac;
355       char gapChar = alignment.getGapCharacter();
356       hmmSeq.insertCharAt(0, ac.getStartRes(), gapChar);
357       hmmSeq.insertCharAt(ac.getEndRes() + 1,
358               alignment.getWidth() - ac.getEndRes() - 1, gapChar);
359       SequenceI topSeq = grp.getSequencesInOrder(alignment)[0];
360       int topIndex = alignment.findIndex(topSeq);
361       alignment.insertSequenceAt(topIndex, hmmSeq);
362       ac.setSeqrep(hmmSeq);
363       grp.addSequence(hmmSeq, false);
364     }
365     else
366     {
367       alignment.insertSequenceAt(0, hmmSeq);
368     }
369   }
370 }