JAL-1807 explicit imports (jalview.ws.*)
[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.api.AlignCalcWorkerI;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.Annotation;
26 import jalview.gui.AlignFrame;
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 import compbio.ws.client.Services;
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             Services.RNAalifoldWS.toString(),
89             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     {
189       data = compbio.data.sequence.RNAStructReader
190               .newEmptyScore(AlifoldResult.consensusAlignment);
191     }
192
193     if (descriptionData == null)
194     {
195       descriptionData = data;
196     }
197
198     String[] typenameAndDescription = constructTypenameAndDescription(descriptionData
199             .first());
200     String typename = typenameAndDescription[0];
201     String description = typenameAndDescription[1];
202
203     AlignmentAnnotation annotation = alignViewport.getAlignment()
204             .findOrCreateAnnotation(typename, calcId, false, null, null);
205
206     constructAnnotationFromScoreHolder(annotation, struct, data);
207
208     /*
209      * update annotation description with the free Energy, frequency in ensemble
210      * or other data where appropriate.
211      * 
212      * Doesnt deal with AlifoldResult.ensembleValues, the free energy of
213      * ensemble and frequency of mfe structure in ensemble. How to deal with
214      * these?
215      */
216     annotation.description = description;
217
218     annotation.belowAlignment = false;
219     // annotation.showAllColLabels = true;
220
221     alignViewport.getAlignment().validateAnnotation(annotation);
222     af.setMenusForViewport();
223
224     ourAnnot.add(annotation);
225   }
226
227   private AlignmentAnnotation constructAnnotationFromScoreHolder(
228           AlignmentAnnotation annotation, String struct, TreeSet<Score> data)
229   {
230     Annotation[] anns = new Annotation[gapMap != null ? gapMap.length + 1
231             : struct.length()];
232
233     if (data != null
234             && data.size() > 1
235             && data.first().getMethod()
236                     .equals(AlifoldResult.contactProbabilities.toString()))
237     {
238
239       // The base pair probabilities are stored in a set in scoreholder. we want
240       // a map
241       LinkedHashMap<Range, Float> basePairs = new LinkedHashMap<Range, Float>();
242       for (Score score : data)
243       {
244         // The Score objects contain a set of size one containing the range and
245         // an ArrayList<float> of size one containing the probabilty
246         basePairs.put(score.getRanges().first(), new Float(score
247                 .getScores().get(0)));
248       }
249
250       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
251       {
252         if (gapMap != null)
253         {
254           // skip any gapped columns in the input data
255           while (!gapMap[ri])
256           {
257             ri++;
258           }
259         }
260         // Return all the contacts associated with position i
261         LinkedHashMap<Range, Float> contacts = isContact(basePairs, i + 1);
262
263         String description = "";
264         float prob = 0f;
265
266         if (contacts.size() == 0)
267         {
268           description = "No Data";
269         }
270         else
271         {
272           for (Range contact : contacts.keySet())
273           {
274             float t = contacts.get(contact);
275             if (t > prob)
276             {
277               prob = t;
278             }
279             description += Integer.toString(contact.from) + "->"
280                     + Integer.toString(contact.to) + ": "
281                     + Float.toString(t) + "%  |  ";
282           }
283         }
284
285         anns[ri] = new Annotation(struct.substring(i, i + 1), description,
286                 isSS(struct.charAt(i)), prob);
287       }
288     }
289     else if (data == null || data.size() == 1)
290     {
291       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
292       {
293         if (gapMap != null)
294         {
295           // skip any gapped columns in the input data
296           while (!gapMap[ri] && ri < gapMap.length)
297           {
298             ri++;
299           }
300           if (ri == gapMap.length)
301           {
302             break;
303           }
304         }
305         anns[ri] = new Annotation(struct.substring(i, i + 1), "",
306                 isSS(struct.charAt(i)), Float.NaN);
307       }
308
309       annotation.graph = 0; // No graph
310     }
311
312     annotation.annotations = anns;
313
314     return annotation;
315   }
316
317   private String[] constructTypenameAndDescription(Score score)
318   {
319     String description = "";
320     String typename = "";
321     String datatype = score.getMethod();
322
323     // Look up java switch syntax and use one here
324     if (datatype.equals(AlifoldResult.mfeStructure.toString()))
325     {
326
327       description = MessageFormat.format(
328               "Minimum Free Energy Structure. Energy: {0} = {1} + {2}",
329               score.getScores().get(0), score.getScores().get(1), score
330                       .getScores().get(2));
331       typename = "MFE Structure";
332     }
333     else if (datatype.equals(AlifoldResult.contactProbabilityStructure
334             .toString()))
335     {
336       description = MessageFormat
337               .format("Base Pair Contact Probabilities. "
338                       + "Energy of Ensemble: {0}  Frequency of Ensemble: {1}",
339                       score.getScores().get(0), score.getScores().get(1));
340       typename = "Contact Probabilities";
341     }
342     else if (datatype.equals(AlifoldResult.centroidStructure.toString()))
343     {
344       description = MessageFormat.format(
345               "Centroid Structure. Energy: {0} = {1} + {2}", score
346                       .getScores().get(0), score.getScores().get(1), score
347                       .getScores().get(2));
348       typename = "Centroid Structure";
349     }
350     else if (datatype.equals(AlifoldResult.stochBTStructure.toString()))
351     {
352       if (score.getScores().size() > 0)
353       {
354         description = MessageFormat.format("Probability: {0}  Energy: {1}",
355                 score.getScores().get(0), score.getScores().get(1));
356       }
357       else
358       {
359         description = "Stochastic Backtrack Structure";
360       }
361     }
362     else if (datatype.equals(AlifoldResult.MEAStucture.toString()))
363     {
364       description = MessageFormat.format(
365               "Maximum Expected Accuracy Values: '{' {0} MEA={1} '}", score
366                       .getScores().get(0), score.getScores().get(1));
367       typename = "MEA Structure";
368     }
369     else if (datatype.equals(AlifoldResult.consensusAlignment.toString()))
370     {
371       typename = "RNAalifold Consensus";
372       description = "Consensus Alignment Produced by RNAalifold";
373     }
374     else
375     {
376       typename = datatype;
377       description = typename;
378     }
379
380     return new String[]
381     { typename, description };
382   }
383
384   // Check whether, at position i there is a base contact and return all the
385   // contacts at this position. Should be in order of descending probability.
386   private LinkedHashMap<Range, Float> isContact(
387           LinkedHashMap<Range, Float> basePairs, int i)
388   {
389     LinkedHashMap<Range, Float> contacts = new LinkedHashMap<Range, Float>();
390
391     for (Range contact : basePairs.keySet())
392     {
393       // finds the contacts associtated with position i ordered by the natural
394       // ordering of the Scores TreeSet in ScoreManager which is, descending
395       // probability
396       if (contact.from == i || contact.to == i)
397       {
398         contacts.put(contact, basePairs.get(contact));
399       }
400     }
401
402     return contacts;
403   }
404
405   private char isSS(char chr)
406   {
407     String regex = "\\(|\\)|\\{|\\}|\\[|\\]";
408     char ss = (Pattern.matches(regex, Character.toString(chr))) ? 'S' : ' ';
409     return ss;
410   }
411 }