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