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