JAL-2629 hmmsearch # of new sequences now only displayed when specified
[jalview.git] / src / jalview / hmmer / HMMSearch.java
1 package jalview.hmmer;
2
3 import jalview.bin.Cache;
4 import jalview.datamodel.Alignment;
5 import jalview.datamodel.AlignmentAnnotation;
6 import jalview.datamodel.AlignmentI;
7 import jalview.datamodel.Annotation;
8 import jalview.datamodel.HiddenMarkovModel;
9 import jalview.datamodel.SequenceI;
10 import jalview.gui.AlignFrame;
11 import jalview.gui.Desktop;
12 import jalview.gui.JvOptionPane;
13 import jalview.io.DataSourceType;
14 import jalview.io.FileParse;
15 import jalview.io.StockholmFile;
16 import jalview.util.FileUtils;
17 import jalview.util.MessageManager;
18 import jalview.ws.params.ArgumentI;
19 import jalview.ws.params.simple.BooleanOption;
20 import jalview.ws.params.simple.Option;
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileReader;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.Hashtable;
29 import java.util.List;
30 import java.util.Scanner;
31
32 import javax.swing.JOptionPane;
33
34 public class HMMSearch extends HmmerCommand
35 {
36   static final String HMMSEARCH = "hmmsearch";
37
38   boolean realign = false;
39
40   boolean trim = false;
41
42   boolean returnNoOfNewSeqs = false;
43
44   int seqsToReturn = Integer.MAX_VALUE;
45
46   boolean searchAlignment = true;
47
48   SequenceI[] seqs;
49
50   private String databaseName;
51
52   Hashtable sequencesHash;
53
54   /**
55    * Constructor for the HMMSearchThread
56    * 
57    * @param af
58    */
59   public HMMSearch(AlignFrame af, List<ArgumentI> args)
60   {
61     super(af, args);
62   }
63
64   /**
65    * Runs the HMMSearchThread: the data on the alignment or group is exported,
66    * then the command is executed in the command line and then the data is
67    * imported and displayed in a new frame. Call this method directly to execute
68    * synchronously, or via start() in a new Thread for asynchronously.
69    */
70   @Override
71   public void run()
72   {
73     HiddenMarkovModel hmm = getHmmProfile();
74     if (hmm == null)
75     {
76       // shouldn't happen if we got this far
77       Cache.log.error("Error: no hmm for hmmsearch");
78       return;
79     }
80
81     SequenceI hmmSeq = hmm.getConsensusSequence();
82     long msgId = System.currentTimeMillis();
83     af.setProgressBar(MessageManager.getString("status.running_search"),
84             msgId);
85
86     try
87     {
88       File hmmFile = FileUtils.createTempFile("hmm", ".hmm");
89       File hitsAlignmentFile = FileUtils.createTempFile("hitAlignment",
90               ".sto");
91       File searchOutputFile = FileUtils.createTempFile("searchOutput",
92               ".sto");
93
94       exportHmm(hmm, hmmFile.getAbsoluteFile());
95
96       boolean ran = runCommand(searchOutputFile, hitsAlignmentFile, hmmFile);
97       if (!ran)
98       {
99         JvOptionPane.showInternalMessageDialog(af, MessageManager
100                 .formatMessage("warn.command_failed", "hmmsearch"));
101         return;
102       }
103
104       importData(hmmSeq, hitsAlignmentFile, hmmFile, searchOutputFile);
105       // TODO make realignment of search results a step at this level
106       // and make it conditional on this.realign
107     } catch (IOException | InterruptedException e)
108     {
109       e.printStackTrace();
110     }
111     finally
112     {
113       af.setProgressBar("", msgId);
114     }
115   }
116
117   /**
118    * Executes an hmmsearch with the given hmm as input. The database to be
119    * searched is a local file as specified by the 'Database' parameter, or the
120    * current alignment (written to file) if none is specified.
121    * 
122    * @param searchOutputFile
123    * @param hitsAlignmentFile
124    * @param hmmFile
125    * 
126    * @return
127    * @throws IOException
128    */
129   private boolean runCommand(File searchOutputFile, File hitsAlignmentFile,
130           File hmmFile) throws IOException
131   {
132     String command = getCommandPath(HMMSEARCH);
133     if (command == null)
134     {
135       return false;
136     }
137
138     List<String> args = new ArrayList<>();
139     args.add(command);
140     buildArguments(args, searchOutputFile, hitsAlignmentFile, hmmFile);
141
142     return runCommand(args);
143   }
144
145   /**
146    * Appends command line arguments to the given list, to specify input and
147    * output files for the search, and any additional options that may have been
148    * passed from the parameters dialog
149    * 
150    * @param args
151    * @param searchOutputFile
152    * @param hitsAlignmentFile
153    * @param hmmFile
154    * @throws IOException
155    */
156   protected void buildArguments(List<String> args, File searchOutputFile,
157           File hitsAlignmentFile, File hmmFile) throws IOException
158   {
159     args.add("-o");
160     args.add(getFilePath(searchOutputFile, true));
161     args.add("-A");
162     args.add(getFilePath(hitsAlignmentFile, true));
163
164     boolean dbFound = false;
165     String dbPath = "";
166     File databaseFile = null;
167
168     boolean useEvalueCutoff = false;
169     boolean useScoreCutoff = false;
170     String seqEvalueCutoff = null;
171     String domEvalueCutoff = null;
172     String seqScoreCutoff = null;
173     String domScoreCutoff = null;
174     databaseName = "Alignment";
175
176     if (params != null)
177     {
178       for (ArgumentI arg : params)
179       {
180         String name = arg.getName();
181         if (MessageManager.getString(NUMBER_OF_RESULTS_KEY)
182                 .equals(name))
183         {
184           seqsToReturn = Integer.parseInt(arg.getValue());
185         }
186         else if (MessageManager.getString(AUTO_ALIGN_SEQS_KEY)
187                 .equals(name))
188         {
189           realign = true;
190         }
191         else if (MessageManager.getString(USE_ACCESSIONS_KEY)
192                 .equals(name))
193         {
194           args.add("--acc");
195         }
196         else if (MessageManager.getString(REPORTING_CUTOFF_KEY)
197                 .equals(name))
198         {
199           if (CUTOFF_EVALUE.equals(arg.getValue()))
200           {
201             useEvalueCutoff = true;
202           }
203           else if (CUTOFF_SCORE.equals(arg.getValue()))
204           {
205             useScoreCutoff = true;
206           }
207         }
208         else if (MessageManager.getString(SEQ_EVALUE_KEY).equals(name))
209         {
210           seqEvalueCutoff = arg.getValue();
211         }
212         else if (MessageManager.getString(SEQ_SCORE_KEY).equals(name))
213         {
214           seqScoreCutoff = arg.getValue();
215         }
216         else if (MessageManager.getString(DOM_EVALUE_KEY)
217                 .equals(name))
218         {
219           domEvalueCutoff = arg.getValue();
220         }
221         else if (MessageManager.getString(DOM_SCORE_KEY).equals(name))
222         {
223           domScoreCutoff = arg.getValue();
224         }
225         else if (MessageManager.getString(TRIM_TERMINI_KEY)
226                 .equals(name))
227         {
228           trim = true;
229         }
230         else if (MessageManager.getString(DATABASE_KEY).equals(name))
231         {
232           dbFound = true;
233           dbPath = arg.getValue();
234           if (!MessageManager.getString(THIS_ALIGNMENT_KEY)
235                   .equals(dbPath))
236           {
237             int pos = dbPath.lastIndexOf(File.separator);
238             databaseName = dbPath.substring(pos + 1);
239             databaseFile = new File(dbPath);
240           }
241           searchAlignment = false;
242         }
243         else if (MessageManager.getString(RETURN_N_NEW_SEQ).equals(name))
244         {
245           returnNoOfNewSeqs = true;
246         }
247       }
248     }
249
250     if (useEvalueCutoff)
251     {
252       args.add("-E");
253       args.add(seqEvalueCutoff);
254       args.add("--domE");
255       args.add(domEvalueCutoff);
256     }
257     else if (useScoreCutoff)
258     {
259       args.add("-T");
260       args.add(seqScoreCutoff);
261       args.add("--domT");
262       args.add(domScoreCutoff);
263     }
264
265 //    if (!dbFound || MessageManager.getString(THIS_ALIGNMENT_KEY)
266 //            .equals(dbPath))
267       if (searchAlignment)
268     {
269       /*
270        * no external database specified for search, so
271        * export current alignment as 'database' to search,
272        * excluding any HMM consensus sequences it contains
273        */
274       databaseFile = FileUtils.createTempFile("database", ".sto");
275       AlignmentI al = af.getViewport().getAlignment();
276       AlignmentI copy = new Alignment(al);
277       deleteHmmSequences(copy);
278
279       sequencesHash = stashSequences(copy.getSequencesArray());
280
281       exportStockholm(copy.getSequencesArray(), databaseFile, null);
282
283     }
284
285     args.add(getFilePath(hmmFile, true));
286     args.add(getFilePath(databaseFile, true));
287   }
288
289   /**
290    * Imports the data from the temporary file to which the output of hmmsearch
291    * was directed. The results are optionally realigned using hmmalign.
292    * 
293    * @param hmmSeq
294    */
295   private void importData(SequenceI hmmSeq, File inputAlignmentTemp,
296           File hmmTemp, File searchOutputFile)
297           throws IOException, InterruptedException
298   {
299     BufferedReader br = new BufferedReader(
300             new FileReader(inputAlignmentTemp));
301     try
302     {
303       if (br.readLine() == null)
304       {
305         JOptionPane.showMessageDialog(af,
306                 MessageManager.getString("label.no_sequences_found"));
307         return;
308       }
309       StockholmFile file = new StockholmFile(new FileParse(
310               inputAlignmentTemp.getAbsolutePath(), DataSourceType.FILE));
311       seqs = file.getSeqsAsArray();
312
313       if (searchAlignment)
314       {
315         recoverSequences(sequencesHash, seqs);
316       }
317
318       // look for PP cons and ref seq in alignment only annotation
319       AlignmentAnnotation modelpos = null, ppcons = null;
320       for (AlignmentAnnotation aa : file.getAnnotations())
321       {
322         if (aa.sequenceRef == null)
323         {
324           if (aa.label.equals("Reference Positions")) // RF feature type in
325                                                       // stockholm parser
326           {
327             modelpos = aa;
328           }
329           if (aa.label.equals("Posterior Probability"))
330           {
331             ppcons = aa;
332           }
333         }
334       }
335       readTable(searchOutputFile);
336
337       int seqCount = Math.min(seqs.length, seqsToReturn);
338       SequenceI[] hmmAndSeqs = new SequenceI[seqCount + 1];
339       hmmSeq = hmmSeq.deriveSequence(); // otherwise all bad things happen
340       hmmAndSeqs[0] = hmmSeq;
341       System.arraycopy(seqs, 0, hmmAndSeqs, 1, seqCount);
342       if (modelpos != null)
343       {
344         // TODO need - get ungapped sequence method
345         hmmSeq.setSequence(
346                 hmmSeq.getDatasetSequence().getSequenceAsString());
347         Annotation[] refpos = modelpos.annotations;
348         // insert gaps to match with refseq positions
349         int gc = 0, lcol = 0;
350         for (int c = 0; c < refpos.length; c++)
351         {
352           if (refpos[c] != null && ("x".equals(refpos[c].displayCharacter)))
353           {
354             if (gc > 0)
355             {
356               hmmSeq.insertCharAt(lcol + 1, gc, '-');
357             }
358             gc = 0;
359             lcol = c;
360           }
361           else
362           {
363             gc++;
364           }
365         }
366       }
367
368       if (realign)
369       {
370         realignResults(hmmAndSeqs);
371       }
372       else
373       {
374         AlignmentI al = new Alignment(hmmAndSeqs);
375         if (ppcons != null)
376         {
377           al.addAnnotation(ppcons);
378         }
379         if (modelpos != null)
380         {
381           al.addAnnotation(modelpos);
382         }
383         AlignFrame alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
384                 AlignFrame.DEFAULT_HEIGHT);
385         String ttl = "hmmSearch of " + databaseName + " using "
386                 + hmmSeq.getName();
387         Desktop.addInternalFrame(alignFrame, ttl, AlignFrame.DEFAULT_WIDTH,
388                 AlignFrame.DEFAULT_HEIGHT);
389
390         if (returnNoOfNewSeqs)
391         {
392           int nNew = checkForNewSequences();
393           JvOptionPane.showMessageDialog(af.alignPanel, nNew + " "
394                   + MessageManager.getString("label.new_returned"));
395         }
396
397       }
398
399
400       hmmTemp.delete();
401       inputAlignmentTemp.delete();
402       searchOutputFile.delete();
403     } finally
404     {
405       if (br != null)
406       {
407         br.close();
408       }
409     }
410   }
411
412   private int checkForNewSequences()
413   {
414     int nNew = seqs.length;
415
416     for (SequenceI resultSeq : seqs)
417     {
418       for (SequenceI aliSeq : alignment.getSequencesArray())
419       {
420         if (resultSeq.getName().equals(aliSeq.getName()))
421         {
422           nNew--;
423           break;
424         }
425       }
426     }
427
428     return nNew;
429
430   }
431
432   /**
433    * Realigns the given sequences using hmmalign, to the HMM profile sequence
434    * which is the first in the array, and opens the results in a new frame
435    * 
436    * @param hmmAndSeqs
437    */
438   protected void realignResults(SequenceI[] hmmAndSeqs)
439   {
440     /*
441      * and align the search results to the HMM profile
442      */
443     AlignmentI al = new Alignment(hmmAndSeqs);
444     AlignFrame frame = new AlignFrame(al, 1, 1);
445     List<ArgumentI> alignArgs = new ArrayList<>();
446     String alignTo = hmmAndSeqs[0].getName();
447     List<String> options = Collections.singletonList(alignTo);
448     Option option = new Option(MessageManager.getString("label.use_hmm"),
449             "", true, alignTo, alignTo, options, null);
450     alignArgs.add(option);
451     if (trim)
452     {
453       alignArgs.add(new BooleanOption(
454               MessageManager.getString(TRIM_TERMINI_KEY),
455               MessageManager.getString("label.trim_termini_desc"), true,
456               true, true, null));
457     }
458     HmmerCommand hmmalign = new HMMAlign(frame, alignArgs);
459     hmmalign.run();
460
461     if (returnNoOfNewSeqs)
462     {
463       int nNew = checkForNewSequences();
464       JvOptionPane.showMessageDialog(frame.alignPanel,
465               nNew + " " + MessageManager.getString("label.new_returned"));
466     }
467   }
468
469   /**
470    * Reads in the scores table output by hmmsearch and adds annotation to
471    * sequences for E-value and bit score
472    * 
473    * @param inputTableTemp
474    * @throws IOException
475    */
476   void readTable(File inputTableTemp) throws IOException
477   {
478     BufferedReader br = new BufferedReader(new FileReader(inputTableTemp));
479     String line = "";
480     while (!line.startsWith("Query:"))
481     {
482       line = br.readLine();
483     }
484     while (!line.contains("-------"))
485     {
486       line = br.readLine();
487     }
488     line = br.readLine();
489
490     int index = 0;
491     while (!"  ------ inclusion threshold ------".equals(line)
492             && !"".equals(line))
493     {
494       SequenceI seq = seqs[index];
495       Scanner scanner = new Scanner(line);
496       String evalue = scanner.next();
497       String score = scanner.next();
498       addScoreAnnotations(evalue, score, seq);
499       scanner.close();
500       line = br.readLine();
501       index++;
502     }
503
504     br.close();
505   }
506
507
508   protected void addScoreAnnotations(String eValue, String bitScore,
509           SequenceI seq)
510   {
511     String label = "Search Scores";
512     String description = "Full sequence bit score and E-Value";
513
514     try
515     {
516       AlignmentAnnotation annot = new AlignmentAnnotation(label,
517               description, null);
518
519       annot.label = label;
520       annot.description = description;
521
522       annot.setCalcId(HMMSEARCH);
523
524       double dEValue = Double.parseDouble(eValue);
525       annot.setEValue(dEValue);
526
527       double dBitScore = Double.parseDouble(bitScore);
528       annot.setBitScore(dBitScore);
529
530       annot.setSequenceRef(seq);
531       seq.addAlignmentAnnotation(annot);
532     } catch (NumberFormatException e)
533     {
534       System.err.println("Error parsing " + label + " from " + eValue
535               + " & " + bitScore);
536     }
537   }
538
539 }