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