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