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