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