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