JAL-2103 always remove gaps from input sequence, then add in hidden regions if recove...
[jalview.git] / src / jalview / ws / jws1 / JPredWSUtils.java
1 package jalview.ws.jws1;
2
3 import jalview.datamodel.Alignment;
4 import jalview.datamodel.AlignmentAnnotation;
5 import jalview.datamodel.AlignmentI;
6 import jalview.datamodel.AlignmentView;
7 import jalview.datamodel.ColumnSelection;
8 import jalview.datamodel.SequenceI;
9 import jalview.io.FileParse;
10 import jalview.io.FormatAdapter;
11 import jalview.util.MessageManager;
12
13 import java.io.IOException;
14 import java.util.Hashtable;
15 import java.util.List;
16
17 /**
18  * extraction of processing routines to allow mocking
19  * 
20  * @author jprocter
21  *
22  */
23 public class JPredWSUtils
24 {
25   /**
26    * Process data extracted from service result set to generate a JPred result
27    * view.
28    * 
29    * @param currentView
30    * 
31    * @param input
32    *          - original input alignment
33    * @param gapChar
34    *          - character to use for reconstructing alignment used for
35    *          prediction
36    * @param SequenceInfo
37    *          - from SeqHash
38    * @param msaPred
39    *          - true if a prediction based on existing MSA
40    * @param predMap
41    *          - position
42    * @param result_PredFile
43    * @param result_Aligfile
44    * @param full_alignment
45    * @return { Alignment, ColumnSelection }
46    * @throws Exception
47    */
48   public static Object[] processJnetResult(AlignmentI currentView,
49           AlignmentView input, char gapChar, Hashtable SequenceInfo,
50           boolean msaPred, int[] predMap, String result_PredFile,
51           String result_Aligfile, FileParse full_alignment)
52           throws Exception
53   {
54
55     AlignmentI al = null;
56     ColumnSelection alcsel = null;
57
58     // the position of the query sequence in Alignment al
59     int FirstSeq = -1;
60
61     // the position of the original sequence in the array of
62     // Sequences in the input object that this job holds a
63     // prediction for
64     int msaIndex = 0;
65
66     // JPredFile prediction = new JPredFile("C:/JalviewX/files/jpred.txt",
67     // "File");
68     jalview.io.JPredFile prediction = new jalview.io.JPredFile(
69             result_PredFile, "Paste");
70     SequenceI[] preds = prediction.getSeqsAsArray();
71     jalview.bin.Cache.log.debug("Got prediction profile.");
72
73     if (msaPred && (result_Aligfile != null))
74     {
75       jalview.bin.Cache.log.debug("Getting associated alignment.");
76       // we ignore the returned alignment if we only predicted on a single
77       // sequence
78       String format = new jalview.io.IdentifyFile().identify(
79               result_Aligfile, "Paste");
80
81       if (jalview.io.FormatAdapter.isValidFormat(format))
82       {
83         SequenceI sqs[];
84         if (predMap != null)
85         {
86           Object[] alandcolsel = input
87                   .getAlignmentAndColumnSelection(gapChar);
88           sqs = (SequenceI[]) alandcolsel[0];
89           al = new Alignment(sqs);
90           alcsel = (ColumnSelection) alandcolsel[1];
91         }
92         else
93         {
94           al = new FormatAdapter().readFile(result_Aligfile, "Paste",
95                   format);
96           sqs = new SequenceI[al.getHeight()];
97
98           for (int i = 0, j = al.getHeight(); i < j; i++)
99           {
100             sqs[i] = al.getSequenceAt(i);
101           }
102           if (!jalview.analysis.SeqsetUtils.deuniquify(SequenceInfo, sqs))
103           {
104             throw (new Exception(
105                     MessageManager
106                             .getString("exception.couldnt_recover_sequence_properties_for_alignment")));
107           }
108         }
109         FirstSeq = 0;
110         if (currentView.getDataset() != null)
111         {
112           al.setDataset(currentView.getDataset());
113
114         }
115         else
116         {
117           al.setDataset(null);
118         }
119         jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
120                 FirstSeq, false, predMap);
121
122       }
123       else
124       {
125         throw (new Exception(MessageManager.formatMessage(
126                 "exception.unknown_format_for_file", new String[] { format,
127                     result_Aligfile })));
128       }
129     }
130     else
131     {
132       AlignmentI fullAlignment = null;
133       try
134       {
135         // read full alignment if present.
136         if (!msaPred && full_alignment != null)
137         {
138           fullAlignment = new FormatAdapter().readFromFile(full_alignment,
139                   "FASTA");
140         }
141       } catch (IOException q)
142       {
143
144       }
145       {
146         if (fullAlignment != null)
147         {
148           al = fullAlignment;
149           FirstSeq = 0;
150         }
151         else
152         {
153           al = new Alignment(preds);
154           FirstSeq = prediction.getQuerySeqPosition();
155         }
156       }
157
158       if (predMap != null)
159       {
160         // map the prediction onto the query sequence, excluding positions
161         // corresponding to hidden regions in the original input.
162         char gc = gapChar;
163         SequenceI[] sqs = (SequenceI[]) input
164                 .getAlignmentAndColumnSelection(gc)[0];
165         if (msaIndex >= sqs.length)
166         {
167           throw new Error(
168                   MessageManager
169                           .getString("error.implementation_error_invalid_msa_index_for_job"));
170         }
171         // //
172         // Uses RemoveGapsCommand
173         // //
174         new jalview.commands.RemoveGapsCommand(
175                 MessageManager.getString("label.remove_gaps"),
176                 new SequenceI[] { sqs[msaIndex] }, currentView);
177         if (fullAlignment == null)
178         {
179           // just replace trimmed sequence in prediction profile with full
180           // length sequence
181           SequenceI profileseq = al.getSequenceAt(FirstSeq);
182           profileseq.setSequence(sqs[msaIndex].getSequenceAsString());
183         }
184         else
185         {
186           insertHiddenResidues(al, '.', predMap, sqs[msaIndex]);
187         }
188       }
189
190       if (!jalview.analysis.SeqsetUtils.SeqCharacterUnhash(
191               al.getSequenceAt(FirstSeq), SequenceInfo))
192       {
193         throw (new Exception(
194                 MessageManager
195                         .getString("exception.couldnt_recover_sequence_props_for_jnet_query")));
196       }
197       else
198       {
199         if (currentView.getDataset() != null)
200         {
201           al.setDataset(currentView.getDataset());
202
203         }
204         else
205         {
206           al.setDataset(null);
207         }
208         if (fullAlignment != null)
209         {
210           // map gapMap from positions in visible sequence to positions in
211           // original sequence
212           if (predMap != null)
213           {
214
215           }
216         }
217         jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
218                 FirstSeq, true, predMap);
219         SequenceI profileseq = al.getSequenceAt(0); // this includes any gaps.
220         if (fullAlignment == null)
221         {
222           alignToProfileSeq(al, profileseq);
223         }
224         if (fullAlignment == null && predMap != null)
225         {
226           // Adjust input view for gaps
227           // propagate insertions into profile
228           alcsel = ColumnSelection.propagateInsertions(profileseq, al,
229                   input);
230         }
231       }
232     }
233
234     // transfer to dataset
235     for (AlignmentAnnotation alant : al.getAlignmentAnnotation())
236     {
237       if (alant.sequenceRef != null)
238       {
239         replaceAnnotationOnAlignmentWith(alant, alant.label,
240                 "jalview.jws1.Jpred" + (msaPred ? "MSA" : ""),
241                 alant.sequenceRef);
242       }
243     }
244
245     return new Object[] { al, alcsel }; // , FirstSeq, noMsa};
246   }
247
248   /**
249    * copied from JabawsCalcWorker
250    * 
251    * @param newAnnot
252    * @param typeName
253    * @param calcId
254    * @param aSeq
255    */
256   public static void replaceAnnotationOnAlignmentWith(
257           AlignmentAnnotation newAnnot, String typeName, String calcId,
258           SequenceI aSeq)
259   {
260     SequenceI dsseq = aSeq.getDatasetSequence();
261     while (dsseq.getDatasetSequence() != null)
262     {
263       dsseq = dsseq.getDatasetSequence();
264     }
265     // look for same annotation on dataset and lift this one over
266     List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
267             typeName);
268     if (dsan != null && dsan.size() > 0)
269     {
270       for (AlignmentAnnotation dssan : dsan)
271       {
272         dsseq.removeAlignmentAnnotation(dssan);
273       }
274     }
275     AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
276     dsseq.addAlignmentAnnotation(dssan);
277     dssan.adjustForAlignment();
278   }
279
280   /**
281    * Given an alignment where all other sequences except profileseq are aligned
282    * to the ungapped profileseq, insert gaps in the other sequences to realign
283    * them with the residues in profileseq
284    * 
285    * @param al
286    * @param profileseq
287    */
288   public static void alignToProfileSeq(AlignmentI al, SequenceI profileseq)
289   {
290     char gc = al.getGapCharacter();
291     int[] gapMap = profileseq.gapMap();
292     insertGapsInto(al, gc, gapMap);
293   }
294
295   /**
296    * Given an original sequence, and an alignment involving just the visible
297    * region insert gaps into the alignment and add in the missing residues from
298    * the original sequence
299    * 
300    * @param al
301    * @param c
302    * @param profileseq
303    */
304   public static void insertHiddenResidues(AlignmentI al, char gc,
305           int[] predMap, SequenceI origseq)
306   {
307     // orig: asdfPPPPPPPasdfPPPPasdf
308     // pred: PPPPPPPPPPP
309     // al: -----P-P-P---P---P----P---P-P--PP---P---
310     // s1: SSSSSSS-SS---S---SSSSSS---S-S--SSSSSSSSS
311     // s2: SSSSSSS-SSSSSSSSSSS----SSS-S-SSS-----SSS
312     //
313     // result:
314     //
315     // al: asdf-----P-P-P---P---P----P---Pasdf-P--PP---P---asdf
316     // s1: ....SSSSSSS-SS---S---SSSSSS---S....-S--SSSSSSSSS....
317     // s2: ....SSSSSSS-SSSSSSSSSSS----SSS-....S-SSS-----SSS....
318
319     // iteration 0: add asdf, append -----P
320     // iteration 1: append -P
321     // iteration 2: append -P
322     // iteration 3: append ---P
323     // iteration 4: append ---P
324     // iteration 5: append ----P
325     // iteration 6: append ---P
326     // iteration 7: append -P
327     // iteration 8: append P
328     // iteration 9: append P
329     // iteration 10: append ---P
330     // tail: append: ---, add asdf
331
332     String alseq = "";
333     SequenceI predseq = al.getSequenceAt(0);
334     int predIdx = 0; // next column of prediction to preserve
335     // positions in original and prediction sequence
336     int lp = origseq.getStart() - 1, predPos = predseq.getStart();
337     for (int r = 0; r < predMap.length; r++)
338     {
339       // also need to keep track of trimmed prediction sequence numbering
340       if (predMap[r] - lp > 1)
341       {
342         // hidden region insert from origseq
343         String insert = origseq.getSequenceAsString(
344                 origseq.findIndex(lp + 1) - 1,
345                 origseq.findIndex(predMap[r]) - 1);
346
347         insertGapsAt(al, gc, alseq.length(), insert.length());
348         alseq += insert;
349       }
350       // Now update prediction sequence for next position.
351       {
352         int predIdxNext = predseq.findIndex(predPos); // everything up
353                                                       // to the current
354                                                       // position in the
355                                                       // prediction
356                                                       // sequence
357                                                       // alignment
358         if (predIdxNext <= predIdx)
359         {
360           predIdxNext = predseq.getLength();
361         }
362         // just add in next segment of predseq
363         String predsert = predseq.getSequenceAsString(predIdx, predIdxNext);
364         alseq += predsert;
365         predIdx = predIdxNext;
366       }
367       lp = predMap[r];
368       predPos++;
369     }
370     // append final bits
371     // add any remaining gaps
372     {
373       int predIdxNext = predseq.findIndex(predPos); // everything up
374                                                     // to the current
375                                                     // position in the
376                                                     // prediction
377                                                     // sequence
378                                                     // alignment
379       if (predIdxNext <= predIdx)
380       {
381         predIdxNext = predseq.getLength();
382       }
383       // just add in next segment of predseq
384       String predsert = predseq.getSequenceAsString(predIdx, predIdxNext);
385       alseq += predsert;
386       predIdx = predIdxNext;
387     }
388
389     if (lp < origseq.getEnd())
390     {
391       String insert = origseq.getSequenceAsString(
392               origseq.findIndex(lp + 1) - 1, origseq.getLength());
393       insertGapsAt(al, gc, alseq.length(), insert.length());
394       alseq += insert;
395     }
396     // then add in origseq data.
397     predseq.setSequence(alseq);
398   }
399
400   public static void insertGapsInto(AlignmentI al, char gc, int[] gapMap)
401   {
402     // insert gaps into profile
403     for (int lp = 0, r = 0; r < gapMap.length; r++)
404     {
405       if (gapMap[r] - lp > 1)
406       {
407         insertGapsAt(al, gc, gapMap[r], gapMap[r] - lp);
408       }
409       lp = gapMap[r];
410     }
411   }
412
413   private static void insertGapsAt(AlignmentI al, char gc, int i, int lp)
414   {
415
416     StringBuffer sb = new StringBuffer();
417     for (int s = 0, ns = lp; s < ns; s++)
418     {
419       sb.append(gc);
420     }
421     for (int s = 1, ns = al.getHeight(); s < ns; s++)
422     {
423       String sq = al.getSequenceAt(s).getSequenceAsString();
424       int diff = i - sq.length();
425       if (diff > 0)
426       {
427         // pad gaps
428         sq = sq + sb;
429         while ((diff = i - sq.length()) > 0)
430         {
431           sq = sq
432                   + ((diff >= sb.length()) ? sb.toString() : sb.substring(
433                           0, diff));
434         }
435         al.getSequenceAt(s).setSequence(sq);
436       }
437       else
438       {
439         al.getSequenceAt(s).setSequence(
440                 sq.substring(0, i) + sb.toString() + sq.substring(i));
441       }
442     }
443   }
444
445 }