JAL-1379 refactor Jws2 service client so dynamic services can be of any type of JABA...
[jalview.git] / src / jalview / ws / jws2 / RNAalifoldClient.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.jws2;
22
23 import jalview.api.AlignCalcWorkerI;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.Annotation;
26 import jalview.gui.AlignFrame;
27 import jalview.ws.jws2.dm.AAConSettings;
28 import jalview.ws.jws2.jabaws2.Jws2Instance;
29 import jalview.ws.params.WsParamSetI;
30 import jalview.ws.uimodel.AlignAnalysisUIText;
31
32 import java.text.MessageFormat;
33 import java.util.ArrayList;
34 import java.util.LinkedHashMap;
35 import java.util.List;
36 import java.util.TreeSet;
37 import java.util.regex.Pattern;
38
39 import compbio.data.sequence.RNAStructReader.AlifoldResult;
40 import compbio.data.sequence.FastaSequence;
41 import compbio.data.sequence.RNAStructScoreManager;
42 import compbio.data.sequence.Range;
43 import compbio.data.sequence.Score;
44 import compbio.metadata.Argument;
45
46 /**
47  * Client for the JABA RNA Alifold Service
48  * 
49  * @author daluke - Daniel Barton
50  * 
51  */
52
53 public class RNAalifoldClient extends JabawsCalcWorker implements
54         AlignCalcWorkerI
55 {
56
57   String methodName;
58
59   AlignFrame af;
60
61   // keeps track of whether the RNAalifold result includes base contact
62   // probabilities
63   boolean bpScores;
64
65   public RNAalifoldClient(Jws2Instance sh, AlignFrame alignFrame,
66           WsParamSetI preset, List<Argument> paramset)
67   {
68     super(sh, alignFrame, preset, paramset);
69
70     // if (arguments == null)
71     // arguments = new ArrayList<Argument>();
72
73     af = alignFrame;
74     methodName = sh.serviceType;
75     alignedSeqs = true;
76     submitGaps = true;
77     nucleotidesAllowed = true;
78     proteinAllowed = false;
79     initViewportParams();
80   }
81
82   public String getCalcId()
83   {
84     return CALC_ID;
85   }
86
87   private static String CALC_ID = "jalview.ws.jws2.RNAalifoldClient";
88
89   public static AlignAnalysisUIText getAlignAnalysisUITest()
90   {
91     return new AlignAnalysisUIText(
92             compbio.ws.client.Services.RNAalifoldWS.toString(),
93             jalview.ws.jws2.RNAalifoldClient.class,
94             CALC_ID,
95             true,
96             false,
97             true,
98             "RNAAliFold Prediction",
99             "When checked, RNA secondary structure predictions will be calculated for the alignment, and updated when edits are made.",
100             "Change RNAAliFold settings...",
101             "Modify settings for the RNAAliFold prediction. Use this to hide or show different results of the RNA calculation, and change RNA folding parameters");
102
103   }
104
105   @Override
106   public String getServiceActionText()
107   {
108     return "Submitting RNA alignment for Secondary Structure prediction using "
109             + "RNAalifold Service";
110   }
111
112   @Override
113   boolean checkValidInputSeqs(boolean dynamic, List<FastaSequence> seqs)
114   {
115     return (seqs.size() > 1);
116   }
117
118   @Override
119   public void updateResultAnnotation(boolean immediate)
120   {
121
122     if (immediate || !calcMan.isWorking(this) && scoremanager != null)
123     {
124
125       List<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
126
127       // Unpack the ScoreManager
128       List<String> structs = ((RNAStructScoreManager) scoremanager)
129               .getStructs();
130       List<TreeSet<Score>> data = ((RNAStructScoreManager) scoremanager)
131               .getData();
132
133       // test to see if this data object contains base pair contacts
134       Score fscore = data.get(0).first();
135       this.bpScores = (fscore.getMethod()
136               .equals(AlifoldResult.contactProbabilities.toString()));
137
138       // add annotation for the consensus sequence alignment
139       createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
140               structs.get(0), null, null);
141
142       // Add annotations for the mfe Structure
143       createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
144               structs.get(1), data.get(1), null);
145
146       // decide whether to add base pair contact probability histogram
147       int count = 2;
148       if (bpScores)
149       {
150         createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
151                 structs.get(2), data.get(0), data.get(2));
152         count++;
153       }
154
155       // Now loop for the rest of the Annotations (if there it isn't stochastic
156       // output
157       // only the centroid and MEA structures remain anyway)
158       for (int i = count; i < structs.size(); i++)
159       {
160         // The ensemble values should be displayed in the description of the
161         // first (or all?) Stochastic Backtrack Structures.
162         if (!data.get(i).first().getMethod()
163                 .equals(AlifoldResult.ensembleValues.toString()))
164         {
165
166           createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
167                   structs.get(i), data.get(i), null);
168         }
169       }
170
171       if (ourAnnot.size() > 0)
172       {
173
174         updateOurAnnots(ourAnnot);
175         ap.adjustAnnotationHeight();
176       }
177     }
178   }
179
180   protected void createAnnotationRowforScoreHolder(
181           List<AlignmentAnnotation> ourAnnot, String calcId, String struct,
182           TreeSet<Score> data, TreeSet<Score> descriptionData)
183   {
184     /*
185      * If contactProbability information is returned from RNAalifold it is
186      * stored in the first TreeSet<Score> object corresponding to the String Id
187      * which holds the consensus alignment. The method enumeration is then
188      * updated to AlifoldResult.contactProbabilties. This line recreates the
189      * same data object as was overwritten with the contact probabilites data.
190      */
191     if (data == null)
192       data = compbio.data.sequence.RNAStructReader
193               .newEmptyScore(AlifoldResult.consensusAlignment);
194
195     if (descriptionData == null)
196       descriptionData = data;
197
198     String[] typenameAndDescription = constructTypenameAndDescription(descriptionData
199             .first());
200     String typename = typenameAndDescription[0];
201     String description = typenameAndDescription[1];
202
203     AlignmentAnnotation annotation = alignViewport.getAlignment()
204             .findOrCreateAnnotation(typename, calcId, false, null, null);
205
206     constructAnnotationFromScoreHolder(annotation, struct, data);
207
208     /*
209      * update annotation description with the free Energy, frequency in ensemble
210      * or other data where appropriate.
211      * 
212      * Doesnt deal with AlifoldResult.ensembleValues, the free energy of
213      * ensemble and frequency of mfe structure in ensemble. How to deal with
214      * these?
215      */
216     annotation.description = description;
217
218     annotation.belowAlignment = false;
219     // annotation.showAllColLabels = true;
220
221     alignViewport.getAlignment().validateAnnotation(annotation);
222     af.setMenusForViewport();
223
224     ourAnnot.add(annotation);
225   }
226
227   private AlignmentAnnotation constructAnnotationFromScoreHolder(
228           AlignmentAnnotation annotation, String struct, TreeSet<Score> data)
229   {
230     Annotation[] anns = new Annotation[gapMap != null ? gapMap.length + 1
231             : struct.length()];
232
233     if (data != null
234             && data.size() > 1
235             && data.first().getMethod()
236                     .equals(AlifoldResult.contactProbabilities.toString()))
237     {
238
239       // The base pair probabilities are stored in a set in scoreholder. we want
240       // a map
241       LinkedHashMap<Range, Float> basePairs = new LinkedHashMap<Range, Float>();
242       for (Score score : data)
243       {
244         // The Score objects contain a set of size one containing the range and
245         // an ArrayList<float> of size one containing the probabilty
246         basePairs.put(score.getRanges().first(), new Float(score
247                 .getScores().get(0)));
248       }
249
250       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
251       {
252         if (gapMap != null)
253         {
254           // skip any gapped columns in the input data
255           while (!gapMap[ri])
256           {
257             ri++;
258           }
259         }
260         // Return all the contacts associated with position i
261         LinkedHashMap<Range, Float> contacts = isContact(basePairs, i + 1);
262
263         String description = "";
264         float prob = 0f;
265
266         if (contacts.size() == 0)
267         {
268           description = "No Data";
269         }
270         else
271         {
272           for (Range contact : contacts.keySet())
273           {
274             float t = contacts.get(contact);
275             if (t > prob)
276               prob = t;
277             description += Integer.toString(contact.from) + "->"
278                     + Integer.toString(contact.to) + ": "
279                     + Float.toString(t) + "%  |  ";
280           }
281         }
282
283         anns[ri] = new Annotation(struct.substring(i, i + 1), description,
284                 isSS(struct.charAt(i)), prob);
285       }
286     }
287     else if (data == null || data.size() == 1)
288     {
289       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
290       {
291         if (gapMap != null)
292         {
293           // skip any gapped columns in the input data
294           while (!gapMap[ri] && ri < gapMap.length)
295           {
296             ri++;
297           }
298           if (ri == gapMap.length)
299           {
300             break;
301           }
302         }
303         anns[ri] = new Annotation(struct.substring(i, i + 1), "",
304                 isSS(struct.charAt(i)), Float.NaN);
305       }
306
307       annotation.graph = 0; // No graph
308     }
309
310     annotation.annotations = anns;
311
312     return annotation;
313   }
314
315   private String[] constructTypenameAndDescription(Score score)
316   {
317     String description = "";
318     String typename = "";
319     String datatype = score.getMethod();
320
321     // Look up java switch syntax and use one here
322     if (datatype.equals(AlifoldResult.mfeStructure.toString()))
323     {
324
325       description = MessageFormat.format(
326               "Minimum Free Energy Structure. Energy: {0} = {1} + {2}",
327               score.getScores().get(0), score.getScores().get(1), score
328                       .getScores().get(2));
329       typename = "MFE Structure";
330     }
331     else if (datatype.equals(AlifoldResult.contactProbabilityStructure
332             .toString()))
333     {
334       description = MessageFormat
335               .format("Base Pair Contact Probabilities. "
336                       + "Energy of Ensemble: {0}  Frequency of Ensemble: {1}",
337                       score.getScores().get(0), score.getScores().get(1));
338       typename = "Contact Probabilities";
339     }
340     else if (datatype.equals(AlifoldResult.centroidStructure.toString()))
341     {
342       description = MessageFormat.format(
343               "Centroid Structure. Energy: {0} = {1} + {2}", score
344                       .getScores().get(0), score.getScores().get(1), score
345                       .getScores().get(2));
346       typename = "Centroid Structure";
347     }
348     else if (datatype.equals(AlifoldResult.stochBTStructure.toString()))
349     {
350       if (score.getScores().size() > 0)
351       {
352         description = MessageFormat.format("Probability: {0}  Energy: {1}",
353                 score.getScores().get(0), score.getScores().get(1));
354       }
355       else
356         description = "Stochastic Backtrack Structure";
357     }
358     else if (datatype.equals(AlifoldResult.MEAStucture.toString()))
359     {
360       description = MessageFormat.format(
361               "Maximum Expected Accuracy Values: '{' {0} MEA={1} '}", score
362                       .getScores().get(0), score.getScores().get(1));
363       typename = "MEA Structure";
364     }
365     else if (datatype.equals(AlifoldResult.consensusAlignment.toString()))
366     {
367       typename = "RNAalifold Consensus";
368       description = "Consensus Alignment Produced by RNAalifold";
369     }
370     else
371     {
372       typename = datatype;
373       description = typename;
374     }
375
376     return new String[]
377     { typename, description };
378   }
379
380   // Check whether, at position i there is a base contact and return all the
381   // contacts at this position. Should be in order of descending probability.
382   private LinkedHashMap<Range, Float> isContact(
383           LinkedHashMap<Range, Float> basePairs, int i)
384   {
385     LinkedHashMap<Range, Float> contacts = new LinkedHashMap<Range, Float>();
386
387     for (Range contact : basePairs.keySet())
388     {
389       // finds the contacts associtated with position i ordered by the natural
390       // ordering of the Scores TreeSet in ScoreManager which is, descending
391       // probability
392       if (contact.from == i || contact.to == i)
393         contacts.put(contact, basePairs.get(contact));
394     }
395
396     return contacts;
397   }
398
399   private char isSS(char chr)
400   {
401     String regex = "\\(|\\)|\\{|\\}|\\[|\\]";
402     char ss = (Pattern.matches(regex, Character.toString(chr))) ? 'S' : ' ';
403     return ss;
404   }
405 }