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