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