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