JAL-845 SplitFrame for "show product" and after aligning from SplitFrame
[jalview.git] / src / jalview / analysis / AlignmentUtils.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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.analysis;
22
23 import jalview.datamodel.AlignedCodonFrame;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SearchResults;
27 import jalview.datamodel.Sequence;
28 import jalview.datamodel.SequenceI;
29 import jalview.schemes.ResidueProperties;
30 import jalview.util.MapList;
31
32 import java.util.ArrayList;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 /**
39  * grab bag of useful alignment manipulation operations Expect these to be
40  * refactored elsewhere at some point.
41  * 
42  * @author jimp
43  * 
44  */
45 public class AlignmentUtils
46 {
47
48   /**
49    * Represents the 3 possible results of trying to map one alignment to
50    * another.
51    */
52   public enum MappingResult
53   {
54     Mapped, NotMapped, AlreadyMapped
55   }
56
57   /**
58    * given an existing alignment, create a new alignment including all, or up to
59    * flankSize additional symbols from each sequence's dataset sequence
60    * 
61    * @param core
62    * @param flankSize
63    * @return AlignmentI
64    */
65   public static AlignmentI expandContext(AlignmentI core, int flankSize)
66   {
67     List<SequenceI> sq = new ArrayList<SequenceI>();
68     int maxoffset = 0;
69     for (SequenceI s : core.getSequences())
70     {
71       SequenceI newSeq = s.deriveSequence();
72       if (newSeq.getStart() > maxoffset
73               && newSeq.getDatasetSequence().getStart() < s.getStart())
74       {
75         maxoffset = newSeq.getStart();
76       }
77       sq.add(newSeq);
78     }
79     if (flankSize > -1)
80     {
81       maxoffset = flankSize;
82     }
83     // now add offset to create a new expanded alignment
84     for (SequenceI s : sq)
85     {
86       SequenceI ds = s;
87       while (ds.getDatasetSequence() != null)
88       {
89         ds = ds.getDatasetSequence();
90       }
91       int s_end = s.findPosition(s.getStart() + s.getLength());
92       // find available flanking residues for sequence
93       int ustream_ds = s.getStart() - ds.getStart(), dstream_ds = ds
94               .getEnd() - s_end;
95
96       // build new flanked sequence
97
98       // compute gap padding to start of flanking sequence
99       int offset = maxoffset - ustream_ds;
100
101       // padding is gapChar x ( maxoffset - min(ustream_ds, flank)
102       if (flankSize >= 0)
103       {
104         if (flankSize < ustream_ds)
105         {
106           // take up to flankSize residues
107           offset = maxoffset - flankSize;
108           ustream_ds = flankSize;
109         }
110         if (flankSize < dstream_ds)
111         {
112           dstream_ds = flankSize;
113         }
114       }
115       char[] upstream = new String(ds.getSequence(s.getStart() - 1
116               - ustream_ds, s.getStart() - 1)).toLowerCase().toCharArray();
117       char[] downstream = new String(ds.getSequence(s_end - 1, s_end + 1
118               + dstream_ds)).toLowerCase().toCharArray();
119       char[] coreseq = s.getSequence();
120       char[] nseq = new char[offset + upstream.length + downstream.length
121               + coreseq.length];
122       char c = core.getGapCharacter();
123       // TODO could lowercase the flanking regions
124       int p = 0;
125       for (; p < offset; p++)
126       {
127         nseq[p] = c;
128       }
129       // s.setSequence(new String(upstream).toLowerCase()+new String(coreseq) +
130       // new String(downstream).toLowerCase());
131       System.arraycopy(upstream, 0, nseq, p, upstream.length);
132       System.arraycopy(coreseq, 0, nseq, p + upstream.length,
133               coreseq.length);
134       System.arraycopy(downstream, 0, nseq, p + coreseq.length
135               + upstream.length, downstream.length);
136       s.setSequence(new String(nseq));
137       s.setStart(s.getStart() - ustream_ds);
138       s.setEnd(s_end + downstream.length);
139     }
140     AlignmentI newAl = new jalview.datamodel.Alignment(
141             sq.toArray(new SequenceI[0]));
142     for (SequenceI s : sq)
143     {
144       if (s.getAnnotation() != null)
145       {
146         for (AlignmentAnnotation aa : s.getAnnotation())
147         {
148           newAl.addAnnotation(aa);
149         }
150       }
151     }
152     newAl.setDataset(core.getDataset());
153     return newAl;
154   }
155
156   /**
157    * Returns the index (zero-based position) of a sequence in an alignment, or
158    * -1 if not found.
159    * 
160    * @param al
161    * @param seq
162    * @return
163    */
164   public static int getSequenceIndex(AlignmentI al, SequenceI seq)
165   {
166     int result = -1;
167     int pos = 0;
168     for (SequenceI alSeq : al.getSequences())
169     {
170       if (alSeq == seq)
171       {
172         result = pos;
173         break;
174       }
175       pos++;
176     }
177     return result;
178   }
179
180   /**
181    * Returns a map of lists of sequences in the alignment, keyed by sequence
182    * name. For use in mapping between different alignment views of the same
183    * sequences.
184    * 
185    * @see jalview.datamodel.AlignmentI#getSequencesByName()
186    */
187   public static Map<String, List<SequenceI>> getSequencesByName(
188           AlignmentI al)
189   {
190     Map<String, List<SequenceI>> theMap = new LinkedHashMap<String, List<SequenceI>>();
191     for (SequenceI seq : al.getSequences())
192     {
193       String name = seq.getName();
194       if (name != null)
195       {
196         List<SequenceI> seqs = theMap.get(name);
197         if (seqs == null)
198         {
199           seqs = new ArrayList<SequenceI>();
200           theMap.put(name, seqs);
201         }
202         seqs.add(seq);
203       }
204     }
205     return theMap;
206   }
207
208   /**
209    * Build mapping of protein to cDNA alignment. Mappings are made between
210    * sequences which have the same name and compatible lengths. Has a 3-valued
211    * result: either Mapped (at least one sequence mapping was created),
212    * AlreadyMapped (all possible sequence mappings already exist), or NotMapped
213    * (no possible sequence mappings exist).
214    * 
215    * @param proteinAlignment
216    * @param cdnaAlignment
217    * @return
218    */
219   public static MappingResult mapProteinToCdna(
220           final AlignmentI proteinAlignment,
221           final AlignmentI cdnaAlignment)
222   {
223     if (proteinAlignment == null || cdnaAlignment == null)
224     {
225       return MappingResult.NotMapped;
226     }
227
228     boolean mappingPossible = false;
229     boolean mappingPerformed = false;
230
231     List<SequenceI> thisSeqs = proteinAlignment.getSequences();
232   
233     /*
234      * Build a look-up of cDNA sequences by name, for matching purposes.
235      */
236     Map<String, List<SequenceI>> cdnaSeqs = cdnaAlignment
237             .getSequencesByName();
238   
239     for (SequenceI aaSeq : thisSeqs)
240     {
241       AlignedCodonFrame acf = new AlignedCodonFrame();
242       List<SequenceI> candidates = cdnaSeqs.get(aaSeq.getName());
243       if (candidates == null)
244       {
245         /*
246          * No cDNA sequence with matching name, so no mapping possible for this
247          * protein sequence
248          */
249         continue;
250       }
251       mappingPossible = true;
252       for (SequenceI cdnaSeq : candidates)
253       {
254         if (!mappingExists(proteinAlignment.getCodonFrames(),
255                 aaSeq.getDatasetSequence(), cdnaSeq.getDatasetSequence()))
256         {
257           MapList map = mapProteinToCdna(aaSeq, cdnaSeq);
258           if (map != null)
259           {
260             acf.addMap(cdnaSeq, aaSeq, map);
261             mappingPerformed = true;
262           }
263         }
264       }
265       proteinAlignment.addCodonFrame(acf);
266     }
267
268     /*
269      * If at least one mapping was possible but none was done, then the
270      * alignments are already as mapped as they can be.
271      */
272     if (mappingPossible && !mappingPerformed)
273     {
274       return MappingResult.AlreadyMapped;
275     }
276     else
277     {
278       return mappingPerformed ? MappingResult.Mapped
279               : MappingResult.NotMapped;
280     }
281   }
282
283   /**
284    * Answers true if the mappings include one between the given (dataset)
285    * sequences.
286    */
287   public static boolean mappingExists(Set<AlignedCodonFrame> set,
288           SequenceI aaSeq, SequenceI cdnaSeq)
289   {
290     if (set != null)
291     {
292       for (AlignedCodonFrame acf : set)
293       {
294         if (cdnaSeq == acf.getDnaForAaSeq(aaSeq))
295         {
296           return true;
297         }
298       }
299     }
300     return false;
301   }
302
303   /**
304    * Build a mapping (if possible) of a protein to a cDNA sequence. The cDNA
305    * must be three times the length of the protein, possibly after ignoring
306    * start and/or stop codons. Returns null if no mapping is determined.
307    * 
308    * @param proteinSeqs
309    * @param cdnaSeq
310    * @return
311    */
312   public static MapList mapProteinToCdna(SequenceI proteinSeq,
313           SequenceI cdnaSeq)
314   {
315     /*
316      * Here we handle either dataset sequence set (desktop) or absent (applet)
317      */
318     final SequenceI proteinDataset = proteinSeq.getDatasetSequence();
319     String aaSeqString = proteinDataset != null ? proteinDataset
320             .getSequenceAsString() : proteinSeq.getSequenceAsString();
321     final SequenceI cdnaDataset = cdnaSeq.getDatasetSequence();
322     String cdnaSeqString = cdnaDataset != null ? cdnaDataset
323             .getSequenceAsString() : cdnaSeq.getSequenceAsString();
324     if (aaSeqString == null || cdnaSeqString == null)
325     {
326       return null;
327     }
328
329     final int mappedLength = 3 * aaSeqString.length();
330     int cdnaLength = cdnaSeqString.length();
331     int cdnaStart = 1;
332     int cdnaEnd = cdnaLength;
333     final int proteinStart = 1;
334     final int proteinEnd = aaSeqString.length();
335
336     /*
337      * If lengths don't match, try ignoring stop codon.
338      */
339     if (cdnaLength != mappedLength)
340     {
341       for (Object stop : ResidueProperties.STOP)
342       {
343         if (cdnaSeqString.toUpperCase().endsWith((String) stop))
344         {
345           cdnaEnd -= 3;
346           cdnaLength -= 3;
347           break;
348         }
349       }
350     }
351
352     /*
353      * If lengths still don't match, try ignoring start codon.
354      */
355     if (cdnaLength != mappedLength
356             && cdnaSeqString.toUpperCase().startsWith(
357                     ResidueProperties.START))
358     {
359       cdnaStart += 3;
360       cdnaLength -= 3;
361     }
362
363     if (cdnaLength == mappedLength)
364     {
365       MapList map = new MapList(new int[]
366       { cdnaStart, cdnaEnd }, new int[]
367       { proteinStart, proteinEnd }, 3, 1);
368       return map;
369     }
370     else
371     {
372       return null;
373     }
374   }
375
376   /**
377    * Align sequence 'seq' to match the alignment of a mapped sequence. Note this
378    * currently assumes that we are aligning cDNA to match protein.
379    * 
380    * @param seq
381    *          the sequence to be realigned
382    * @param al
383    *          the alignment whose sequence alignment is to be 'copied'
384    * @param gap
385    *          character string represent a gap in the realigned sequence
386    * @param preserveUnmappedGaps
387    * @param preserveMappedGaps
388    * @return true if the sequence was realigned, false if it could not be
389    */
390   public static boolean alignSequenceAs(SequenceI seq, AlignmentI al,
391           String gap, boolean preserveMappedGaps,
392           boolean preserveUnmappedGaps)
393   {
394     /*
395      * Get any mappings from the source alignment to the target (dataset) sequence.
396      */
397     // TODO there may be one AlignedCodonFrame per dataset sequence, or one with
398     // all mappings. Would it help to constrain this?
399     List<AlignedCodonFrame> mappings = al.getCodonFrame(seq);
400     if (mappings == null)
401     {
402       return false;
403     }
404   
405     /*
406      * Locate the aligned source sequence whose dataset sequence is mapped. We
407      * just take the first match here (as we can't align cDNA like more than one
408      * protein sequence).
409      */
410     SequenceI alignFrom = null;
411     AlignedCodonFrame mapping = null;
412     for (AlignedCodonFrame mp : mappings)
413     {
414       alignFrom = mp.findAlignedSequence(seq.getDatasetSequence(), al);
415       if (alignFrom != null)
416       {
417         mapping = mp;
418         break;
419       }
420     }
421   
422     if (alignFrom == null)
423     {
424       return false;
425     }
426     alignSequenceAs(seq, alignFrom, mapping, gap, al.getGapCharacter(),
427             preserveMappedGaps, preserveUnmappedGaps);
428     return true;
429   }
430
431   /**
432    * Align sequence 'alignTo' the same way as 'alignFrom', using the mapping to
433    * match residues and codons. Flags control whether existing gaps in unmapped
434    * (intron) and mapped (exon) regions are preserved or not. Gaps linking intro
435    * and exon are only retained if both flags are set.
436    * 
437    * @param alignTo
438    * @param alignFrom
439    * @param mapping
440    * @param myGap
441    * @param sourceGap
442    * @param preserveUnmappedGaps
443    * @param preserveMappedGaps
444    */
445   public static void alignSequenceAs(SequenceI alignTo,
446           SequenceI alignFrom,
447           AlignedCodonFrame mapping, String myGap, char sourceGap,
448           boolean preserveMappedGaps, boolean preserveUnmappedGaps)
449   {
450     // TODO generalise to work for Protein-Protein, dna-dna, dna-protein
451     final char[] thisSeq = alignTo.getSequence();
452     final char[] thatAligned = alignFrom.getSequence();
453     StringBuilder thisAligned = new StringBuilder(2 * thisSeq.length);
454   
455     // aligned and dataset sequence positions, all base zero
456     int thisSeqPos = 0;
457     int sourceDsPos = 0;
458
459     int basesWritten = 0;
460     char myGapChar = myGap.charAt(0);
461     int ratio = myGap.length();
462
463     /*
464      * Traverse the aligned protein sequence.
465      */
466     int sourceGapMappedLength = 0;
467     boolean inExon = false;
468     for (char sourceChar : thatAligned)
469     {
470       if (sourceChar == sourceGap)
471       {
472         sourceGapMappedLength += ratio;
473         continue;
474       }
475
476       /*
477        * Found a residue. Locate its mapped codon (start) position.
478        */
479       sourceDsPos++;
480       // Note mapping positions are base 1, our sequence positions base 0
481       int[] mappedPos = mapping.getMappedRegion(alignTo, alignFrom,
482               sourceDsPos);
483       if (mappedPos == null)
484       {
485         /*
486          * Abort realignment if unmapped protein. Or could ignore it??
487          */
488         System.err.println("Can't align: no codon mapping to residue "
489                 + sourceDsPos + "(" + sourceChar + ")");
490         return;
491       }
492
493       int mappedCodonStart = mappedPos[0]; // position (1...) of codon start
494       int mappedCodonEnd = mappedPos[mappedPos.length - 1]; // codon end pos
495       StringBuilder trailingCopiedGap = new StringBuilder();
496
497       /*
498        * Copy dna sequence up to and including this codon. Optionally, include
499        * gaps before the codon starts (in introns) and/or after the codon starts
500        * (in exons).
501        * 
502        * Note this only works for 'linear' splicing, not reverse or interleaved.
503        * But then 'align dna as protein' doesn't make much sense otherwise.
504        */
505       int intronLength = 0;
506       while (basesWritten < mappedCodonEnd && thisSeqPos < thisSeq.length)
507       {
508         final char c = thisSeq[thisSeqPos++];
509         if (c != myGapChar)
510         {
511           basesWritten++;
512
513           if (basesWritten < mappedCodonStart)
514           {
515             /*
516              * Found an unmapped (intron) base. First add in any preceding gaps
517              * (if wanted).
518              */
519             if (preserveUnmappedGaps && trailingCopiedGap.length() > 0)
520             {
521               thisAligned.append(trailingCopiedGap.toString());
522               intronLength += trailingCopiedGap.length();
523               trailingCopiedGap = new StringBuilder();
524             }
525             intronLength++;
526             inExon = false;
527           }
528           else
529           {
530             final boolean startOfCodon = basesWritten == mappedCodonStart;
531             int gapsToAdd = calculateGapsToInsert(preserveMappedGaps,
532                     preserveUnmappedGaps, sourceGapMappedLength, inExon,
533                     trailingCopiedGap.length(), intronLength, startOfCodon);
534             for (int i = 0; i < gapsToAdd; i++)
535             {
536               thisAligned.append(myGapChar);
537             }
538             sourceGapMappedLength = 0;
539             inExon = true;
540           }
541           thisAligned.append(c);
542           trailingCopiedGap = new StringBuilder();
543         }
544         else
545         {
546           if (inExon && preserveMappedGaps)
547           {
548             trailingCopiedGap.append(myGapChar);
549           }
550           else if (!inExon && preserveUnmappedGaps)
551           {
552             trailingCopiedGap.append(myGapChar);
553           }
554         }
555       }
556     }
557
558     /*
559      * At end of protein sequence. Copy any remaining dna sequence, optionally
560      * including (intron) gaps. We do not copy trailing gaps in protein.
561      */
562     while (thisSeqPos < thisSeq.length)
563     {
564       final char c = thisSeq[thisSeqPos++];
565       if (c != myGapChar || preserveUnmappedGaps)
566       {
567         thisAligned.append(c);
568       }
569     }
570
571     /*
572      * All done aligning, set the aligned sequence.
573      */
574     alignTo.setSequence(new String(thisAligned));
575   }
576
577   /**
578    * Helper method to work out how many gaps to insert when realigning.
579    * 
580    * @param preserveMappedGaps
581    * @param preserveUnmappedGaps
582    * @param sourceGapMappedLength
583    * @param inExon
584    * @param trailingCopiedGap
585    * @param intronLength
586    * @param startOfCodon
587    * @return
588    */
589   protected static int calculateGapsToInsert(boolean preserveMappedGaps,
590           boolean preserveUnmappedGaps, int sourceGapMappedLength,
591           boolean inExon, int trailingGapLength,
592           int intronLength, final boolean startOfCodon)
593   {
594     int gapsToAdd = 0;
595     if (startOfCodon)
596     {
597       /*
598        * Reached start of codon. Ignore trailing gaps in intron unless we are
599        * preserving gaps in both exon and intron. Ignore them anyway if the
600        * protein alignment introduces a gap at least as large as the intronic
601        * region.
602        */
603       if (inExon && !preserveMappedGaps)
604       {
605         trailingGapLength = 0;
606       }
607       if (!inExon && !(preserveMappedGaps && preserveUnmappedGaps))
608       {
609         trailingGapLength = 0;
610       }
611       if (inExon)
612       {
613         gapsToAdd = Math.max(sourceGapMappedLength, trailingGapLength);
614       }
615       else
616       {
617         if (intronLength + trailingGapLength <= sourceGapMappedLength)
618         {
619           gapsToAdd = sourceGapMappedLength - intronLength;
620         }
621         else
622         {
623           gapsToAdd = Math.min(intronLength + trailingGapLength
624                   - sourceGapMappedLength, trailingGapLength);
625         }
626       }
627     }
628     else
629     {
630       /*
631        * second or third base of codon; check for any gaps in dna
632        */
633       if (!preserveMappedGaps)
634       {
635         trailingGapLength = 0;
636       }
637       gapsToAdd = Math.max(sourceGapMappedLength, trailingGapLength);
638     }
639     return gapsToAdd;
640   }
641
642   /**
643    * Returns a list of sequences mapped from the given sequences and aligned
644    * (gapped) in the same way. For example, the cDNA for aligned protein, where
645    * a single gap in protein generates three gaps in cDNA.
646    * 
647    * @param sequences
648    * @param gapCharacter
649    * @param mappings
650    * @return
651    */
652   public static List<SequenceI> getAlignedTranslation(
653           List<SequenceI> sequences, char gapCharacter,
654           Set<AlignedCodonFrame> mappings)
655   {
656     List<SequenceI> alignedSeqs = new ArrayList<SequenceI>();
657
658     for (SequenceI seq : sequences)
659     {
660       List<SequenceI> mapped = getAlignedTranslation(seq, gapCharacter,
661               mappings);
662       alignedSeqs.addAll(mapped);
663     }
664     return alignedSeqs;
665   }
666
667   /**
668    * Returns sequences aligned 'like' the source sequence, as mapped by the
669    * given mappings. Normally we expect zero or one 'mapped' sequences, but this
670    * will support 1-to-many as well.
671    * 
672    * @param seq
673    * @param gapCharacter
674    * @param mappings
675    * @return
676    */
677   protected static List<SequenceI> getAlignedTranslation(SequenceI seq,
678           char gapCharacter, Set<AlignedCodonFrame> mappings)
679   {
680     List<SequenceI> result = new ArrayList<SequenceI>();
681     for (AlignedCodonFrame mapping : mappings)
682     {
683       if (mapping.involvesSequence(seq))
684       {
685         SequenceI mapped = getAlignedTranslation(seq, gapCharacter, mapping);
686         if (mapped != null)
687         {
688           result.add(mapped);
689         }
690       }
691     }
692     return result;
693   }
694
695   /**
696    * Returns the translation of 'seq' (as held in the mapping) with
697    * corresponding alignment (gaps).
698    * 
699    * @param seq
700    * @param gapCharacter
701    * @param mapping
702    * @return
703    */
704   protected static SequenceI getAlignedTranslation(SequenceI seq,
705           char gapCharacter, AlignedCodonFrame mapping)
706   {
707     String gap = String.valueOf(gapCharacter);
708     boolean toDna = false;
709     int fromRatio = 1;
710     SequenceI mapTo = mapping.getDnaForAaSeq(seq);
711     if (mapTo != null)
712     {
713       // mapping is from protein to nucleotide
714       toDna = true;
715       // should ideally get gap count ratio from mapping
716       gap = String.valueOf(new char[]
717       { gapCharacter, gapCharacter, gapCharacter });
718     }
719     else
720     {
721       // mapping is from nucleotide to protein
722       mapTo = mapping.getAaForDnaSeq(seq);
723       fromRatio = 3;
724     }
725     StringBuilder newseq = new StringBuilder(seq.getLength()
726             * (toDna ? 3 : 1));
727
728     int residueNo = 0; // in seq, base 1
729     int[] phrase = new int[fromRatio];
730     int phraseOffset = 0;
731     int gapWidth = 0;
732     boolean first = true;
733     final Sequence alignedSeq = new Sequence("", "");
734
735     for (char c : seq.getSequence())
736     {
737       if (c == gapCharacter)
738       {
739         gapWidth++;
740         if (gapWidth >= fromRatio)
741         {
742           newseq.append(gap);
743           gapWidth = 0;
744         }
745       }
746       else
747       {
748         phrase[phraseOffset++] = residueNo + 1;
749         if (phraseOffset == fromRatio)
750         {
751           /*
752            * Have read a whole codon (or protein residue), now translate: map
753            * source phrase to positions in target sequence add characters at
754            * these positions to newseq Note mapping positions are base 1, our
755            * sequence positions base 0.
756            */
757           SearchResults sr = new SearchResults();
758           for (int pos : phrase)
759           {
760             mapping.markMappedRegion(seq, pos, sr);
761           }
762           newseq.append(sr.toString());
763           if (first)
764           {
765             first = false;
766             // Hack: Copy sequence dataset, name and description from
767             // SearchResults.match[0].sequence
768             // TODO? carry over sequence names from original 'complement'
769             // alignment
770             SequenceI mappedTo = sr.getResultSequence(0);
771             alignedSeq.setName(mappedTo.getName());
772             alignedSeq.setDescription(mappedTo.getDescription());
773             alignedSeq.setDatasetSequence(mappedTo);
774           }
775           phraseOffset = 0;
776         }
777         residueNo++;
778       }
779     }
780     alignedSeq.setSequence(newseq.toString());
781     return alignedSeq;
782   }
783 }