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