JAL-1620 version bump and release notes
[jalview.git] / src / jalview / ws / jws2 / RNAalifoldClient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
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.FastaSequence;
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,
90             CALC_ID,
91             true,
92             false,
93             true,
94             "RNAAliFold Prediction",
95             "When checked, RNA secondary structure predictions will be calculated for the alignment, and updated when edits are made.",
96             "Change RNAAliFold settings...",
97             "Modify settings for the RNAAliFold prediction. Use this to hide or show different results of the RNA calculation, and change RNA folding parameters");
98
99   }
100
101   @Override
102   public String getServiceActionText()
103   {
104     return "Submitting RNA alignment for Secondary Structure prediction using "
105             + "RNAalifold Service";
106   }
107
108   @Override
109   boolean checkValidInputSeqs(boolean dynamic, List<FastaSequence> seqs)
110   {
111     return (seqs.size() > 1);
112   }
113
114   @Override
115   public void updateResultAnnotation(boolean immediate)
116   {
117
118     if (immediate || !calcMan.isWorking(this) && scoremanager != null)
119     {
120
121       List<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
122
123       // Unpack the ScoreManager
124       List<String> structs = ((RNAStructScoreManager) scoremanager)
125               .getStructs();
126       List<TreeSet<Score>> data = ((RNAStructScoreManager) scoremanager)
127               .getData();
128
129       // test to see if this data object contains base pair contacts
130       Score fscore = data.get(0).first();
131       this.bpScores = (fscore.getMethod()
132               .equals(AlifoldResult.contactProbabilities.toString()));
133
134       // add annotation for the consensus sequence alignment
135       createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
136               structs.get(0), null, null);
137
138       // Add annotations for the mfe Structure
139       createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
140               structs.get(1), data.get(1), null);
141
142       // decide whether to add base pair contact probability histogram
143       int count = 2;
144       if (bpScores)
145       {
146         createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
147                 structs.get(2), data.get(0), data.get(2));
148         count++;
149       }
150
151       // Now loop for the rest of the Annotations (if there it isn't stochastic
152       // output
153       // only the centroid and MEA structures remain anyway)
154       for (int i = count; i < structs.size(); i++)
155       {
156         // The ensemble values should be displayed in the description of the
157         // first (or all?) Stochastic Backtrack Structures.
158         if (!data.get(i).first().getMethod()
159                 .equals(AlifoldResult.ensembleValues.toString()))
160         {
161
162           createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
163                   structs.get(i), data.get(i), null);
164         }
165       }
166
167       if (ourAnnot.size() > 0)
168       {
169
170         updateOurAnnots(ourAnnot);
171         ap.adjustAnnotationHeight();
172       }
173     }
174   }
175
176   protected void createAnnotationRowforScoreHolder(
177           List<AlignmentAnnotation> ourAnnot, String calcId, String struct,
178           TreeSet<Score> data, TreeSet<Score> descriptionData)
179   {
180     /*
181      * If contactProbability information is returned from RNAalifold it is
182      * stored in the first TreeSet<Score> object corresponding to the String Id
183      * which holds the consensus alignment. The method enumeration is then
184      * updated to AlifoldResult.contactProbabilties. This line recreates the
185      * same data object as was overwritten with the contact probabilites data.
186      */
187     if (data == null)
188       data = compbio.data.sequence.RNAStructReader
189               .newEmptyScore(AlifoldResult.consensusAlignment);
190
191     if (descriptionData == null)
192       descriptionData = data;
193
194     String[] typenameAndDescription = constructTypenameAndDescription(descriptionData
195             .first());
196     String typename = typenameAndDescription[0];
197     String description = typenameAndDescription[1];
198
199     AlignmentAnnotation annotation = alignViewport.getAlignment()
200             .findOrCreateAnnotation(typename, calcId, false, null, null);
201
202     constructAnnotationFromScoreHolder(annotation, struct, data);
203
204     /*
205      * update annotation description with the free Energy, frequency in ensemble
206      * or other data where appropriate.
207      * 
208      * Doesnt deal with AlifoldResult.ensembleValues, the free energy of
209      * ensemble and frequency of mfe structure in ensemble. How to deal with
210      * these?
211      */
212     annotation.description = description;
213
214     annotation.belowAlignment = false;
215     // annotation.showAllColLabels = true;
216
217     alignViewport.getAlignment().validateAnnotation(annotation);
218     af.setMenusForViewport();
219
220     ourAnnot.add(annotation);
221   }
222
223   private AlignmentAnnotation constructAnnotationFromScoreHolder(
224           AlignmentAnnotation annotation, String struct, TreeSet<Score> data)
225   {
226     Annotation[] anns = new Annotation[gapMap != null ? gapMap.length + 1
227             : struct.length()];
228
229     if (data != null
230             && data.size() > 1
231             && data.first().getMethod()
232                     .equals(AlifoldResult.contactProbabilities.toString()))
233     {
234
235       // The base pair probabilities are stored in a set in scoreholder. we want
236       // a map
237       LinkedHashMap<Range, Float> basePairs = new LinkedHashMap<Range, Float>();
238       for (Score score : data)
239       {
240         // The Score objects contain a set of size one containing the range and
241         // an ArrayList<float> of size one containing the probabilty
242         basePairs.put(score.getRanges().first(), new Float(score
243                 .getScores().get(0)));
244       }
245
246       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
247       {
248         if (gapMap != null)
249         {
250           // skip any gapped columns in the input data
251           while (!gapMap[ri])
252           {
253             ri++;
254           }
255         }
256         // Return all the contacts associated with position i
257         LinkedHashMap<Range, Float> contacts = isContact(basePairs, i + 1);
258
259         String description = "";
260         float prob = 0f;
261
262         if (contacts.size() == 0)
263         {
264           description = "No Data";
265         }
266         else
267         {
268           for (Range contact : contacts.keySet())
269           {
270             float t = contacts.get(contact);
271             if (t > prob)
272               prob = t;
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         description = "Stochastic Backtrack Structure";
353     }
354     else if (datatype.equals(AlifoldResult.MEAStucture.toString()))
355     {
356       description = MessageFormat.format(
357               "Maximum Expected Accuracy Values: '{' {0} MEA={1} '}", score
358                       .getScores().get(0), score.getScores().get(1));
359       typename = "MEA Structure";
360     }
361     else if (datatype.equals(AlifoldResult.consensusAlignment.toString()))
362     {
363       typename = "RNAalifold Consensus";
364       description = "Consensus Alignment Produced by RNAalifold";
365     }
366     else
367     {
368       typename = datatype;
369       description = typename;
370     }
371
372     return new String[]
373     { typename, description };
374   }
375
376   // Check whether, at position i there is a base contact and return all the
377   // contacts at this position. Should be in order of descending probability.
378   private LinkedHashMap<Range, Float> isContact(
379           LinkedHashMap<Range, Float> basePairs, int i)
380   {
381     LinkedHashMap<Range, Float> contacts = new LinkedHashMap<Range, Float>();
382
383     for (Range contact : basePairs.keySet())
384     {
385       // finds the contacts associtated with position i ordered by the natural
386       // ordering of the Scores TreeSet in ScoreManager which is, descending
387       // probability
388       if (contact.from == i || contact.to == i)
389         contacts.put(contact, basePairs.get(contact));
390     }
391
392     return contacts;
393   }
394
395   private char isSS(char chr)
396   {
397     String regex = "\\(|\\)|\\{|\\}|\\[|\\]";
398     char ss = (Pattern.matches(regex, Character.toString(chr))) ? 'S' : ' ';
399     return ss;
400   }
401 }