52c2145b89a189d4ff45a43e5fef5ae2f703d204
[jalview.git] / src / jalview / ws / jws1 / JPredThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ws.jws1;
19
20 import java.util.*;
21
22 import jalview.analysis.*;
23 import jalview.bin.*;
24 import jalview.datamodel.*;
25 import jalview.gui.*;
26 import jalview.io.*;
27 import jalview.util.*;
28 import jalview.ws.AWsJob;
29 import jalview.ws.JobStateSummary;
30 import jalview.ws.WSClientI;
31 import vamsas.objects.simple.JpredResult;
32
33 class JPredThread extends JWS1Thread implements WSClientI
34 {
35   // TODO: put mapping between JPredJob input and input data here -
36   // JNetAnnotation adding is done after result parsing.
37   class JPredJob extends WSJob
38   {
39     // TODO: make JPredJob deal only with what was sent to and received from a
40     // JNet service
41     int[] predMap = null; // mapping from sequence(i) to the original
42
43     // sequence(predMap[i]) being predicted on
44
45     vamsas.objects.simple.Sequence sequence;
46
47     vamsas.objects.simple.Msfalignment msa;
48
49     java.util.Hashtable SequenceInfo = null;
50
51     int msaIndex = 0; // the position of the original sequence in the array of
52
53     // Sequences in the input object that this job holds a
54     // prediction for
55
56     /**
57      * 
58      * @return true if getResultSet will return a valid alignment and prediction
59      *         result.
60      */
61     public boolean hasResults()
62     {
63       if (subjobComplete && result != null && result.isFinished()
64               && ((JpredResult) result).getPredfile() != null
65               && ((JpredResult) result).getAligfile() != null)
66       {
67         return true;
68       }
69       return false;
70     }
71
72     public boolean hasValidInput()
73     {
74       if (sequence != null)
75       {
76         return true;
77       }
78       return false;
79     }
80
81     /**
82      * 
83      * @return null or Object[] { annotated alignment for this prediction,
84      *         ColumnSelection for this prediction} or null if no results
85      *         available.
86      * @throws Exception
87      */
88     public Object[] getResultSet() throws Exception
89     {
90       if (result == null || !result.isFinished())
91       {
92         return null;
93       }
94       Alignment al = null;
95       ColumnSelection alcsel = null;
96       int FirstSeq = -1; // the position of the query sequence in Alignment al
97
98       JpredResult result = (JpredResult) this.result;
99
100       jalview.bin.Cache.log.debug("Parsing output from JNet job.");
101       // JPredFile prediction = new JPredFile("C:/JalviewX/files/jpred.txt",
102       // "File");
103       jalview.io.JPredFile prediction = new jalview.io.JPredFile(
104               result.getPredfile(), "Paste");
105       SequenceI[] preds = prediction.getSeqsAsArray();
106       jalview.bin.Cache.log.debug("Got prediction profile.");
107
108       if ((this.msa != null) && (result.getAligfile() != null))
109       {
110         jalview.bin.Cache.log.debug("Getting associated alignment.");
111         // we ignore the returned alignment if we only predicted on a single
112         // sequence
113         String format = new jalview.io.IdentifyFile().Identify(
114                 result.getAligfile(), "Paste");
115
116         if (jalview.io.FormatAdapter.isValidFormat(format))
117         {
118           SequenceI sqs[];
119           if (predMap != null)
120           {
121             Object[] alandcolsel = input
122                     .getAlignmentAndColumnSelection(getGapChar());
123             sqs = (SequenceI[]) alandcolsel[0];
124             al = new Alignment(sqs);
125             alcsel = (ColumnSelection) alandcolsel[1];
126           }
127           else
128           {
129             al = new FormatAdapter().readFile(result.getAligfile(),
130                     "Paste", format);
131             sqs = new SequenceI[al.getHeight()];
132
133             for (int i = 0, j = al.getHeight(); i < j; i++)
134             {
135               sqs[i] = al.getSequenceAt(i);
136             }
137             if (!jalview.analysis.SeqsetUtils.deuniquify(
138                     (Hashtable) SequenceInfo, sqs))
139             {
140               throw (new Exception(
141                       "Couldn't recover sequence properties for alignment."));
142             }
143           }
144           FirstSeq = 0;
145           al.setDataset(null);
146
147           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
148                   FirstSeq, false, predMap);
149
150         }
151         else
152         {
153           throw (new Exception("Unknown format " + format
154                   + " for file : \n" + result.getAligfile()));
155         }
156       }
157       else
158       {
159         al = new Alignment(preds);
160         FirstSeq = prediction.getQuerySeqPosition();
161         if (predMap != null)
162         {
163           char gc = getGapChar();
164           SequenceI[] sqs = (SequenceI[]) ((java.lang.Object[]) input
165                   .getAlignmentAndColumnSelection(gc))[0];
166           if (this.msaIndex >= sqs.length)
167           {
168             throw new Error(
169                     "Implementation Error! Invalid msaIndex for JPredJob on parent MSA input object!");
170           }
171
172           // ///
173           // Uses RemoveGapsCommand
174           // ///
175           new jalview.commands.RemoveGapsCommand("Remove Gaps",
176                   new SequenceI[]
177                   { sqs[msaIndex] }, currentView);
178
179           SequenceI profileseq = al.getSequenceAt(FirstSeq);
180           profileseq.setSequence(sqs[msaIndex].getSequenceAsString());
181         }
182
183         if (!jalview.analysis.SeqsetUtils.SeqCharacterUnhash(
184                 al.getSequenceAt(FirstSeq), SequenceInfo))
185         {
186           throw (new Exception(
187                   "Couldn't recover sequence properties for JNet Query sequence!"));
188         }
189         else
190         {
191           al.setDataset(null);
192           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
193                   FirstSeq, true, predMap);
194           SequenceI profileseq = al.getSequenceAt(0); // this includes any gaps.
195           alignToProfileSeq(al, profileseq);
196           if (predMap != null)
197           {
198             // Adjust input view for gaps
199             // propagate insertions into profile
200             alcsel = ColumnSelection.propagateInsertions(profileseq, al,
201                     input);
202           }
203         }
204       }
205       return new Object[]
206       { al, alcsel }; // , FirstSeq, noMsa};
207     }
208
209     /**
210      * Given an alignment where all other sequences except profileseq are
211      * aligned to the ungapped profileseq, insert gaps in the other sequences to
212      * realign them with the residues in profileseq
213      * 
214      * @param al
215      * @param profileseq
216      */
217     private void alignToProfileSeq(Alignment al, SequenceI profileseq)
218     {
219       char gc = al.getGapCharacter();
220       int[] gapMap = profileseq.gapMap();
221       // insert gaps into profile
222       for (int lp = 0, r = 0; r < gapMap.length; r++)
223       {
224         if (gapMap[r] - lp > 1)
225         {
226           StringBuffer sb = new StringBuffer();
227           for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
228           {
229             sb.append(gc);
230           }
231           for (int s = 1, ns = al.getHeight(); s < ns; s++)
232           {
233             String sq = al.getSequenceAt(s).getSequenceAsString();
234             int diff = gapMap[r] - sq.length();
235             if (diff > 0)
236             {
237               // pad gaps
238               sq = sq + sb;
239               while ((diff = gapMap[r] - sq.length()) > 0)
240               {
241                 sq = sq
242                         + ((diff >= sb.length()) ? sb.toString() : sb
243                                 .substring(0, diff));
244               }
245               al.getSequenceAt(s).setSequence(sq);
246             }
247             else
248             {
249               al.getSequenceAt(s).setSequence(
250                       sq.substring(0, gapMap[r]) + sb.toString()
251                               + sq.substring(gapMap[r]));
252             }
253           }
254         }
255         lp = gapMap[r];
256       }
257     }
258
259     public JPredJob(Hashtable SequenceInfo, SequenceI seq, int[] delMap)
260     {
261       super();
262       this.predMap = delMap;
263       String sq = AlignSeq.extractGaps(Comparison.GapChars,
264               seq.getSequenceAsString());
265       if (sq.length() >= 20)
266       {
267         this.SequenceInfo = SequenceInfo;
268         sequence = new vamsas.objects.simple.Sequence();
269         sequence.setId(seq.getName());
270         sequence.setSeq(sq);
271       }
272       else
273       {
274         errorMessage = "Sequence is too short to predict with JPred - need at least 20 amino acids.";
275       }
276     }
277
278     public JPredJob(Hashtable SequenceInfo, SequenceI[] msf, int[] delMap)
279     {
280       this(SequenceInfo, msf[0], delMap);
281       if (sequence != null)
282       {
283         if (msf.length > 1)
284         {
285           msa = new vamsas.objects.simple.Msfalignment();
286           jalview.io.PileUpfile pileup = new jalview.io.PileUpfile();
287           msa.setMsf(pileup.print(msf));
288         }
289       }
290     }
291
292     String errorMessage = "";
293
294     public String getValidationMessages()
295     {
296       return errorMessage + "\n";
297     }
298   }
299
300   ext.vamsas.Jpred server;
301
302   String altitle = "";
303
304   JPredThread(WebserviceInfo wsinfo, String altitle,
305           ext.vamsas.Jpred server, String wsurl, AlignmentView alview,
306           AlignFrame alframe)
307   {
308     super(alframe, wsinfo, alview, wsurl);
309     this.altitle = altitle;
310     this.server = server;
311   }
312
313   JPredThread(WebserviceInfo wsinfo, String altitle,
314           ext.vamsas.Jpred server, String wsurl, Hashtable SequenceInfo,
315           SequenceI seq, int[] delMap, AlignmentView alview,
316           AlignFrame alframe)
317   {
318     this(wsinfo, altitle, server, wsurl, alview, alframe);
319     JPredJob job = new JPredJob(SequenceInfo, seq, delMap);
320     if (job.hasValidInput())
321     {
322       OutputHeader = wsInfo.getProgressText();
323       jobs = new WSJob[]
324       { job };
325       job.setJobnum(0);
326     }
327     else
328     {
329       wsInfo.appendProgressText(job.getValidationMessages());
330     }
331   }
332
333   JPredThread(WebserviceInfo wsinfo, String altitle,
334           ext.vamsas.Jpred server, Hashtable SequenceInfo, SequenceI[] msf,
335           int[] delMap, AlignmentView alview, AlignFrame alframe,
336           String wsurl)
337   {
338     this(wsinfo, altitle, server, wsurl, alview, alframe);
339     JPredJob job = new JPredJob(SequenceInfo, msf, delMap);
340     if (job.hasValidInput())
341     {
342       jobs = new WSJob[]
343       { job };
344       OutputHeader = wsInfo.getProgressText();
345       job.setJobnum(0);
346     }
347     else
348     {
349       wsInfo.appendProgressText(job.getValidationMessages());
350     }
351   }
352
353   public void StartJob(AWsJob j)
354   {
355     if (!(j instanceof JPredJob))
356     {
357       throw new Error(
358               "Implementation error - StartJob(JpredJob) called on "
359                       + j.getClass());
360     }
361     try
362     {
363       JPredJob job = (JPredJob) j;
364       if (job.msa != null)
365       {
366         job.setJobId(server.predictOnMsa(job.msa));
367       }
368       else if (job.sequence != null)
369       {
370         job.setJobId(server.predict(job.sequence)); // debug like : job.jobId =
371         // "/jobs/www-jpred/jp_Yatat29";//
372       }
373
374       if (job.getJobId() != null)
375       {
376         if (job.getJobId().startsWith("Broken"))
377         {
378           job.result = (vamsas.objects.simple.Result) new JpredResult();
379           job.result.setInvalid(true);
380           job.result.setStatus("Submission " + job.getJobId());
381           throw new Exception(job.getJobId());
382         }
383         else
384         {
385           job.setSubmitted(true);
386           job.setSubjobComplete(false);
387           Cache.log.info(WsUrl + " Job Id '" + job.getJobId() + "'");
388         }
389       }
390       else
391       {
392         throw new Exception("Server timed out - try again later\n");
393       }
394     } catch (Exception e)
395     {
396       // kill the whole job.
397       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
398       if (e.getMessage().indexOf("Exception") > -1)
399       {
400         wsInfo.setStatus(j.getJobnum(),
401                 WebserviceInfo.STATE_STOPPED_SERVERERROR);
402         wsInfo.setProgressText(
403                 j.getJobnum(),
404                 "Failed to submit the prediction. (Just close the window)\n"
405                         + "It is most likely that there is a problem with the server.\n");
406         System.err
407                 .println("JPredWS Client: Failed to submit the prediction. Quite possibly because of a server error - see below)\n"
408                         + e.getMessage() + "\n");
409
410         jalview.bin.Cache.log.warn("Server Exception", e);
411       }
412       else
413       {
414         wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
415         // JBPNote - this could be a popup informing the user of the problem.
416         wsInfo.appendProgressText(j.getJobnum(),
417                 "Failed to submit the prediction:\n" + e.getMessage()
418                         + wsInfo.getProgressText());
419
420         jalview.bin.Cache.log.debug(
421                 "Failed Submission of job " + j.getJobnum(), e);
422
423       }
424       j.setAllowedServerExceptions(-1);
425       j.setSubjobComplete(true);
426     }
427   }
428
429   public void parseResult()
430   {
431     int results = 0; // number of result sets received
432     JobStateSummary finalState = new JobStateSummary();
433     try
434     {
435       for (int j = 0; j < jobs.length; j++)
436       {
437         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
438         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
439                 && jobs[j].hasResults())
440         {
441           results++;
442         }
443       }
444     } catch (Exception ex)
445     {
446
447       Cache.log.error("Unexpected exception when processing results for "
448               + altitle, ex);
449       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
450     }
451     if (results > 0)
452     {
453       wsInfo.showResultsNewFrame
454               .addActionListener(new java.awt.event.ActionListener()
455               {
456                 public void actionPerformed(java.awt.event.ActionEvent evt)
457                 {
458                   displayResults(true);
459                 }
460               });
461       wsInfo.mergeResults
462               .addActionListener(new java.awt.event.ActionListener()
463               {
464                 public void actionPerformed(java.awt.event.ActionEvent evt)
465                 {
466                   displayResults(false);
467                 }
468               });
469       wsInfo.setResultsReady();
470     }
471     else
472     {
473       wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
474       wsInfo.appendInfoText("No jobs ran.");
475       wsInfo.setFinishedNoResults();
476     }
477   }
478
479   void displayResults(boolean newWindow)
480   {
481     // TODO: cope with multiple subjobs.
482     if (jobs != null)
483     {
484       Object[] res = null;
485       boolean msa = false;
486       for (int jn = 0; jn < jobs.length; jn++)
487       {
488         Object[] jobres = null;
489         JPredJob j = (JPredJob) jobs[jn];
490
491         if (j.hasResults())
492         {
493           // hack - we only deal with all single seuqence predictions or all
494           // profile predictions
495           msa = (j.msa != null) ? true : msa;
496           try
497           {
498             jalview.bin.Cache.log.debug("Parsing output of job " + jn);
499             jobres = j.getResultSet();
500             jalview.bin.Cache.log.debug("Finished parsing output.");
501             if (jobs.length == 1)
502             {
503               res = jobres;
504             }
505             else
506             {
507               // do merge with other job results
508               throw new Error(
509                       "Multiple JNet subjob merging not yet implemented.");
510             }
511           } catch (Exception e)
512           {
513             jalview.bin.Cache.log.error(
514                     "JNet Client: JPred Annotation Parse Error", e);
515             wsInfo.setStatus(j.getJobnum(),
516                     WebserviceInfo.STATE_STOPPED_ERROR);
517             wsInfo.appendProgressText(j.getJobnum(), OutputHeader + "\n"
518                     + j.result.getStatus()
519                     + "\nInvalid JNet job result data!\n" + e.getMessage());
520             j.result.setBroken(true);
521           }
522         }
523       }
524
525       if (res != null)
526       {
527         if (newWindow)
528         {
529           AlignFrame af;
530           if (input == null)
531           {
532             if (res[1] != null)
533             {
534               af = new AlignFrame((Alignment) res[0],
535                       (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
536                       AlignFrame.DEFAULT_HEIGHT);
537             }
538             else
539             {
540               af = new AlignFrame((Alignment) res[0],
541                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
542             }
543           }
544           else
545           {
546             /*
547              * java.lang.Object[] alandcolsel =
548              * input.getAlignmentAndColumnSelection
549              * (alignFrame.getViewport().getGapCharacter()); if
550              * (((SequenceI[])alandcolsel[0])[0].getLength()!=res.getWidth()) {
551              * if (msa) { throw new Error("Implementation Error! ColumnSelection
552              * from input alignment will not map to result alignment!"); } } if
553              * (!msa) { // update hidden regions to account for loss of gaps in
554              * profile. - if any // gapMap returns insert list, interpreted as
555              * delete list by pruneDeletions //((ColumnSelection)
556              * alandcolsel[1]).pruneDeletions(ShiftList.parseMap(((SequenceI[])
557              * alandcolsel[0])[0].gapMap())); }
558              */
559
560             af = new AlignFrame((Alignment) res[0],
561                     (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
562                     AlignFrame.DEFAULT_HEIGHT);
563           }
564           Desktop.addInternalFrame(af, altitle, AlignFrame.DEFAULT_WIDTH,
565                   AlignFrame.DEFAULT_HEIGHT);
566         }
567         else
568         {
569           Cache.log.info("Append results onto existing alignment.");
570         }
571       }
572     }
573   }
574
575   public void pollJob(AWsJob job) throws Exception
576   {
577     ((JPredJob) job).result = server.getresult(job.getJobId());
578   }
579
580   public boolean isCancellable()
581   {
582     return false;
583   }
584
585   public void cancelJob()
586   {
587     throw new Error("Implementation error!");
588   }
589
590   public boolean canMergeResults()
591   {
592     return false;
593   }
594
595 }