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