X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2FAlignedCodonFrame.java;h=6ccc0fc0c663b6458d88377696421e0381026fde;hb=855af27bbb88788ac9c1ee9872a43fbb333ae380;hp=5dfd434e81bd29af02855a6f9e833f633959a1ee;hpb=36394666b7848eb7c2569188dad062c7d450d612;p=jalview.git diff --git a/src/jalview/datamodel/AlignedCodonFrame.java b/src/jalview/datamodel/AlignedCodonFrame.java index 5dfd434..6ccc0fc 100644 --- a/src/jalview/datamodel/AlignedCodonFrame.java +++ b/src/jalview/datamodel/AlignedCodonFrame.java @@ -20,12 +20,13 @@ */ package jalview.datamodel; -import jalview.util.MapList; -import jalview.util.MappingUtils; - +import java.util.AbstractList; import java.util.ArrayList; import java.util.List; +import jalview.util.MapList; +import jalview.util.MappingUtils; + /** * Stores mapping between the columns of a protein alignment and a DNA alignment * and a list of individual codon to amino acid mappings between sequences. @@ -36,7 +37,7 @@ public class AlignedCodonFrame /* * Data bean to hold mappings from one sequence to another */ - private class SequenceToSequenceMapping + public class SequenceToSequenceMapping { private SequenceI fromSeq; @@ -57,6 +58,192 @@ public class AlignedCodonFrame return String.format("From %s %s", fromSeq.getName(), mapping.toString()); } + + /** + * Returns a hashCode derived from the hashcodes of the mappings and fromSeq + * + * @see SequenceToSequenceMapping#hashCode() + */ + @Override + public int hashCode() + { + return (fromSeq == null ? 0 : fromSeq.hashCode() * 31) + + mapping.hashCode(); + } + + /** + * Answers true if the objects hold the same mapping between the same two + * sequences + * + * @see Mapping#equals + */ + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof SequenceToSequenceMapping)) + { + return false; + } + SequenceToSequenceMapping that = (SequenceToSequenceMapping) obj; + if (this.mapping == null) + { + return that.mapping == null; + } + // TODO: can simplify by asserting fromSeq is a dataset sequence + return (this.fromSeq == that.fromSeq + || (this.fromSeq != null && that.fromSeq != null + && this.fromSeq.getDatasetSequence() != null + && this.fromSeq.getDatasetSequence() == that.fromSeq + .getDatasetSequence())) + && this.mapping.equals(that.mapping); + } + + public SequenceI getFromSeq() + { + return fromSeq; + } + + public Mapping getMapping() + { + return mapping; + } + + /** + * Returns true if the mapping covers the full length of the given sequence. + * This allows us to distinguish the CDS that codes for a protein from + * another overlapping CDS in the parent dna sequence. + * + * @param seq + * @return + */ + public boolean covers(SequenceI seq) + { + return covers(seq,false,false); + } + /** + * + * @param seq + * @param localCover - when true - compare extent of seq's dataset sequence rather than the local extent + * @param either - when true coverage is required for either seq or the mapped sequence + * @return true if mapping covers full length of given sequence (or the other if either==true) + */ + public boolean covers(SequenceI seq, boolean localCover,boolean either) + { + List mappedRanges = null,otherRanges=null; + MapList mapList = mapping.getMap(); + int mstart=seq.getStart(),mend=seq.getEnd(),ostart,oend; + ; + if (fromSeq == seq || fromSeq == seq.getDatasetSequence()) + { + if (localCover && fromSeq !=seq) + { + mstart=fromSeq.getStart(); + mend=fromSeq.getEnd(); + } + mappedRanges = mapList.getFromRanges(); + otherRanges=mapList.getToRanges(); + ostart=mapping.to.getStart(); + oend=mapping.to.getEnd(); + } + else if (mapping.to == seq || mapping.to == seq.getDatasetSequence()) + { + if (localCover && mapping.to !=seq) + { + mstart=mapping.to.getStart(); + mend=mapping.to.getEnd(); + } + mappedRanges = mapList.getToRanges(); + otherRanges=mapList.getFromRanges(); + ostart=fromSeq.getStart(); + oend=fromSeq.getEnd(); + } + else + { + return false; + } + + /* + * check that each mapped range lies within the sequence range + * (necessary for circular CDS - example EMBL:J03321:AAA91567) + * and mapped length covers (at least) sequence length + */ + int length = countRange(mappedRanges,mstart,mend); + + if (length != -1) + { + // add 3 to mapped length to allow for a mapped stop codon + if (length + 3 >= (mend - mstart + 1)) + { + return true; + } + } + if (either) + { + // also check coverage of the other range + length = countRange(otherRanges, ostart, oend); + if (length != -1) + { + if (length + 1 >= (oend - ostart + 1)) + { + return true; + } + } + } + return false; + } + private int countRange(List mappedRanges,int mstart,int mend) + { + int length=0; + for (int[] range : mappedRanges) + { + int from = Math.min(range[0], range[1]); + int to = Math.max(range[0], range[1]); + if (from < mstart || to > mend) + { + return -1; + } + length += (to - from + 1); + } + return length; + } + + /** + * Adds any regions mapped to or from position {@code pos} in sequence + * {@code seq} to the given search results + * + * @param seq + * @param pos + * @param sr + */ + public void markMappedRegion(SequenceI seq, int pos, SearchResultsI sr) + { + int[] codon = null; + SequenceI mappedSeq = null; + SequenceI ds = seq.getDatasetSequence(); + if (ds == null) + { + ds = seq; + } + + if (this.fromSeq == seq || this.fromSeq == ds) + { + codon = this.mapping.map.locateInTo(pos, pos); + mappedSeq = this.mapping.to; + } + else if (this.mapping.to == seq || this.mapping.to == ds) + { + codon = this.mapping.map.locateInFrom(pos, pos); + mappedSeq = this.fromSeq; + } + + if (codon != null) + { + for (int i = 0; i < codon.length; i += 2) + { + sr.addResult(mappedSeq, codon[i], codon[i + 1]); + } + } + } } private List mappings; @@ -66,7 +253,7 @@ public class AlignedCodonFrame */ public AlignedCodonFrame() { - mappings = new ArrayList(); + mappings = new ArrayList<>(); } /** @@ -79,17 +266,34 @@ public class AlignedCodonFrame */ public void addMap(SequenceI dnaseq, SequenceI aaseq, MapList map) { + addMap(dnaseq, aaseq, map, null); + } + + /** + * Adds a mapping between the dataset sequences for the associated dna and + * protein sequence objects + * + * @param dnaseq + * @param aaseq + * @param map + * @param mapFromId + */ + public void addMap(SequenceI dnaseq, SequenceI aaseq, MapList map, + String mapFromId) + { // JBPNote DEBUG! THIS ! // dnaseq.transferAnnotation(aaseq, mp); // aaseq.transferAnnotation(dnaseq, new Mapping(map.getInverse())); SequenceI fromSeq = (dnaseq.getDatasetSequence() == null) ? dnaseq : dnaseq.getDatasetSequence(); - SequenceI toSeq = (aaseq.getDatasetSequence() == null) ? aaseq : aaseq - .getDatasetSequence(); + SequenceI toSeq = (aaseq.getDatasetSequence() == null) ? aaseq + : aaseq.getDatasetSequence(); /* * if we already hold a mapping between these sequences, just add to it + * note that 'adding' a duplicate map does nothing; this protects against + * creating duplicate mappings in AlignedCodonFrame */ for (SequenceToSequenceMapping ssm : mappings) { @@ -104,6 +308,7 @@ public class AlignedCodonFrame * otherwise, add a new sequence mapping */ Mapping mp = new Mapping(toSeq, map); + mp.setMappedFromId(mapFromId); mappings.add(new SequenceToSequenceMapping(fromSeq, mp)); } @@ -111,7 +316,7 @@ public class AlignedCodonFrame { // TODO return a list instead? // return dnaSeqs; - List seqs = new ArrayList(); + List seqs = new ArrayList<>(); for (SequenceToSequenceMapping ssm : mappings) { seqs.add(ssm.fromSeq); @@ -122,7 +327,7 @@ public class AlignedCodonFrame public SequenceI[] getAaSeqs() { // TODO not used - remove? - List seqs = new ArrayList(); + List seqs = new ArrayList<>(); for (SequenceToSequenceMapping ssm : mappings) { seqs.add(ssm.mapping.to); @@ -132,7 +337,7 @@ public class AlignedCodonFrame public MapList[] getdnaToProt() { - List maps = new ArrayList(); + List maps = new ArrayList<>(); for (SequenceToSequenceMapping ssm : mappings) { maps.add(ssm.mapping.map); @@ -142,7 +347,7 @@ public class AlignedCodonFrame public Mapping[] getProtMappings() { - List maps = new ArrayList(); + List maps = new ArrayList<>(); for (SequenceToSequenceMapping ssm : mappings) { maps.add(ssm.mapping); @@ -152,7 +357,7 @@ public class AlignedCodonFrame /** * Returns the first mapping found which is to or from the given sequence, or - * null. + * null if none is found * * @param seq * @return @@ -193,9 +398,12 @@ public class AlignedCodonFrame } /** + * Return the corresponding aligned or dataset dna sequence for given amino + * acid sequence, or null if not found. returns the sequence from the first + * mapping found that involves the protein sequence. * - * @param sequenceRef - * @return null or corresponding aaSeq entry for dnaSeq entry + * @param aaSeqRef + * @return */ public SequenceI getDnaForAaSeq(SequenceI aaSeqRef) { @@ -225,7 +433,8 @@ public class AlignedCodonFrame /** * Add search results for regions in other sequences that translate or are - * translated from a particular position in seq + * translated from a particular position in seq (which may be an aligned or + * dataset sequence) * * @param seq * @param index @@ -234,36 +443,16 @@ public class AlignedCodonFrame * where highlighted regions go */ public void markMappedRegion(SequenceI seq, int index, - SearchResults results) + SearchResultsI results) { - int[] codon; SequenceI ds = seq.getDatasetSequence(); + if (ds == null) + { + ds = seq; + } for (SequenceToSequenceMapping ssm : mappings) { - if (ssm.fromSeq == seq || ssm.fromSeq == ds) - { - codon = ssm.mapping.map.locateInTo(index, index); - if (codon != null) - { - for (int i = 0; i < codon.length; i += 2) - { - results.addResult(ssm.mapping.to, codon[i], codon[i + 1]); - } - } - } - else if (ssm.mapping.to == seq || ssm.mapping.to == ds) - { - { - codon = ssm.mapping.map.locateInFrom(index, index); - if (codon != null) - { - for (int i = 0; i < codon.length; i += 2) - { - results.addResult(ssm.fromSeq, codon[i], codon[i + 1]); - } - } - } - } + ssm.markMappedRegion(ds, index, results); } } @@ -303,7 +492,8 @@ public class AlignedCodonFrame /** * Convenience method to return the first aligned sequence in the given - * alignment whose dataset has a mapping with the given dataset sequence. + * alignment whose dataset has a mapping with the given (aligned or dataset) + * sequence. * * @param seq * @@ -312,17 +502,39 @@ public class AlignedCodonFrame */ public SequenceI findAlignedSequence(SequenceI seq, AlignmentI al) { + return findAlignedSequence(seq, al, null); + } + /** + * Convenience method to return the first aligned sequence in the given + * alignment whose dataset has a mapping with the given (aligned or dataset) + * sequence, and optionally the mapping that relates them + * + * @param seq + * @param al + * @param map - list to add the mapping to + * @return sequence from al that maps to seq + */ + public SequenceI findAlignedSequence(SequenceI seq, AlignmentI al,List map) + { /* * Search mapped protein ('to') sequences first. */ for (SequenceToSequenceMapping ssm : mappings) { - if (ssm.fromSeq == seq) + int mStart=ssm.getMapping().getMap().getFromLowest(),mEnd=ssm.getMapping().map.getFromHighest(); + if ((ssm.fromSeq == seq || ssm.fromSeq == seq.getDatasetSequence()) + // here AlignmentUtilsTest. testAlignProteinAsDna_incompleteStartCodon fails because mStart/mEnd is contained by seq + // without this filter, we don't get the correct mapping, however + )// && seq.getStart()>=mStart && seq.getEnd()<=mEnd) { for (SequenceI sourceAligned : al.getSequences()) { - if (ssm.mapping.to == sourceAligned.getDatasetSequence()) + if (ssm.covers(sourceAligned,true,false)) { + if (map != null) + { + map.add(ssm); + } return sourceAligned; } } @@ -334,12 +546,19 @@ public class AlignedCodonFrame */ for (SequenceToSequenceMapping ssm : mappings) { - if (ssm.mapping.to == seq) + int mStart=ssm.getMapping().getMap().getToLowest(),mEnd=ssm.getMapping().map.getToHighest(); + if ((ssm.mapping.to == seq + || ssm.mapping.to == seq.getDatasetSequence()) + && seq.getStart()>=mStart && seq.getEnd()<=mEnd) { for (SequenceI sourceAligned : al.getSequences()) { - if (ssm.fromSeq == sourceAligned.getDatasetSequence()) + if (ssm.covers(sourceAligned,true,true)) { + if (map != null) + { + map.add(ssm); + } return sourceAligned; } } @@ -364,8 +583,8 @@ public class AlignedCodonFrame { SequenceI targetDs = target.getDatasetSequence() == null ? target : target.getDatasetSequence(); - SequenceI queryDs = query.getDatasetSequence() == null ? query : query - .getDatasetSequence(); + SequenceI queryDs = query.getDatasetSequence() == null ? query + : query.getDatasetSequence(); if (targetDs == null || queryDs == null /*|| dnaToProt == null*/) { return null; @@ -414,11 +633,12 @@ public class AlignedCodonFrame { MapList ml = null; SequenceI dnaSeq = null; - List result = new ArrayList(); + List result = new ArrayList<>(); for (SequenceToSequenceMapping ssm : mappings) { - if (ssm.mapping.to == protein) + if (ssm.mapping.to == protein + && ssm.mapping.getMap().getFromRatio() == 3) { ml = ssm.mapping.map; dnaSeq = ssm.fromSeq; @@ -433,33 +653,34 @@ public class AlignedCodonFrame * Read off the mapped nucleotides (converting to position base 0) */ codonPos = MappingUtils.flattenRanges(codonPos); - char[] dna = dnaSeq.getSequence(); int start = dnaSeq.getStart(); - result.add(new char[] { dna[codonPos[0] - start], - dna[codonPos[1] - start], dna[codonPos[2] - start] }); + char c1 = dnaSeq.getCharAt(codonPos[0] - start); + char c2 = dnaSeq.getCharAt(codonPos[1] - start); + char c3 = dnaSeq.getCharAt(codonPos[2] - start); + result.add(new char[] { c1, c2, c3 }); } } return result.isEmpty() ? null : result; } /** - * Returns any mappings found which are to (or from) the given sequence, and - * to distinct sequences. + * Returns any mappings found which are from the given sequence, and to + * distinct sequences. * * @param seq * @return */ - public List getMappingsForSequence(SequenceI seq) + public List getMappingsFromSequence(SequenceI seq) { - List result = new ArrayList(); - List related = new ArrayList(); + List result = new ArrayList<>(); + List related = new ArrayList<>(); SequenceI seqDs = seq.getDatasetSequence(); seqDs = seqDs != null ? seqDs : seq; for (SequenceToSequenceMapping ssm : mappings) { final Mapping mapping = ssm.mapping; - if (ssm.fromSeq == seqDs || mapping.to == seqDs) + if (ssm.fromSeq == seqDs) { if (!related.contains(mapping.to)) { @@ -508,8 +729,9 @@ public class AlignedCodonFrame */ protected int realiseWith(SequenceI seq, boolean doUpdate) { - SequenceI ds = seq.getDatasetSequence() != null ? seq - .getDatasetSequence() : seq; + SequenceI ds = seq.getDatasetSequence() != null + ? seq.getDatasetSequence() + : seq; int count = 0; /* @@ -583,8 +805,8 @@ public class AlignedCodonFrame { int start = replacement.getStart(); int end = replacement.getEnd(); - boolean mappingOverlapsSequence = (mapStart >= start && mapStart <= end) - || (mapEnd >= start && mapEnd <= end); + boolean mappingOverlapsSequence = (mapStart >= start + && mapStart <= end) || (mapEnd >= start && mapEnd <= end); if (mappingOverlapsSequence) { return true; @@ -637,4 +859,127 @@ public class AlignedCodonFrame { return mappings.isEmpty(); } + + /** + * Method for debug / inspection purposes only, may change in future + */ + @Override + public String toString() + { + return mappings == null ? "null" : mappings.toString(); + } + + /** + * Returns the first mapping found that is between 'fromSeq' and 'toSeq', or + * null if none found + * + * @param fromSeq + * aligned or dataset sequence + * @param toSeq + * aligned or dataset sequence + * @return + */ + public Mapping getMappingBetween(SequenceI fromSeq, SequenceI toSeq) + { + SequenceI dssFrom = fromSeq.getDatasetSequence() == null ? fromSeq + : fromSeq.getDatasetSequence(); + SequenceI dssTo = toSeq.getDatasetSequence() == null ? toSeq + : toSeq.getDatasetSequence(); + + for (SequenceToSequenceMapping mapping : mappings) + { + SequenceI from = mapping.fromSeq; + SequenceI to = mapping.mapping.to; + if ((from == dssFrom && to == dssTo) + || (from == dssTo && to == dssFrom)) + { + return mapping.mapping; + } + } + return null; + } + + /** + * Returns a hashcode derived from the list of sequence mappings + * + * @see SequenceToSequenceMapping#hashCode() + * @see AbstractList#hashCode() + */ + @Override + public int hashCode() + { + return this.mappings.hashCode(); + } + + /** + * Two AlignedCodonFrame objects are equal if they hold the same ordered list + * of mappings + * + * @see SequenceToSequenceMapping#equals + */ + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof AlignedCodonFrame)) + { + return false; + } + return this.mappings.equals(((AlignedCodonFrame) obj).mappings); + } + + public List getMappings() + { + return mappings; + } + + /** + * Returns the first mapping found which is between the two given sequences, + * and covers the full extent of both. + * + * @param seq1 + * @param seq2 + * @return + */ + public SequenceToSequenceMapping getCoveringMapping(SequenceI seq1, + SequenceI seq2) + { + for (SequenceToSequenceMapping mapping : mappings) + { + if (mapping.covers(seq2) && mapping.covers(seq1)) + { + return mapping; + } + } + return null; + } + + /** + * Returns the first mapping found which is between the given dataset sequence + * and another, is a triplet mapping (3:1 or 1:3), and covers the full extent + * of both sequences involved + * + * @param seq + * @return + */ + public SequenceToSequenceMapping getCoveringCodonMapping(SequenceI seq) + { + for (SequenceToSequenceMapping mapping : mappings) + { + if (mapping.getMapping().getMap().isTripletMap() + && mapping.covers(seq)) + { + if (mapping.fromSeq == seq + && mapping.covers(mapping.getMapping().getTo())) + { + return mapping; + } + else if (mapping.getMapping().getTo() == seq + && mapping.covers(mapping.fromSeq)) + { + return mapping; + } + } + } + return null; + } }