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