Merge branch 'Release_2_8_1_Branch' into Release_2_8_1_Branch_i18n
[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[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])
265           {
266             ri++;
267           }
268         }
269         anns[ri] = new Annotation(struct.substring(i, i + 1), "",
270                 isSS(struct.charAt(i)), Float.NaN);
271       }
272
273       annotation.graph = 0; // No graph
274     }
275
276     annotation.annotations = anns;
277
278     return annotation;
279   }
280
281   private String[] constructTypenameAndDescription(Score score)
282   {
283     String description = "";
284     String typename = "";
285     String datatype = score.getMethod();
286
287     // Look up java switch syntax and use one here
288     if (datatype.equals(AlifoldResult.mfeStructure.toString()))
289     {
290
291       description = MessageFormat.format(
292               "Minimum Free Energy Structure. Energy: {0} = {1} + {2}",
293               score.getScores().get(0), score.getScores().get(1), score
294                       .getScores().get(2));
295       typename = "MFE Structure";
296     }
297     else if (datatype.equals(AlifoldResult.contactProbabilityStructure
298             .toString()))
299     {
300       description = MessageFormat
301               .format("Base Pair Contact Probabilities. "
302                       + "Energy of Ensemble: {0}  Frequency of Ensemble: {1}",
303                       score.getScores().get(0), score.getScores().get(1));
304       typename = "Contact Probabilities";
305     }
306     else if (datatype.equals(AlifoldResult.centroidStructure.toString()))
307     {
308       description = MessageFormat.format(
309               "Centroid Structure. Energy: {0} = {1} + {2}", score
310                       .getScores().get(0), score.getScores().get(1), score
311                       .getScores().get(2));
312       typename = "Centroid Structure";
313     }
314     else if (datatype.equals(AlifoldResult.stochBTStructure.toString()))
315     {
316       if (score.getScores().size() > 0)
317       {
318         description = MessageFormat.format("Probability: {0}  Energy: {1}",
319                 score.getScores().get(0), score.getScores().get(1));
320       }
321       else
322         description = "Stochastic Backtrack Structure";
323     }
324     else if (datatype.equals(AlifoldResult.MEAStucture.toString()))
325     {
326       description = MessageFormat.format(
327               "Maximum Expected Accuracy Values: '{' {0} MEA={1} '}", score
328                       .getScores().get(0), score.getScores().get(1));
329       typename = "MEA Structure";
330     }
331     else if (datatype.equals(AlifoldResult.consensusAlignment.toString()))
332     {
333       typename = "RNAalifold Consensus";
334       description = "Consensus Alignment Produced by RNAalifold";
335     }
336     else
337     {
338       typename = datatype;
339       description = typename;
340     }
341
342     return new String[]
343     { typename, description };
344   }
345
346   // Check whether, at position i there is a base contact and return all the
347   // contacts at this position. Should be in order of descending probability.
348   private LinkedHashMap<Range, Float> isContact(
349           LinkedHashMap<Range, Float> basePairs, int i)
350   {
351     LinkedHashMap<Range, Float> contacts = new LinkedHashMap<Range, Float>();
352
353     for (Range contact : basePairs.keySet())
354     {
355       // finds the contacts associtated with position i ordered by the natural
356       // ordering of the Scores TreeSet in ScoreManager which is, descending
357       // probability
358       if (contact.from == i || contact.to == i)
359         contacts.put(contact, basePairs.get(contact));
360     }
361
362     return contacts;
363   }
364
365   private char isSS(char chr)
366   {
367     String regex = "\\(|\\)|\\{|\\}|\\[|\\]";
368     char ss = (Pattern.matches(regex, Character.toString(chr))) ? 'S' : ' ';
369     return ss;
370   }
371 }