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