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