JAL-3899 Update usages of uniquify and deuniquify.
[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.datamodel.Alignment;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.AnnotatedCollectionI;
9 import jalview.datamodel.ResidueCount;
10 import jalview.datamodel.SequenceGroup;
11 import jalview.datamodel.SequenceI;
12 import jalview.gui.AlignFrame;
13 import jalview.gui.JvOptionPane;
14 import jalview.io.DataSourceType;
15 import jalview.io.FileParse;
16 import jalview.io.HMMFile;
17 import jalview.util.FileUtils;
18 import jalview.util.MessageManager;
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.List;
26 import java.util.Map;
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     if (params == null || params.isEmpty())
62     {
63       Cache.log.error("No parameters to HMMBuild!|");
64       return;
65     }
66
67     long msgID = System.currentTimeMillis();
68     af.setProgressBar(MessageManager.getString("status.running_hmmbuild"),
69             msgID);
70
71     AlignViewportI viewport = af.getViewport();
72     try
73     {
74       /*
75        * run hmmbuild for alignment and/or groups as selected
76        */
77       List<AnnotatedCollectionI> runBuildFor = parseParameters(viewport);
78
79       for (AnnotatedCollectionI grp : runBuildFor)
80       {
81         runHMMBuild(grp);
82       }
83     } finally
84     {
85       af.setProgressBar("", msgID);
86       viewport.alignmentChanged(af.alignPanel);
87       af.buildColourMenu(); // to enable HMMER colour schemes
88     }
89   }
90
91   /**
92    * Scans the parameters to determine whether to run hmmmbuild for the whole
93    * alignment or specified subgroup(s) or both
94    * 
95    * @param viewport
96    * @return
97    */
98   protected List<AnnotatedCollectionI> parseParameters(
99           AlignViewportI viewport)
100   {
101     List<AnnotatedCollectionI> runBuildFor = new ArrayList<>();
102     boolean foundArg = false;
103
104     for (ArgumentI arg : params)
105     {
106       String name = arg.getName();
107       if (MessageManager.getString("label.hmmbuild_for").equals(name))
108       {
109         foundArg = true;
110         String value = arg.getValue();
111
112         if (MessageManager.getString("label.alignment").equals(value))
113         {
114           runBuildFor.add(viewport.getAlignmentView(false)
115                   .getVisibleAlignment('-'));
116         }
117         else if (MessageManager.getString("label.groups_and_alignment")
118                 .equals(value))
119         {
120           runBuildFor.add(viewport.getAlignmentView(false)
121                   .getVisibleAlignment('-'));
122           runBuildFor.addAll(viewport.getAlignment().getGroups());
123         }
124         else if (MessageManager.getString("label.groups").equals(value))
125         {
126           runBuildFor.addAll(viewport.getAlignment().getGroups());
127         }
128         else if (MessageManager.getString("label.selected_group")
129                 .equals(value))
130         {
131           runBuildFor.add(viewport.getSelectionGroup());
132         }
133       }
134       else if (MessageManager.getString("label.use_reference")
135               .equals(name))
136       {
137         // todo disable this option if no RF annotation on alignment
138         if (!af.getViewport().hasReferenceAnnotation())
139         {
140           JvOptionPane.showInternalMessageDialog(af, MessageManager
141                   .getString("warn.no_reference_annotation"));
142           // return;
143         }
144       }
145     }
146
147     /*
148      * default is to build for the whole alignment
149      */
150     if (!foundArg)
151     {
152       runBuildFor.add(alignment);
153     }
154
155     return runBuildFor;
156   }
157
158   /**
159    * Runs hmmbuild on the given sequences (alignment or group)
160    * 
161    * @param grp
162    */
163   private void runHMMBuild(AnnotatedCollectionI ac)
164   {
165     File hmmFile = null;
166     File alignmentFile = null;
167     try
168     {
169       hmmFile = FileUtils.createTempFile("hmm", ".hmm");
170       alignmentFile = FileUtils.createTempFile("output", ".sto");
171
172       if (ac instanceof Alignment)
173       {
174         AlignmentI al = (Alignment) ac;
175         // todo pad gaps in an unaligned SequenceGroup as well?
176         if (!al.isAligned())
177         {
178           al.padGaps();
179         }
180       }
181
182       deleteHmmSequences(ac);
183
184       List<SequenceI> copy = new ArrayList<>();
185       if (ac instanceof Alignment)
186       {
187         copy.addAll(ac.getSequences());
188       }
189       else
190       {
191         SequenceI[] sel = ((SequenceGroup) ac)
192                                                 .getSelectionAsNewSequences((AlignmentI) ac.getContext());
193         for (SequenceI seq : sel)
194         {
195           if (seq != null)
196           {
197             copy.add(seq);
198           }
199         }
200       }
201       // TODO rather than copy alignment data we should anonymize in situ -
202       // export/File import could use anonymization hash to reinstate references
203       // at import level ?
204
205       SequenceI[] copyArray = copy.toArray(new SequenceI[copy.size()]);
206       Map<String, SequenceInfo> sequencesHash = stashSequences(copyArray);
207
208       exportStockholm(copyArray, alignmentFile, ac);
209
210       recoverSequences(sequencesHash, copy.toArray(new SequenceI[] {}));
211
212       boolean ran = runCommand(alignmentFile, hmmFile, ac);
213       if (!ran)
214       {
215         JvOptionPane.showInternalMessageDialog(af, MessageManager
216                 .formatMessage("warn.command_failed", "hmmbuild"));
217         return;
218       }
219       importData(hmmFile, ac);
220     } catch (Exception e)
221     {
222       e.printStackTrace();
223     } finally
224     {
225       if (hmmFile != null)
226       {
227         hmmFile.delete();
228       }
229       if (alignmentFile != null)
230       {
231         alignmentFile.delete();
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(getFilePath(hmmFile, true));
315     args.add(getFilePath(sequencesFile, true));
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     if (hmmFile.length() == 0L)
334     {
335       Cache.log.error("Error: hmmbuild produced empty hmm file");
336       return;
337     }
338
339     HMMFile file = new HMMFile(
340             new FileParse(hmmFile.getAbsolutePath(), DataSourceType.FILE));
341     SequenceI hmmSeq = file.getHMM().getConsensusSequence();
342
343
344
345     ResidueCount counts = new ResidueCount(alignment.getSequences());
346     hmmSeq.getHMM().setBackgroundFrequencies(counts);
347
348     if (hmmSeq == null)
349     {
350       // hmmbuild failure not detected earlier
351       return;
352     }
353
354     if (ac instanceof SequenceGroup)
355     {
356       SequenceGroup grp = (SequenceGroup) ac;
357       char gapChar = alignment.getGapCharacter();
358       hmmSeq.insertCharAt(0, ac.getStartRes(), gapChar);
359       hmmSeq.insertCharAt(ac.getEndRes() + 1,
360               alignment.getWidth() - ac.getEndRes() - 1, gapChar);
361       SequenceI topSeq = grp.getSequencesInOrder(alignment)[0];
362       int topIndex = alignment.findIndex(topSeq);
363       alignment.insertSequenceAt(topIndex, hmmSeq);
364       ac.setSeqrep(hmmSeq);
365       grp.addSequence(hmmSeq, false);
366     }
367     else
368     {
369       alignment.insertSequenceAt(0, hmmSeq);
370     }
371   }
372 }