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