JAL-2629 InformationThread.findOrCreateAnnotation and related updates for a more...
[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.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.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.Iterator;
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)
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       viewport.updateInformation(af.alignPanel);
85       af.buildColourMenu(); // to enable HMMER colour schemes
86       af.setProgressBar("", msgID);
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     for (ArgumentI arg : params)
102     {
103       String name = arg.getName();
104       if (MessageManager.getString("label.hmmbuild_for").equals(name))
105       {
106         String value = arg.getValue();
107         if (MessageManager.getString("label.alignment").equals(value))
108         {
109           runBuildFor.add(alignment);
110         }
111         else if (MessageManager.getString("label.groups_and_alignment")
112                 .equals(value))
113         {
114           runBuildFor.add(alignment);
115           runBuildFor.addAll(viewport.getAlignment().getGroups());
116         }
117         else if (MessageManager.getString("label.groups").equals(value))
118         {
119           runBuildFor.addAll(viewport.getAlignment().getGroups());
120         }
121         else if (MessageManager.getString("label.selected_group")
122                 .equals(value))
123         {
124           runBuildFor.add(viewport.getSelectionGroup());
125         }
126       }
127       else if (MessageManager.getString("label.use_reference")
128               .equals(name))
129       {
130         // todo disable this option if no RF annotation on alignment
131         if (!af.getViewport().hasReferenceAnnotation())
132         {
133           JvOptionPane.showInternalMessageDialog(af, MessageManager
134                   .getString("warn.no_reference_annotation"));
135           // return;
136         }
137       }
138     }
139     return runBuildFor;
140   }
141
142   /**
143    * Runs hmmbuild on the given sequences (alignment or group)
144    * 
145    * @param grp
146    */
147   private void runHMMBuild(AnnotatedCollectionI ac)
148   {
149     File hmmFile = null;
150     File alignmentFile = null;
151     try
152     {
153       hmmFile = createTempFile("hmm", ".hmm");
154       alignmentFile = createTempFile("output", ".sto");
155       List<SequenceI> seqs = ac.getSequences();
156       List<SequenceI> copy = new ArrayList<>();
157       copy.addAll(seqs);
158
159       if (ac instanceof Alignment)
160       {
161         AlignmentI al = (Alignment) ac;
162         // todo pad gaps in an unaligned SequenceGroup as well?
163         if (!al.isAligned())
164         {
165           al.padGaps();
166         }
167       }
168
169       /*
170        * copy over sequences, excluding hmm consensus sequences
171        * hmm sequences are also deleted in preparation for 
172        * re-adding them when recalculated; Information annotation is not
173        * deleted, it will be updated to reference the new hmm sequence
174        * by InformationThread.findOrCreateAnnotation
175        */
176       Iterator<SequenceI> it = copy.iterator();
177       while (it.hasNext())
178       {
179         SequenceI seq = it.next();
180         if (seq.isHMMConsensusSequence())
181         {
182           alignment.deleteSequence(seq);
183           it.remove();
184         }
185       }
186
187       SequenceI[] copyArray = copy.toArray(new SequenceI[copy.size()]);
188       Hashtable sequencesHash = stashSequences(copyArray);
189
190       exportStockholm(copyArray, alignmentFile, ac);
191
192       recoverSequences(sequencesHash, seqs.toArray(new SequenceI[] {}));
193
194       boolean ran = runCommand(alignmentFile, hmmFile, ac);
195       if (!ran)
196       {
197         return;
198       }
199       importData(hmmFile, ac);
200     } catch (Exception e)
201     {
202       e.printStackTrace();
203     } finally
204     {
205       if (hmmFile != null)
206       {
207         hmmFile.delete();
208       }
209       if (alignmentFile != null)
210       {
211         alignmentFile.delete();
212       }
213     }
214   }
215
216   /**
217    * Constructs and executes the hmmbuild command as a separate process
218    * 
219    * @param sequencesFile
220    *          the alignment from which the HMM is built
221    * @param hmmFile
222    *          the output file to which the HMM is written
223    * @param group
224    *          alignment or group for which the hmm is generated
225    * 
226    * @return
227    * @throws IOException
228    */
229   private boolean runCommand(File sequencesFile, File hmmFile,
230           AnnotatedCollectionI group) throws IOException
231   {
232     String cmd = getCommandPath(HMMBUILD);
233     if (cmd == null)
234     {
235       return false; // executable not found
236     }
237     List<String> args = new ArrayList<>();
238     args.add(cmd);
239
240     /*
241      * HMM name (will be given to consensus sequence) is
242      * - as specified by an input parameter if set
243      * - else group name with _HMM appended (if for a group)
244      * - else align fame title with _HMM appended (if title is not too long)
245      * - else "Alignment_HMM" 
246      */
247     String name = "";
248
249     if (params != null)
250     {
251       for (ArgumentI arg : params)
252       {
253         String argName = arg.getName();
254         switch (argName)
255         {
256         case "HMM Name":
257           name = arg.getValue().trim();
258           break;
259         case "Use Reference Annotation":
260           args.add("--hand");
261           break;
262         }
263       }
264     }
265
266     if (group instanceof SequenceGroup)
267     {
268       name = ((SequenceGroup) group).getName() + "_HMM";
269     }
270
271     if ("".equals(name))
272     {
273       if (af != null && af.getTitle().length() < 15)
274       {
275         name = af.getTitle();
276       }
277       else
278       {
279         name = "Alignment_HMM";
280       }
281     }
282
283     args.add("-n");
284     args.add(name.replace(' ', '_'));
285     if (!alignment.isNucleotide())
286     {
287       args.add(ARG_AMINO); // TODO check for rna
288     }
289     else
290     {
291       args.add(ARG_DNA);
292     }
293
294     args.add(hmmFile.getAbsolutePath());
295     args.add(sequencesFile.getAbsolutePath());
296
297     return runCommand(args);
298   }
299
300   /**
301    * Imports the .hmm file produced by hmmbuild, and inserts the HMM consensus
302    * sequence (with attached HMM profile) as the first sequence in the alignment
303    * or group for which it was generated
304    * 
305    * @param hmmFile
306    * @param ac
307    *          (optional) the group for which the hmm was generated
308    * @throws IOException
309    */
310   private void importData(File hmmFile, AnnotatedCollectionI ac)
311           throws IOException
312   {
313     HMMFile file = new HMMFile(
314             new FileParse(hmmFile.getAbsolutePath(), DataSourceType.FILE));
315     SequenceI[] seqs = file.getSeqsAsArray();
316     SequenceI hmmSeq = seqs[0];
317     hmmSeq.createDatasetSequence();
318     if (ac instanceof SequenceGroup)
319     {
320       SequenceGroup grp = (SequenceGroup) ac;
321       hmmSeq.insertCharAt(0, ac.getStartRes(), '-');
322       hmmSeq.insertCharAt(ac.getEndRes() + 1,
323               alignment.getWidth() - ac.getEndRes() - 1, '-');
324       hmmSeq.updateHMMMapping();
325       SequenceI topSeq = grp.getSequencesInOrder(alignment)[0];
326       int topIndex = alignment.findIndex(topSeq);
327       alignment.insertSequenceAt(topIndex, hmmSeq);
328       ac.setSeqrep(hmmSeq);
329       grp.addSequence(hmmSeq, false);
330       grp.setHmmConsensus(hmmSeq);
331     }
332     else
333     {
334       alignment.insertSequenceAt(0, hmmSeq);
335       alignment.setHmmConsensus(hmmSeq);
336     }
337
338     AlignViewport viewport = af.getViewport();
339     if (viewport != null)
340     {
341       AlignmentPanel alignPanel = viewport.getAlignPanel();
342       viewport.alignmentChanged(alignPanel);
343       alignPanel.adjustAnnotationHeight();
344       viewport.updateSequenceIdColours();
345
346       if (alignPanel.alignFrame.getSelectedHMM() == null)
347       {
348         alignPanel.alignFrame.setSelectedHMMSequence(hmmSeq);
349       }
350     }
351   }
352 }