JAL-1517 fix copyright for 2.8.2
[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  * @author daluke - Daniel Barton
48  *
49  */
50
51 public class RNAalifoldClient extends JabawsAlignCalcWorker implements
52         AlignCalcWorkerI
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
68     //if (arguments == null)
69     //  arguments = new ArrayList<Argument>();
70
71     af = alignFrame;
72     methodName = sh.serviceType;
73     alignedSeqs=true;
74     submitGaps=true;
75     nucleotidesAllowed = true;
76     proteinAllowed = false;
77     initViewportParams();
78   }
79   
80   public String getCalcId()
81   {
82     return CALC_ID;
83   }
84   private static String CALC_ID="jalview.ws.jws2.RNAalifoldClient";
85
86   public static AlignAnalysisUIText getAlignAnalysisUITest()
87   {
88     return new AlignAnalysisUIText(
89             compbio.ws.client.Services.RNAalifoldWS.toString(),
90             jalview.ws.jws2.RNAalifoldClient.class,
91             CALC_ID,
92             true,
93             false,
94             true,
95             "RNAAliFold Prediction",
96             "When checked, RNA secondary structure predictions will be calculated for the alignment, and updated when edits are made.",
97             "Change RNAAliFold settings...",
98             "Modify settings for the RNAAliFold prediction. Use this to hide or show different results of the RNA calculation, and change RNA folding parameters");
99
100   }
101
102   @Override
103   public String getServiceActionText()
104   {
105     return "Submitting RNA alignment for Secondary Structure prediction using "
106             + "RNAalifold Service";
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       data = compbio.data.sequence.RNAStructReader
184               .newEmptyScore(AlifoldResult.consensusAlignment);
185
186     if (descriptionData == null)
187       descriptionData = data;
188
189     String[] typenameAndDescription = constructTypenameAndDescription(descriptionData
190             .first());
191     String typename = typenameAndDescription[0];
192     String description = typenameAndDescription[1];
193
194     AlignmentAnnotation annotation = alignViewport.getAlignment()
195             .findOrCreateAnnotation(typename, calcId, false, null, null);
196
197     constructAnnotationFromScoreHolder(annotation, struct, data);
198
199     /*
200      * update annotation description with the free Energy, frequency in ensemble
201      * or other data where appropriate.
202      * 
203      * Doesnt deal with AlifoldResult.ensembleValues, the free energy of
204      * ensemble and frequency of mfe structure in ensemble. How to deal with
205      * these?
206      */
207     annotation.description = description;
208
209     annotation.belowAlignment = false;
210     // annotation.showAllColLabels = true;
211
212     alignViewport.getAlignment().validateAnnotation(annotation);
213     af.setMenusForViewport();
214
215     ourAnnot.add(annotation);
216   }
217
218   private AlignmentAnnotation constructAnnotationFromScoreHolder(
219           AlignmentAnnotation annotation, String struct, TreeSet<Score> data)
220   {
221     Annotation[] anns = new Annotation[gapMap!= null ? gapMap.length+1 : struct.length()];
222
223     if (data != null
224             && data.size() > 1
225             && data.first().getMethod()
226                     .equals(AlifoldResult.contactProbabilities.toString()))
227     {
228
229       // The base pair probabilities are stored in a set in scoreholder. we want
230       // a map
231       LinkedHashMap<Range, Float> basePairs = new LinkedHashMap<Range, Float>();
232       for (Score score : data)
233       {
234         // The Score objects contain a set of size one containing the range and
235         // an ArrayList<float> of size one containing the probabilty
236         basePairs.put(score.getRanges().first(), new Float(score
237                 .getScores().get(0)));
238       }
239       
240       for (int i = 0,ri=0,iEnd=struct.length();i<iEnd; i++,ri++)
241       {
242         if (gapMap!=null)
243         {
244           // skip any gapped columns in the input data
245           while (!gapMap[ri])
246           {
247             ri++;
248           }
249         }
250         // Return all the contacts associated with position i
251         LinkedHashMap<Range, Float> contacts = isContact(basePairs, i + 1);
252
253         String description = "";
254         float prob = 0f;
255
256         if (contacts.size() == 0)
257         {
258           description = "No Data";
259         }
260         else
261         {
262           for (Range contact : contacts.keySet())
263           {
264             float t = contacts.get(contact);
265             if (t > prob)
266               prob = t;
267             description += Integer.toString(contact.from) + "->"
268                     + Integer.toString(contact.to) + ": "
269                     + Float.toString(t) + "%  |  ";
270           }
271         }
272
273         anns[ri] = new Annotation(struct.substring(i, i + 1), description,
274                 isSS(struct.charAt(i)), prob);
275       }
276     }
277     else if (data == null || data.size() == 1)
278     {
279       for (int i = 0,ri=0,iEnd=struct.length();i<iEnd; i++,ri++)
280       {
281         if (gapMap!=null)
282         {
283           // skip any gapped columns in the input data
284           while (!gapMap[ri] && ri<gapMap.length)
285           {
286             ri++;
287           }
288           if (ri==gapMap.length)
289           {
290             break;
291           }
292         }
293         anns[ri] = new Annotation(struct.substring(i, i + 1), "",
294                 isSS(struct.charAt(i)), Float.NaN);
295       }
296
297       annotation.graph = 0; // No graph
298     }
299
300     annotation.annotations = anns;
301
302     return annotation;
303   }
304
305   private String[] constructTypenameAndDescription(Score score)
306   {
307     String description = "";
308     String typename = "";
309     String datatype = score.getMethod();
310
311     // Look up java switch syntax and use one here
312     if (datatype.equals(AlifoldResult.mfeStructure.toString()))
313     {
314
315       description = MessageFormat.format(
316               "Minimum Free Energy Structure. Energy: {0} = {1} + {2}",
317               score.getScores().get(0), score.getScores().get(1), score
318                       .getScores().get(2));
319       typename = "MFE Structure";
320     }
321     else if (datatype.equals(AlifoldResult.contactProbabilityStructure
322             .toString()))
323     {
324       description = MessageFormat
325               .format("Base Pair Contact Probabilities. "
326                       + "Energy of Ensemble: {0}  Frequency of Ensemble: {1}",
327                       score.getScores().get(0), score.getScores().get(1));
328       typename = "Contact Probabilities";
329     }
330     else if (datatype.equals(AlifoldResult.centroidStructure.toString()))
331     {
332       description = MessageFormat.format(
333               "Centroid Structure. Energy: {0} = {1} + {2}", score
334                       .getScores().get(0), score.getScores().get(1), score
335                       .getScores().get(2));
336       typename = "Centroid Structure";
337     }
338     else if (datatype.equals(AlifoldResult.stochBTStructure.toString()))
339     {
340       if (score.getScores().size() > 0)
341       {
342         description = MessageFormat.format("Probability: {0}  Energy: {1}",
343                 score.getScores().get(0), score.getScores().get(1));
344       }
345       else
346         description = "Stochastic Backtrack Structure";
347     }
348     else if (datatype.equals(AlifoldResult.MEAStucture.toString()))
349     {
350       description = MessageFormat.format(
351               "Maximum Expected Accuracy Values: '{' {0} MEA={1} '}", score
352                       .getScores().get(0), score.getScores().get(1));
353       typename = "MEA Structure";
354     }
355     else if (datatype.equals(AlifoldResult.consensusAlignment.toString()))
356     {
357       typename = "RNAalifold Consensus";
358       description = "Consensus Alignment Produced by RNAalifold";
359     }
360     else
361     {
362       typename = datatype;
363       description = typename;
364     }
365
366     return new String[]
367     { typename, description };
368   }
369
370   // Check whether, at position i there is a base contact and return all the
371   // contacts at this position. Should be in order of descending probability.
372   private LinkedHashMap<Range, Float> isContact(
373           LinkedHashMap<Range, Float> basePairs, int i)
374   {
375     LinkedHashMap<Range, Float> contacts = new LinkedHashMap<Range, Float>();
376
377     for (Range contact : basePairs.keySet())
378     {
379       // finds the contacts associtated with position i ordered by the natural
380       // ordering of the Scores TreeSet in ScoreManager which is, descending
381       // probability
382       if (contact.from == i || contact.to == i)
383         contacts.put(contact, basePairs.get(contact));
384     }
385
386     return contacts;
387   }
388
389   private char isSS(char chr)
390   {
391     String regex = "\\(|\\)|\\{|\\}|\\[|\\]";
392     char ss = (Pattern.matches(regex, Character.toString(chr))) ? 'S' : ' ';
393     return ss;
394   }
395 }