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