JAL-2418 source formatting
[jalview.git] / src / jalview / ws / jws2 / RNAalifoldClient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Annotation;
25 import jalview.gui.AlignFrame;
26 import jalview.util.MessageManager;
27 import jalview.ws.jws2.jabaws2.Jws2Instance;
28 import jalview.ws.params.WsParamSetI;
29 import jalview.ws.uimodel.AlignAnalysisUIText;
30
31 import java.text.MessageFormat;
32 import java.util.ArrayList;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.TreeSet;
36 import java.util.regex.Pattern;
37
38 import compbio.data.sequence.FastaSequence;
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 JabawsCalcWorker
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     af = alignFrame;
68     methodName = sh.serviceType;
69     alignedSeqs = true;
70     submitGaps = true;
71     nucleotidesAllowed = true;
72     proteinAllowed = false;
73     initViewportParams();
74   }
75
76   @Override
77   public String getCalcId()
78   {
79     return CALC_ID;
80   }
81
82   private static String CALC_ID = "jalview.ws.jws2.RNAalifoldClient";
83
84   public static AlignAnalysisUIText getAlignAnalysisUITest()
85   {
86     return new AlignAnalysisUIText(
87             compbio.ws.client.Services.RNAalifoldWS.toString(),
88             jalview.ws.jws2.RNAalifoldClient.class, CALC_ID, true, false,
89             true, MessageManager.getString("label.rnalifold_calculations"),
90             MessageManager.getString("tooltip.rnalifold_calculations"),
91             MessageManager.getString("label.rnalifold_settings"),
92             MessageManager.getString("tooltip.rnalifold_settings"));
93   }
94
95   @Override
96   public String getServiceActionText()
97   {
98     return "Submitting RNA alignment for Secondary Structure prediction using "
99             + "RNAalifold Service";
100   }
101
102   @Override
103   boolean checkValidInputSeqs(boolean dynamic, List<FastaSequence> seqs)
104   {
105     return (seqs.size() > 1);
106   }
107
108   @Override
109   public void updateResultAnnotation(boolean immediate)
110   {
111
112     if (immediate || !calcMan.isWorking(this) && scoremanager != null)
113     {
114
115       List<AlignmentAnnotation> ourAnnot = new ArrayList<AlignmentAnnotation>();
116
117       // Unpack the ScoreManager
118       List<String> structs = ((RNAStructScoreManager) scoremanager)
119               .getStructs();
120       List<TreeSet<Score>> data = ((RNAStructScoreManager) scoremanager)
121               .getData();
122
123       // test to see if this data object contains base pair contacts
124       Score fscore = data.get(0).first();
125       this.bpScores = (fscore.getMethod()
126               .equals(AlifoldResult.contactProbabilities.toString()));
127
128       // add annotation for the consensus sequence alignment
129       createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
130               structs.get(0), null, null);
131
132       // Add annotations for the mfe Structure
133       createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
134               structs.get(1), data.get(1), null);
135
136       // decide whether to add base pair contact probability histogram
137       int count = 2;
138       if (bpScores)
139       {
140         createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
141                 structs.get(2), data.get(0), data.get(2));
142         count++;
143       }
144
145       // Now loop for the rest of the Annotations (if there it isn't stochastic
146       // output
147       // only the centroid and MEA structures remain anyway)
148       for (int i = count; i < structs.size(); i++)
149       {
150         // The ensemble values should be displayed in the description of the
151         // first (or all?) Stochastic Backtrack Structures.
152         if (!data.get(i).first().getMethod()
153                 .equals(AlifoldResult.ensembleValues.toString()))
154         {
155
156           createAnnotationRowforScoreHolder(ourAnnot, getCalcId(),
157                   structs.get(i), data.get(i), null);
158         }
159       }
160
161       if (ourAnnot.size() > 0)
162       {
163
164         updateOurAnnots(ourAnnot);
165         ap.adjustAnnotationHeight();
166       }
167     }
168   }
169
170   protected void createAnnotationRowforScoreHolder(
171           List<AlignmentAnnotation> ourAnnot, String calcId, String struct,
172           TreeSet<Score> data, TreeSet<Score> descriptionData)
173   {
174     /*
175      * If contactProbability information is returned from RNAalifold it is
176      * stored in the first TreeSet<Score> object corresponding to the String Id
177      * which holds the consensus alignment. The method enumeration is then
178      * updated to AlifoldResult.contactProbabilties. This line recreates the
179      * same data object as was overwritten with the contact probabilites data.
180      */
181     if (data == null)
182     {
183       data = compbio.data.sequence.RNAStructReader
184               .newEmptyScore(AlifoldResult.consensusAlignment);
185     }
186
187     if (descriptionData == null)
188     {
189       descriptionData = data;
190     }
191
192     String[] typenameAndDescription = constructTypenameAndDescription(
193             descriptionData.first());
194     String typename = typenameAndDescription[0];
195     String description = typenameAndDescription[1];
196
197     AlignmentAnnotation annotation = alignViewport.getAlignment()
198             .findOrCreateAnnotation(typename, calcId, false, null, null);
199
200     constructAnnotationFromScoreHolder(annotation, struct, data);
201
202     /*
203      * update annotation description with the free Energy, frequency in ensemble
204      * or other data where appropriate.
205      * 
206      * Doesnt deal with AlifoldResult.ensembleValues, the free energy of
207      * ensemble and frequency of mfe structure in ensemble. How to deal with
208      * these?
209      */
210     annotation.description = description;
211
212     annotation.belowAlignment = false;
213     // annotation.showAllColLabels = true;
214
215     alignViewport.getAlignment().validateAnnotation(annotation);
216     af.setMenusForViewport();
217
218     ourAnnot.add(annotation);
219   }
220
221   private AlignmentAnnotation constructAnnotationFromScoreHolder(
222           AlignmentAnnotation annotation, String struct,
223           TreeSet<Score> data)
224   {
225     Annotation[] anns = new Annotation[gapMap != null ? gapMap.length + 1
226             : struct.length()];
227
228     if (data != null && data.size() > 1 && 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(),
240                 new Float(score.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             {
270               prob = t;
271             }
272             description += Integer.toString(contact.from) + "->"
273                     + Integer.toString(contact.to) + ": "
274                     + Float.toString(t) + "%  |  ";
275           }
276         }
277
278         anns[ri] = new Annotation(struct.substring(i, i + 1), description,
279                 isSS(struct.charAt(i)), prob);
280       }
281     }
282     else if (data == null || data.size() == 1)
283     {
284       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
285       {
286         if (gapMap != null)
287         {
288           // skip any gapped columns in the input data
289           while (!gapMap[ri] && ri < gapMap.length)
290           {
291             ri++;
292           }
293           if (ri == gapMap.length)
294           {
295             break;
296           }
297         }
298         anns[ri] = new Annotation(struct.substring(i, i + 1), "",
299                 isSS(struct.charAt(i)), Float.NaN);
300       }
301
302       annotation.graph = 0; // No graph
303     }
304
305     annotation.annotations = anns;
306
307     return annotation;
308   }
309
310   private String[] constructTypenameAndDescription(Score score)
311   {
312     String description = "";
313     String typename = "";
314     String datatype = score.getMethod();
315
316     // Look up java switch syntax and use one here
317     if (datatype.equals(AlifoldResult.mfeStructure.toString()))
318     {
319
320       description = MessageFormat.format(
321               "Minimum Free Energy Structure. Energy: {0} = {1} + {2}",
322               score.getScores().get(0), score.getScores().get(1),
323               score.getScores().get(2));
324       typename = "MFE Structure";
325     }
326     else if (datatype
327             .equals(AlifoldResult.contactProbabilityStructure.toString()))
328     {
329       description = MessageFormat.format("Base Pair Contact Probabilities. "
330               + "Energy of Ensemble: {0}  Frequency of Ensemble: {1}",
331               score.getScores().get(0), score.getScores().get(1));
332       typename = "Contact Probabilities";
333     }
334     else if (datatype.equals(AlifoldResult.centroidStructure.toString()))
335     {
336       description = MessageFormat.format(
337               "Centroid Structure. Energy: {0} = {1} + {2}",
338               score.getScores().get(0), score.getScores().get(1),
339               score.getScores().get(2));
340       typename = "Centroid Structure";
341     }
342     else if (datatype.equals(AlifoldResult.stochBTStructure.toString()))
343     {
344       if (score.getScores().size() > 0)
345       {
346         description = MessageFormat.format("Probability: {0}  Energy: {1}",
347                 score.getScores().get(0), score.getScores().get(1));
348       }
349       else
350       {
351         description = "Stochastic Backtrack Structure";
352       }
353     }
354     else if (datatype.equals(AlifoldResult.MEAStucture.toString()))
355     {
356       description = MessageFormat.format(
357               "Maximum Expected Accuracy Values: '{' {0} MEA={1} '}",
358               score.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[] { typename, description };
373   }
374
375   // Check whether, at position i there is a base contact and return all the
376   // contacts at this position. Should be in order of descending probability.
377   private LinkedHashMap<Range, Float> isContact(
378           LinkedHashMap<Range, Float> basePairs, int i)
379   {
380     LinkedHashMap<Range, Float> contacts = new LinkedHashMap<Range, Float>();
381
382     for (Range contact : basePairs.keySet())
383     {
384       // finds the contacts associtated with position i ordered by the natural
385       // ordering of the Scores TreeSet in ScoreManager which is, descending
386       // probability
387       if (contact.from == i || contact.to == i)
388       {
389         contacts.put(contact, basePairs.get(contact));
390       }
391     }
392
393     return contacts;
394   }
395
396   private char isSS(char chr)
397   {
398     String regex = "\\(|\\)|\\{|\\}|\\[|\\]";
399     char ss = (Pattern.matches(regex, Character.toString(chr))) ? 'S' : ' ';
400     return ss;
401   }
402 }