JAL-2629 ensure hmm consensus sequence set on alignment output
[jalview.git] / src / jalview / hmmer / HMMAlign.java
1 package jalview.hmmer;
2
3 import jalview.analysis.AlignmentSorter;
4 import jalview.datamodel.Alignment;
5 import jalview.datamodel.AlignmentI;
6 import jalview.datamodel.AlignmentOrder;
7 import jalview.datamodel.AlignmentView;
8 import jalview.datamodel.HiddenColumns;
9 import jalview.datamodel.HiddenMarkovModel;
10 import jalview.datamodel.SequenceI;
11 import jalview.gui.AlignFrame;
12 import jalview.gui.Desktop;
13 import jalview.gui.JvOptionPane;
14 import jalview.gui.SplitFrame;
15 import jalview.io.DataSourceType;
16 import jalview.io.StockholmFile;
17 import jalview.util.MessageManager;
18 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
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
27 import javax.swing.JInternalFrame;
28
29 public class HMMAlign extends HmmerCommand
30 {
31   static final String HMMALIGN = "hmmalign";
32
33   static final String ARG_TRIM = "--trim";
34
35   private final AlignmentI dataset;
36
37   /**
38    * Constructor for the HMMAlignThread
39    * 
40    * @param af
41    * @param args
42    */
43   public HMMAlign(AlignFrame af, List<ArgumentI> args)
44   {
45     super(af, args);
46     if (alignment.getDataset() != null)
47     {
48       dataset = alignment.getDataset();
49     }
50     else
51     {
52       dataset = null;
53     }
54   }
55
56   /**
57    * Runs the HMMAlignThread: the data on the alignment or group is exported,
58    * then the command is executed in the command line and then the data is
59    * imported and displayed in a new frame (if true). The command is executed
60    * for each segment of the alignment. Call this method directly to execute
61    * synchronously, or via start() in a new Thread for asynchronously.
62    */
63   @Override
64   public void run()
65   {
66     HiddenMarkovModel hmm = af.getSelectedHMM();
67     if (hmm == null)
68     {
69       System.err.println("Can't run hmmalign as no HMM profile selected");
70       return;
71     }
72
73     long msgId = System.currentTimeMillis();
74     af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
75             msgId);
76
77     AlignmentView msa = af.gatherSequencesForAlignment();
78     SequenceI[][] subAlignments = msa.getVisibleContigs(alignment.getGapCharacter());
79
80     List<AlignmentOrder> allOrders = new ArrayList<>();
81
82     SequenceI[][] allResults = new SequenceI[subAlignments.length][];
83     int job = 0;
84     for (SequenceI[] seqs : subAlignments)
85     {
86       Hashtable sequencesHash = stashSequences(seqs);
87       try
88       {
89         File modelFile = createTempFile("hmm", ".hmm");
90         File alignmentFile = createTempFile("output", ".sto");
91         File resultFile = createTempFile("input", ".sto");
92
93         exportStockholm(seqs, alignmentFile.getAbsoluteFile(), null);
94         exportHmm(hmm, modelFile.getAbsoluteFile());
95
96         boolean ran = runCommand(modelFile, alignmentFile, resultFile);
97         if (!ran)
98         {
99           JvOptionPane.showInternalMessageDialog(af,
100                   MessageManager.getString("warn.hmmalign_failed"));
101           return;
102         }
103
104         SequenceI[] result = importData(resultFile, allOrders);
105         recoverSequences(sequencesHash, result);
106         allResults[job] = result;
107         modelFile.delete();
108         alignmentFile.delete();
109         resultFile.delete();
110       } catch (IOException e)
111       {
112         e.printStackTrace();
113       }
114       job++;
115     }
116
117     String title = "hmmalign to " + hmm.getConsensusSequence().getName();
118     displayResults(allResults, allOrders, msa, title);
119
120     af.setProgressBar("", msgId);
121   }
122
123   /**
124    * Executes the hmmalign command and returns true if successful, false if an
125    * error is detected
126    * 
127    * @param modelFile
128    *          the HMM to align to
129    * @param alignmentFile
130    *          the sequences to align
131    * @param resultFile
132    *          the file to hold the results of alignment
133    * @return
134    * @throws IOException
135    */
136   private boolean runCommand(File modelFile, File alignmentFile,
137           File resultFile) throws IOException
138   {
139     String command = getCommandPath(HMMALIGN);
140     if (command == null)
141     {
142       return false;
143     }
144     List<String> args = new ArrayList<>();
145     args.add(command);
146
147     if (params != null)
148     {
149       for (ArgumentI arg : params)
150       {
151         String name = arg.getName();
152         if (MessageManager.getString("label.trim_termini").equals(name))
153         {
154           args.add(ARG_TRIM);
155         }
156       }
157     }
158     args.add("-o");
159     args.add(resultFile.getAbsolutePath());
160     args.add(modelFile.getAbsolutePath());
161     args.add(alignmentFile.getAbsolutePath());
162     
163     return runCommand(args);
164   }
165
166   /**
167    * Imports the data from the file holding the output of hmmalign
168    * 
169    * @param resultFile
170    * @param allOrders
171    *          a list of alignment orders to add to
172    * 
173    * @return
174    * @throws IOException
175    */
176   private SequenceI[] importData(File resultFile,
177           List<AlignmentOrder> allOrders) throws IOException
178   {
179     StockholmFile file = new StockholmFile(resultFile.getAbsolutePath(),
180             DataSourceType.FILE);
181     SequenceI[] result = file.getSeqsAsArray();
182     AlignmentOrder msaorder = new AlignmentOrder(result);
183     AlignmentSorter.recoverOrder(result);
184     allOrders.add(msaorder);
185
186     return result;
187   }
188
189   /**
190    * Displays the results of all 'jobs' in a new frame
191    * 
192    * @param allResults
193    * 
194    * @param allOrders
195    * @param msa
196    * @param title
197    */
198   private void displayResults(SequenceI[][] allResults,
199           List<AlignmentOrder> allOrders, AlignmentView msa, String title)
200   {
201     AlignmentOrder[] arrOrders = allOrders
202             .toArray(new AlignmentOrder[allOrders.size()]);
203     Object[] newview = msa.getUpdatedView(allResults, arrOrders,
204             alignment.getGapCharacter());
205     SequenceI[] seqs = (SequenceI[]) newview[0];
206     HiddenColumns hidden = (HiddenColumns) newview[1];
207     Alignment al = new Alignment(seqs);
208     al.setProperty("Alignment Program", "hmmalign");
209     if (dataset != null)
210     {
211       al.setDataset(dataset);
212     }
213
214     /*
215      * hack to ensure hmm set on alignment
216      */
217     if (al.getSequenceAt(0).isHMMConsensusSequence())
218     {
219       al.setHmmConsensus(al.getSequenceAt(0));
220     }
221
222     displayInNewFrame(al, allOrders, hidden, title);
223   }
224
225   /**
226    * Displays the results in a new frame
227    * 
228    * @param al
229    *          The alignment containing the results
230    * @param alorders
231    *          The order of the sequences in the alignment on which the jobs were
232    *          run
233    * @param hidden
234    *          Hidden columns in the previous alignment
235    * @param title
236    */
237   private void displayInNewFrame(AlignmentI al,
238           List<AlignmentOrder> alorders, HiddenColumns hidden, String title)
239   {
240     AlignFrame alignFrame = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
241             AlignFrame.DEFAULT_HEIGHT);
242     alignFrame.setTitle(title);
243
244     FeatureRendererSettings featureSettings = af.getFeatureRenderer()
245             .getSettings();
246     // initialise with same renderer settings as in parent alignframe.
247     alignFrame.getFeatureRenderer().transferSettings(featureSettings);
248
249     addSortByMenuItems(alignFrame, alorders);
250
251     // TODO: refactor retrieve and show as new splitFrame as Desktop method
252
253     /*
254      * If alignment was requested from one half of a SplitFrame, show in a
255      * SplitFrame with the other pane similarly aligned.
256      */
257     AlignFrame requestedBy = this.af;
258     if (requestedBy != null && requestedBy.getSplitViewContainer() != null
259             && requestedBy.getSplitViewContainer()
260                     .getComplement(requestedBy) != null)
261     {
262       AlignmentI complement = requestedBy.getSplitViewContainer()
263               .getComplement(requestedBy);
264       String complementTitle = requestedBy.getSplitViewContainer()
265               .getComplementTitle(requestedBy);
266       // becomes null if the alignment window was closed before the alignment
267       // job finished.
268       AlignmentI copyComplement = new Alignment(complement);
269       // todo should this be done by copy constructor?
270       copyComplement.setGapCharacter(complement.getGapCharacter());
271       // share the same dataset (and the mappings it holds)
272       copyComplement.setDataset(complement.getDataset());
273       copyComplement.alignAs(al);
274       if (copyComplement.getHeight() > 0)
275       {
276         alignFrame.setTitle(this.af.getTitle());
277         AlignFrame af2 = new AlignFrame(copyComplement,
278                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
279         af2.setTitle(complementTitle);
280         String linkedTitle = MessageManager
281                 .getString("label.linked_view_title");
282         JInternalFrame splitFrame = new SplitFrame(
283                 al.isNucleotide() ? alignFrame : af2, al.isNucleotide() ? af2 : alignFrame);
284         Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
285         return;
286       }
287     }
288
289     /*
290      * Not from SplitFrame, or failed to created a complementary alignment
291      */
292     Desktop.addInternalFrame(alignFrame, alignFrame.getTitle(), AlignFrame.DEFAULT_WIDTH,
293             AlignFrame.DEFAULT_HEIGHT);
294   }
295
296   /**
297    * Adds sort order options to the AlignFrame menus
298    * 
299    * @param alignFrame
300    * @param alorders
301    */
302   protected void addSortByMenuItems(AlignFrame alignFrame,
303           List<AlignmentOrder> alorders)
304   {
305     // update orders
306     if (alorders.size() == 1)
307     {
308       alignFrame.addSortByOrderMenuItem("hmmalign" + " Ordering", alorders.get(0));
309     }
310     else
311     {
312       // construct a non-redundant ordering set
313       List<String> names = new ArrayList<>();
314       for (int i = 0, l = alorders.size(); i < l; i++)
315       {
316         String orderName = " Region " + i;
317         int j = i + 1;
318
319         while (j < l)
320         {
321           if (alorders.get(i).equals(alorders.get(j)))
322           {
323             alorders.remove(j);
324             l--;
325             orderName += "," + j;
326           }
327           else
328           {
329             j++;
330           }
331         }
332
333         if (i == 0 && j == 1)
334         {
335           names.add("");
336         }
337         else
338         {
339           names.add(orderName);
340         }
341       }
342       for (int i = 0, l = alorders.size(); i < l; i++)
343       {
344         alignFrame.addSortByOrderMenuItem("hmmalign" + (names.get(i)) + " Ordering",
345                 alorders.get(i));
346       }
347     }
348   }
349 }