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