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