import jalview.datamodel.DBRefEntry;
import jalview.datamodel.DBRefSource;
import jalview.datamodel.FeatureProperties;
+import jalview.datamodel.IncompleteCodonException;
import jalview.datamodel.Mapping;
import jalview.datamodel.SearchResults;
import jalview.datamodel.Sequence;
import jalview.io.gff.SequenceOntologyFactory;
import jalview.io.gff.SequenceOntologyI;
import jalview.schemes.ResidueProperties;
+import jalview.util.Comparison;
import jalview.util.DBRefUtils;
import jalview.util.MapList;
import jalview.util.MappingUtils;
+import jalview.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
}
else
{
- MapList map = mapProteinSequenceToCdna(aaSeq, cdnaSeq);
+ MapList map = mapCdnaToProtein(aaSeq, cdnaSeq);
if (map != null)
{
acf.addMap(cdnaSeq, aaSeq, map);
}
/**
- * Build a mapping (if possible) of a protein to a cDNA sequence. The cDNA
- * must be three times the length of the protein, possibly after ignoring
- * start and/or stop codons, and must translate to the protein. Returns null
- * if no mapping is determined.
+ * Builds a mapping (if possible) of a cDNA to a protein sequence.
+ * <ul>
+ * <li>first checks if the cdna translates exactly to the protein sequence</li>
+ * <li>else checks for translation after removing a STOP codon</li>
+ * <li>else checks for translation after removing a START codon</li>
+ * <li>if that fails, inspect CDS features on the cDNA sequence</li>
+ * </ul>
+ * Returns null if no mapping is determined.
*
- * @param proteinSeqs
+ * @param proteinSeq
+ * the aligned protein sequence
* @param cdnaSeq
+ * the aligned cdna sequence
* @return
*/
- public static MapList mapProteinSequenceToCdna(SequenceI proteinSeq,
+ public static MapList mapCdnaToProtein(SequenceI proteinSeq,
SequenceI cdnaSeq)
{
/*
final int proteinEnd = proteinSeq.getEnd();
/*
- * If lengths don't match, try ignoring stop codon.
+ * If lengths don't match, try ignoring stop codon (if present)
*/
if (cdnaLength != mappedLength && cdnaLength > 2)
{
cdnaLength -= 3;
}
- if (cdnaLength != mappedLength)
- {
- return null;
- }
- if (!translatesAs(cdnaSeqChars, startOffset, aaSeqChars))
+ if (translatesAs(cdnaSeqChars, startOffset, aaSeqChars))
{
- return null;
+ /*
+ * protein is translation of dna (+/- start/stop codons)
+ */
+ MapList map = new MapList(new int[] { cdnaStart, cdnaEnd }, new int[]
+ { proteinStart, proteinEnd }, 3, 1);
+ return map;
}
- MapList map = new MapList(new int[] { cdnaStart, cdnaEnd }, new int[] {
- proteinStart, proteinEnd }, 3, 1);
- return map;
+
+ /*
+ * translation failed - try mapping CDS annotated regions of dna
+ */
+ return mapCdsToProtein(cdnaSeq, proteinSeq);
}
/**
return false;
}
- int aaResidue = 0;
- for (int i = cdnaStart; i < cdnaSeqChars.length - 2
- && aaResidue < aaSeqChars.length; i += 3, aaResidue++)
+ int aaPos = 0;
+ int dnaPos = cdnaStart;
+ for (; dnaPos < cdnaSeqChars.length - 2
+ && aaPos < aaSeqChars.length; dnaPos += 3, aaPos++)
{
- String codon = String.valueOf(cdnaSeqChars, i, 3);
+ String codon = String.valueOf(cdnaSeqChars, dnaPos, 3);
final String translated = ResidueProperties.codonTranslate(codon);
+
/*
* allow * in protein to match untranslatable in dna
*/
- final char aaRes = aaSeqChars[aaResidue];
+ final char aaRes = aaSeqChars[aaPos];
if ((translated == null || "STOP".equals(translated)) && aaRes == '*')
{
continue;
return false;
}
}
- // fail if we didn't match all of the aa sequence
- return (aaResidue == aaSeqChars.length);
+
+ /*
+ * check we matched all of the protein sequence
+ */
+ if (aaPos != aaSeqChars.length)
+ {
+ return false;
+ }
+
+ /*
+ * check we matched all of the dna except
+ * for optional trailing STOP codon
+ */
+ if (dnaPos == cdnaSeqChars.length)
+ {
+ return true;
+ }
+ if (dnaPos == cdnaSeqChars.length - 3)
+ {
+ String codon = String.valueOf(cdnaSeqChars, dnaPos, 3);
+ if ("STOP".equals(ResidueProperties.codonTranslate(codon)))
+ {
+ return true;
+ }
+ }
+ return false;
}
/**
public static int alignProteinAsDna(AlignmentI protein, AlignmentI dna)
{
List<SequenceI> unmappedProtein = new ArrayList<SequenceI>();
+ Map<AlignedCodon, Map<SequenceI, AlignedCodon>> alignedCodons = buildCodonColumnsMap(
+ protein, dna, unmappedProtein);
+ return alignProteinAs(protein, alignedCodons, unmappedProtein);
+ }
+
+ /**
+ * Builds a map whose key is an aligned codon position (3 alignment column
+ * numbers base 0), and whose value is a map from protein sequence to each
+ * protein's peptide residue for that codon. The map generates an ordering of
+ * the codons, and allows us to read off the peptides at each position in
+ * order to assemble 'aligned' protein sequences.
+ *
+ * @param protein
+ * the protein alignment
+ * @param dna
+ * the coding dna alignment
+ * @param unmappedProtein
+ * any unmapped proteins are added to this list
+ * @return
+ */
+ protected static Map<AlignedCodon, Map<SequenceI, AlignedCodon>> buildCodonColumnsMap(
+ AlignmentI protein, AlignmentI dna,
+ List<SequenceI> unmappedProtein)
+ {
+ /*
+ * maintain a list of any proteins with no mappings - these will be
+ * rendered 'as is' in the protein alignment as we can't align them
+ */
unmappedProtein.addAll(protein.getSequences());
List<AlignedCodonFrame> mappings = protein.getCodonFrames();
* {dnaSequence, {proteinSequence, codonProduct}} at that position. The
* comparator keeps the codon positions ordered.
*/
- Map<AlignedCodon, Map<SequenceI, String>> alignedCodons = new TreeMap<AlignedCodon, Map<SequenceI, String>>(
+ Map<AlignedCodon, Map<SequenceI, AlignedCodon>> alignedCodons = new TreeMap<AlignedCodon, Map<SequenceI, AlignedCodon>>(
new CodonComparator());
+
for (SequenceI dnaSeq : dna.getSequences())
{
for (AlignedCodonFrame mapping : mappings)
{
- Mapping seqMap = mapping.getMappingForSequence(dnaSeq);
SequenceI prot = mapping.findAlignedSequence(
dnaSeq.getDatasetSequence(), protein);
if (prot != null)
{
+ Mapping seqMap = mapping.getMappingForSequence(dnaSeq);
addCodonPositions(dnaSeq, prot, protein.getGapCharacter(),
seqMap, alignedCodons);
unmappedProtein.remove(prot);
}
}
}
- return alignProteinAs(protein, alignedCodons, unmappedProtein);
+
+ /*
+ * Finally add any unmapped peptide start residues (e.g. for incomplete
+ * codons) as if at the codon position before the second residue
+ */
+ int mappedSequenceCount = protein.getHeight() - unmappedProtein.size();
+ addUnmappedPeptideStarts(alignedCodons, mappedSequenceCount);
+
+ return alignedCodons;
+ }
+
+ /**
+ * Scans for any protein mapped from position 2 (meaning unmapped start
+ * position e.g. an incomplete codon), and synthesizes a 'codon' for it at the
+ * preceding position in the alignment
+ *
+ * @param alignedCodons
+ * the codon-to-peptide map
+ * @param mappedSequenceCount
+ * the number of distinct sequences in the map
+ */
+ protected static void addUnmappedPeptideStarts(
+ Map<AlignedCodon, Map<SequenceI, AlignedCodon>> alignedCodons,
+ int mappedSequenceCount)
+ {
+ // TODO delete this ugly hack once JAL-2022 is resolved
+ // i.e. we can model startPhase > 0 (incomplete start codon)
+
+ List<SequenceI> sequencesChecked = new ArrayList<SequenceI>();
+ AlignedCodon lastCodon = null;
+ Map<SequenceI, AlignedCodon> toAdd = new HashMap<SequenceI, AlignedCodon>();
+
+ for (Entry<AlignedCodon, Map<SequenceI, AlignedCodon>> entry : alignedCodons
+ .entrySet())
+ {
+ for (Entry<SequenceI, AlignedCodon> sequenceCodon : entry.getValue()
+ .entrySet())
+ {
+ SequenceI seq = sequenceCodon.getKey();
+ if (sequencesChecked.contains(seq))
+ {
+ continue;
+ }
+ sequencesChecked.add(seq);
+ AlignedCodon codon = sequenceCodon.getValue();
+ if (codon.peptideCol > 1)
+ {
+ System.err
+ .println("Problem mapping protein with >1 unmapped start positions: "
+ + seq.getName());
+ }
+ else if (codon.peptideCol == 1)
+ {
+ /*
+ * first position (peptideCol == 0) was unmapped - add it
+ */
+ if (lastCodon != null)
+ {
+ AlignedCodon firstPeptide = new AlignedCodon(lastCodon.pos1,
+ lastCodon.pos2, lastCodon.pos3, String.valueOf(seq
+ .getCharAt(0)), 0);
+ toAdd.put(seq, firstPeptide);
+ }
+ else
+ {
+ /*
+ * unmapped residue at start of alignment (no prior column) -
+ * 'insert' at nominal codon [0, 0, 0]
+ */
+ AlignedCodon firstPeptide = new AlignedCodon(0, 0, 0,
+ String.valueOf(seq.getCharAt(0)), 0);
+ toAdd.put(seq, firstPeptide);
+ }
+ }
+ if (sequencesChecked.size() == mappedSequenceCount)
+ {
+ // no need to check past first mapped position in all sequences
+ break;
+ }
+ }
+ lastCodon = entry.getKey();
+ }
+
+ /*
+ * add any new codons safely after iterating over the map
+ */
+ for (Entry<SequenceI, AlignedCodon> startCodon : toAdd.entrySet())
+ {
+ addCodonToMap(alignedCodons, startCodon.getValue(),
+ startCodon.getKey());
+ }
}
/**
* @return
*/
protected static int alignProteinAs(AlignmentI protein,
- Map<AlignedCodon, Map<SequenceI, String>> alignedCodons,
+ Map<AlignedCodon, Map<SequenceI, AlignedCodon>> alignedCodons,
List<SequenceI> unmappedProtein)
{
/*
int column = 0;
for (AlignedCodon codon : alignedCodons.keySet())
{
- final Map<SequenceI, String> columnResidues = alignedCodons
+ final Map<SequenceI, AlignedCodon> columnResidues = alignedCodons
.get(codon);
- for (Entry<SequenceI, String> entry : columnResidues.entrySet())
+ for (Entry<SequenceI, AlignedCodon> entry : columnResidues.entrySet())
{
// place translated codon at its column position in sequence
- entry.getKey().getSequence()[column] = entry.getValue().charAt(0);
+ entry.getKey().getSequence()[column] = entry.getValue().product
+ .charAt(0);
}
column++;
}
*/
static void addCodonPositions(SequenceI dna, SequenceI protein,
char gapChar, Mapping seqMap,
- Map<AlignedCodon, Map<SequenceI, String>> alignedCodons)
+ Map<AlignedCodon, Map<SequenceI, AlignedCodon>> alignedCodons)
{
Iterator<AlignedCodon> codons = seqMap.getCodonIterator(dna, gapChar);
+
+ /*
+ * add codon positions, and their peptide translations, to the alignment
+ * map, while remembering the first codon mapped
+ */
while (codons.hasNext())
{
- AlignedCodon codon = codons.next();
- Map<SequenceI, String> seqProduct = alignedCodons.get(codon);
- if (seqProduct == null)
+ try
+ {
+ AlignedCodon codon = codons.next();
+ addCodonToMap(alignedCodons, codon, protein);
+ } catch (IncompleteCodonException e)
{
- seqProduct = new HashMap<SequenceI, String>();
- alignedCodons.put(codon, seqProduct);
+ // possible incomplete trailing codon - ignore
+ } catch (NoSuchElementException e)
+ {
+ // possibly peptide lacking STOP
}
- seqProduct.put(protein, codon.product);
}
}
/**
+ * Helper method to add a codon-to-peptide entry to the aligned codons map
+ *
+ * @param alignedCodons
+ * @param codon
+ * @param protein
+ */
+ protected static void addCodonToMap(
+ Map<AlignedCodon, Map<SequenceI, AlignedCodon>> alignedCodons,
+ AlignedCodon codon, SequenceI protein)
+ {
+ Map<SequenceI, AlignedCodon> seqProduct = alignedCodons.get(codon);
+ if (seqProduct == null)
+ {
+ seqProduct = new HashMap<SequenceI, AlignedCodon>();
+ alignedCodons.put(codon, seqProduct);
+ }
+ seqProduct.put(protein, codon);
+ }
+
+ /**
* Returns true if a cDNA/Protein mapping either exists, or could be made,
* between at least one pair of sequences in the two alignments. Currently,
* the logic is:
* Just try to make a mapping (it is not yet stored), test whether
* successful.
*/
- return mapProteinSequenceToCdna(proteinDs, dnaDs) != null;
+ return mapCdnaToProtein(proteinDs, dnaDs) != null;
}
/**
}
/**
- * Constructs an alignment consisting of the mapped cds regions in the given
- * nucleotide sequences, and updates mappings to match.
+ * Constructs an alignment consisting of the mapped (CDS) regions in the given
+ * nucleotide sequences, and updates mappings to match. The new sequences are
+ * aligned as per the original sequence, with entirely gapped columns (codon
+ * interrupted by intron) omitted.
*
* @param dna
* aligned dna sequences
* @param mappings
* from dna to protein; these are replaced with new mappings
+ * @param al
* @return an alignment whose sequences are the cds-only parts of the dna
- * sequences (or null if no cds are found)
+ * sequences (or null if no mappings are found)
*/
public static AlignmentI makeCdsAlignment(SequenceI[] dna,
- List<AlignedCodonFrame> mappings)
+ List<AlignedCodonFrame> mappings, AlignmentI al)
{
+ List<int[]> cdsColumns = findCdsColumns(dna);
+
+ /*
+ * create CDS sequences and new mappings
+ * (from cdna to cds, and cds to peptide)
+ */
List<AlignedCodonFrame> newMappings = new ArrayList<AlignedCodonFrame>();
List<SequenceI> cdsSequences = new ArrayList<SequenceI>();
+ char gap = al.getGapCharacter();
for (SequenceI dnaSeq : dna)
{
for (AlignedCodonFrame acf : seqMappings)
{
AlignedCodonFrame newMapping = new AlignedCodonFrame();
- final List<SequenceI> mappedCds = makeCdsSequences(ds, acf,
- newMapping);
+ final List<SequenceI> mappedCds = makeCdsSequences(dnaSeq, acf,
+ cdsColumns, newMapping, gap);
if (!mappedCds.isEmpty())
{
cdsSequences.addAll(mappedCds);
}
}
}
- AlignmentI al = new Alignment(
+ AlignmentI newAl = new Alignment(
cdsSequences.toArray(new SequenceI[cdsSequences.size()]));
- al.setDataset(null);
+
+ /*
+ * add new sequences to the shared dataset, set it on the new alignment
+ */
+ List<SequenceI> dsseqs = al.getDataset().getSequences();
+ for (SequenceI seq : newAl.getSequences())
+ {
+ if (!dsseqs.contains(seq.getDatasetSequence()))
+ {
+ dsseqs.add(seq.getDatasetSequence());
+ }
+ }
+ newAl.setDataset(al.getDataset());
/*
* Replace the old mappings with the new ones
mappings.clear();
mappings.addAll(newMappings);
- return al;
+ return newAl;
+ }
+
+ /**
+ * Returns a consolidated list of column ranges where at least one sequence
+ * has a CDS feature. This assumes CDS features are on genomic sequence i.e.
+ * are for contiguous CDS ranges (no gaps).
+ *
+ * @param seqs
+ * @return
+ */
+ public static List<int[]> findCdsColumns(SequenceI[] seqs)
+ {
+ // TODO use refactored code from AlignViewController
+ // markColumnsContainingFeatures, not reinvent the wheel!
+
+ List<int[]> result = new ArrayList<int[]>();
+ for (SequenceI seq : seqs)
+ {
+ result.addAll(findCdsColumns(seq));
+ }
+
+ /*
+ * sort and compact the list into ascending, non-overlapping ranges
+ */
+ Collections.sort(result, new Comparator<int[]>()
+ {
+ @Override
+ public int compare(int[] o1, int[] o2)
+ {
+ return Integer.compare(o1[0], o2[0]);
+ }
+ });
+ result = MapList.coalesceRanges(result);
+
+ return result;
+ }
+
+ public static List<int[]> findCdsColumns(SequenceI seq)
+ {
+ List<int[]> result = new ArrayList<int[]>();
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ SequenceFeature[] sfs = seq.getSequenceFeatures();
+ if (sfs != null)
+ {
+ for (SequenceFeature sf : sfs)
+ {
+ if (so.isA(sf.getType(), SequenceOntologyI.CDS))
+ {
+ int colStart = seq.findIndex(sf.getBegin());
+ int colEnd = seq.findIndex(sf.getEnd());
+ result.add(new int[] { colStart, colEnd });
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Answers true if all sequences have a gap at (or do not extend to) the
+ * specified column position (base 1)
+ *
+ * @param seqs
+ * @param col
+ * @return
+ */
+ public static boolean isGappedColumn(List<SequenceI> seqs, int col)
+ {
+ if (seqs != null)
+ {
+ for (SequenceI seq : seqs)
+ {
+ if (!Comparison.isGap(seq.getCharAt(col - 1)))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the column ranges (base 1) of each aligned sequence that are
+ * involved in any mapping. This is a helper method for aligning protein
+ * products of aligned transcripts.
+ *
+ * @param mappedSequences
+ * (possibly gapped) dna sequences
+ * @param mappings
+ * @return
+ */
+ protected static List<List<int[]>> getMappedColumns(
+ List<SequenceI> mappedSequences, List<AlignedCodonFrame> mappings)
+ {
+ List<List<int[]>> result = new ArrayList<List<int[]>>();
+ for (SequenceI seq : mappedSequences)
+ {
+ List<int[]> columns = new ArrayList<int[]>();
+ List<AlignedCodonFrame> seqMappings = MappingUtils
+ .findMappingsForSequence(seq, mappings);
+ for (AlignedCodonFrame mapping : seqMappings)
+ {
+ List<Mapping> maps = mapping.getMappingsForSequence(seq);
+ for (Mapping map : maps)
+ {
+ /*
+ * Get the codon regions as { [2, 5], [7, 12], [14, 14] etc }
+ * Find and add the overall aligned column range for each
+ */
+ for (int[] cdsRange : map.getMap().getFromRanges())
+ {
+ int startPos = cdsRange[0];
+ int endPos = cdsRange[1];
+ int startCol = seq.findIndex(startPos);
+ int endCol = seq.findIndex(endPos);
+ columns.add(new int[] { startCol, endCol });
+ }
+ }
+ }
+ result.add(columns);
+ }
+ return result;
}
/**
* (example EMBL KF591215).
*
* @param dnaSeq
- * a dna dataset sequence
+ * a dna aligned sequence
* @param mapping
* containing one or more mappings of the sequence to protein
+ * @param ungappedCdsColumns
* @param newMappings
* the new mapping to populate, from the cds-only sequences to their
* mapped protein sequences
* @return
*/
protected static List<SequenceI> makeCdsSequences(SequenceI dnaSeq,
- AlignedCodonFrame mapping, AlignedCodonFrame newMappings)
+ AlignedCodonFrame mapping, List<int[]> ungappedCdsColumns,
+ AlignedCodonFrame newMappings, char gapChar)
{
List<SequenceI> cdsSequences = new ArrayList<SequenceI>();
List<Mapping> seqMappings = mapping.getMappingsForSequence(dnaSeq);
for (Mapping seqMapping : seqMappings)
{
- SequenceI cds = makeCdsSequence(dnaSeq, seqMapping);
+ SequenceI cds = makeCdsSequence(dnaSeq, seqMapping,
+ ungappedCdsColumns, gapChar);
+ cds.createDatasetSequence();
cdsSequences.add(cds);
/*
* add new mappings, from dna to cds, and from cds to peptide
*/
- MapList dnaToCds = addCdsMappings(dnaSeq, cds, seqMapping,
- newMappings);
+ MapList dnaToCds = addCdsMappings(dnaSeq.getDatasetSequence(), cds,
+ seqMapping, newMappings);
/*
* transfer any features on dna that overlap the CDS
*/
- transferFeatures(dnaSeq, cds, dnaToCds, null, "CDS" /* SequenceOntology.CDS */);
+ transferFeatures(dnaSeq, cds, dnaToCds, null, SequenceOntologyI.CDS);
}
return cdsSequences;
}
* @return
*/
protected static MapList addCdsMappings(SequenceI dnaSeq,
- SequenceI cdsSeq,
- Mapping dnaMapping, AlignedCodonFrame newMappings)
+ SequenceI cdsSeq, Mapping dnaMapping,
+ AlignedCodonFrame newMappings)
{
cdsSeq.createDatasetSequence();
* the peptide ranges taken unchanged from the dna mapping
*/
List<int[]> cdsRanges = new ArrayList<int[]>();
- cdsRanges.add(new int[] { 1, cdsSeq.getLength() });
+ SequenceI cdsDataset = cdsSeq.getDatasetSequence();
+ cdsRanges.add(new int[] { 1, cdsDataset.getLength() });
MapList cdsToPeptide = new MapList(cdsRanges, dnaMapping.getMap()
.getToRanges(), 3, 1);
- newMappings.addMap(cdsSeq.getDatasetSequence(), dnaMapping.getTo(),
- cdsToPeptide);
+ newMappings.addMap(cdsDataset, dnaMapping.getTo(), cdsToPeptide);
/*
* dna 'from' ranges map 1:1 to the contiguous extracted CDS
*/
- MapList dnaToCds = new MapList(
- dnaMapping.getMap().getFromRanges(), cdsRanges, 1, 1);
- newMappings.addMap(dnaSeq, cdsSeq.getDatasetSequence(), dnaToCds);
+ MapList dnaToCds = new MapList(dnaMapping.getMap().getFromRanges(),
+ cdsRanges, 1, 1);
+ newMappings.addMap(dnaSeq, cdsDataset, dnaToCds);
return dnaToCds;
}
* nucleotide sequence
* @param seqMapping
* mappings from CDS regions of nucleotide
+ * @param ungappedCdsColumns
* @return
*/
protected static SequenceI makeCdsSequence(SequenceI dnaSeq,
- Mapping seqMapping)
+ Mapping seqMapping, List<int[]> ungappedCdsColumns, char gapChar)
{
- StringBuilder newSequence = new StringBuilder(dnaSeq.getLength());
- final char[] dna = dnaSeq.getSequence();
- int offset = dnaSeq.getStart() - 1;
+ int cdsWidth = MappingUtils.getLength(ungappedCdsColumns);
/*
- * Get the codon regions as { [2, 5], [7, 12], [14, 14] etc }
+ * populate CDS columns with the aligned
+ * column character if that column is mapped (which may be a gap
+ * if an intron interrupts a codon), else with a gap
*/
- final List<int[]> dnaCdsRanges = seqMapping.getMap().getFromRanges();
- for (int[] range : dnaCdsRanges)
+ List<int[]> fromRanges = seqMapping.getMap().getFromRanges();
+ char[] cdsChars = new char[cdsWidth];
+ int pos = 0;
+ for (int[] columns : ungappedCdsColumns)
{
- // TODO handle reverse mapping as well (range[1] < range[0])
- for (int pos = range[0]; pos <= range[1]; pos++)
+ for (int i = columns[0]; i <= columns[1]; i++)
{
- newSequence.append(dna[pos - offset - 1]);
+ char dnaChar = dnaSeq.getCharAt(i - 1);
+ if (Comparison.isGap(dnaChar))
+ {
+ cdsChars[pos] = gapChar;
+ }
+ else
+ {
+ int seqPos = dnaSeq.findPosition(i - 1);
+ if (MappingUtils.contains(fromRanges, seqPos))
+ {
+ cdsChars[pos] = dnaChar;
+ }
+ else
+ {
+ cdsChars[pos] = gapChar;
+ }
+ }
+ pos++;
}
}
+ SequenceI cdsSequence = new Sequence(dnaSeq.getName(),
+ String.valueOf(cdsChars));
- SequenceI cds = new Sequence(dnaSeq.getName(),
- newSequence.toString());
+ transferDbRefs(seqMapping.getTo(), cdsSequence);
- transferDbRefs(seqMapping.getTo(), cds);
-
- return cds;
+ return cdsSequence;
}
/**
to.setName(to.getName() + "|" + cdsAccId);
}
}
+
+ /**
+ * Returns a mapping from dna to protein by inspecting sequence features of
+ * type "CDS" on the dna.
+ *
+ * @param dnaSeq
+ * @param proteinSeq
+ * @return
+ */
+ public static MapList mapCdsToProtein(SequenceI dnaSeq,
+ SequenceI proteinSeq)
+ {
+ List<int[]> ranges = findCdsPositions(dnaSeq);
+ int mappedDnaLength = MappingUtils.getLength(ranges);
+
+ int proteinLength = proteinSeq.getLength();
+ int proteinStart = proteinSeq.getStart();
+ int proteinEnd = proteinSeq.getEnd();
+
+ /*
+ * incomplete start codon may mean X at start of peptide
+ * we ignore both for mapping purposes
+ */
+ if (proteinSeq.getCharAt(0) == 'X')
+ {
+ // todo JAL-2022 support startPhase > 0
+ proteinStart++;
+ proteinLength--;
+ }
+ List<int[]> proteinRange = new ArrayList<int[]>();
+
+ /*
+ * dna length should map to protein (or protein plus stop codon)
+ */
+ int codesForResidues = mappedDnaLength / 3;
+ if (codesForResidues == (proteinLength + 1))
+ {
+ // assuming extra codon is for STOP and not in peptide
+ codesForResidues--;
+ }
+ if (codesForResidues == proteinLength)
+ {
+ proteinRange.add(new int[] { proteinStart, proteinEnd });
+ return new MapList(ranges, proteinRange, 3, 1);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of CDS ranges found (as sequence positions base 1), i.e. of
+ * start/end positions of sequence features of type "CDS" (or a sub-type of
+ * CDS in the Sequence Ontology)
+ *
+ * @param dnaSeq
+ * @return
+ */
+ public static List<int[]> findCdsPositions(SequenceI dnaSeq)
+ {
+ List<int[]> result = new ArrayList<int[]>();
+ SequenceFeature[] sfs = dnaSeq.getSequenceFeatures();
+ if (sfs == null)
+ {
+ return result;
+ }
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ for (SequenceFeature sf : sfs)
+ {
+ /*
+ * process a CDS feature (or a sub-type of CDS)
+ */
+ if (so.isA(sf.getType(), SequenceOntologyI.CDS))
+ {
+ int phase = 0;
+ try {
+ phase = Integer.parseInt(sf.getPhase());
+ } catch (NumberFormatException e)
+ {
+ // ignore
+ }
+ /*
+ * phase > 0 on first codon means 5' incomplete - skip to the start
+ * of the next codon; example ENST00000496384
+ */
+ int begin = sf.getBegin();
+ int end = sf.getEnd();
+ if (result.isEmpty())
+ {
+ // TODO JAL-2022 support start phase > 0
+ begin += phase;
+ if (begin > end)
+ {
+ continue; // shouldn't happen?
+ }
+ }
+ result.add(new int[] { begin, end });
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Maps exon features from dna to protein, and computes variants in peptide
+ * product generated by variants in dna, and adds them as sequence_variant
+ * features on the protein sequence. Returns the number of variant features
+ * added.
+ *
+ * @param dnaSeq
+ * @param peptide
+ * @param dnaToProtein
+ */
+ public static int computeProteinFeatures(SequenceI dnaSeq,
+ SequenceI peptide, MapList dnaToProtein)
+ {
+ while (dnaSeq.getDatasetSequence() != null)
+ {
+ dnaSeq = dnaSeq.getDatasetSequence();
+ }
+ while (peptide.getDatasetSequence() != null)
+ {
+ peptide = peptide.getDatasetSequence();
+ }
+
+ transferFeatures(dnaSeq, peptide, dnaToProtein,
+ SequenceOntologyI.EXON);
+
+ LinkedHashMap<Integer, String[][]> variants = buildDnaVariantsMap(
+ dnaSeq, dnaToProtein);
+
+ /*
+ * scan codon variations, compute peptide variants and add to peptide sequence
+ */
+ int count = 0;
+ for (Entry<Integer, String[][]> variant : variants.entrySet())
+ {
+ int peptidePos = variant.getKey();
+ String[][] codonVariants = variant.getValue();
+ String residue = String.valueOf(peptide.getCharAt(peptidePos - 1)); // 0-based
+ List<String> peptideVariants = computePeptideVariants(codonVariants,
+ residue);
+ if (!peptideVariants.isEmpty())
+ {
+ String desc = StringUtils.listToDelimitedString(peptideVariants,
+ ", ");
+ SequenceFeature sf = new SequenceFeature(
+ SequenceOntologyI.SEQUENCE_VARIANT, desc, peptidePos,
+ peptidePos, 0f, null);
+ peptide.addSequenceFeature(sf);
+ count++;
+ }
+ }
+
+ /*
+ * ugly sort to get sequence features in start position order
+ * - would be better to store in Sequence as a TreeSet instead?
+ */
+ Arrays.sort(peptide.getSequenceFeatures(),
+ new Comparator<SequenceFeature>()
+ {
+ @Override
+ public int compare(SequenceFeature o1, SequenceFeature o2)
+ {
+ int c = Integer.compare(o1.getBegin(), o2.getBegin());
+ return c == 0 ? Integer.compare(o1.getEnd(), o2.getEnd())
+ : c;
+ }
+ });
+ return count;
+ }
+
+ /**
+ * Builds a map whose key is position in the protein sequence, and value is an
+ * array of all variants for the coding codon positions
+ *
+ * @param dnaSeq
+ * @param dnaToProtein
+ * @return
+ */
+ static LinkedHashMap<Integer, String[][]> buildDnaVariantsMap(
+ SequenceI dnaSeq, MapList dnaToProtein)
+ {
+ /*
+ * map from peptide position to all variant features of the codon for it
+ * LinkedHashMap ensures we add the peptide features in sequence order
+ */
+ LinkedHashMap<Integer, String[][]> variants = new LinkedHashMap<Integer, String[][]>();
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+
+ SequenceFeature[] dnaFeatures = dnaSeq.getSequenceFeatures();
+ if (dnaFeatures == null)
+ {
+ return variants;
+ }
+
+ int dnaStart = dnaSeq.getStart();
+ int[] lastCodon = null;
+ int lastPeptidePostion = 0;
+
+ /*
+ * build a map of codon variations for peptides
+ */
+ for (SequenceFeature sf : dnaFeatures)
+ {
+ int dnaCol = sf.getBegin();
+ if (dnaCol != sf.getEnd())
+ {
+ // not handling multi-locus variant features
+ continue;
+ }
+ if (so.isA(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT))
+ {
+ int[] mapsTo = dnaToProtein.locateInTo(dnaCol, dnaCol);
+ if (mapsTo == null)
+ {
+ // feature doesn't lie within coding region
+ continue;
+ }
+ int peptidePosition = mapsTo[0];
+ String[][] codonVariants = variants.get(peptidePosition);
+ if (codonVariants == null)
+ {
+ codonVariants = new String[3][];
+ variants.put(peptidePosition, codonVariants);
+ }
+
+ /*
+ * extract dna variants to a string array
+ */
+ String alls = (String) sf.getValue("alleles");
+ if (alls == null)
+ {
+ continue;
+ }
+ String[] alleles = alls.toUpperCase().split(",");
+ int i = 0;
+ for (String allele : alleles)
+ {
+ alleles[i++] = allele.trim(); // lose any space characters "A, G"
+ }
+
+ /*
+ * get this peptide's codon positions e.g. [3, 4, 5] or [4, 7, 10]
+ */
+ int[] codon = peptidePosition == lastPeptidePostion ? lastCodon
+ : MappingUtils.flattenRanges(dnaToProtein.locateInFrom(
+ peptidePosition, peptidePosition));
+ lastPeptidePostion = peptidePosition;
+ lastCodon = codon;
+
+ /*
+ * save nucleotide (and this variant) for each codon position
+ */
+ for (int codonPos = 0; codonPos < 3; codonPos++)
+ {
+ String nucleotide = String.valueOf(
+ dnaSeq.getCharAt(codon[codonPos] - dnaStart))
+ .toUpperCase();
+ if (codonVariants[codonPos] == null)
+ {
+ /*
+ * record current dna base
+ */
+ codonVariants[codonPos] = new String[] { nucleotide };
+ }
+ if (codon[codonPos] == dnaCol)
+ {
+ /*
+ * add alleles to dna base (and any previously found alleles)
+ */
+ String[] known = codonVariants[codonPos];
+ String[] dnaVariants = new String[alleles.length + known.length];
+ System.arraycopy(known, 0, dnaVariants, 0, known.length);
+ System.arraycopy(alleles, 0, dnaVariants, known.length,
+ alleles.length);
+ codonVariants[codonPos] = dnaVariants;
+ }
+ }
+ }
+ }
+ return variants;
+ }
+
+ /**
+ * Returns a sorted, non-redundant list of all peptide translations generated
+ * by the given dna variants, excluding the current residue value
+ *
+ * @param codonVariants
+ * an array of base values (acgtACGT) for codon positions 1, 2, 3
+ * @param residue
+ * the current residue translation
+ * @return
+ */
+ static List<String> computePeptideVariants(
+ String[][] codonVariants, String residue)
+ {
+ List<String> result = new ArrayList<String>();
+ for (String base1 : codonVariants[0])
+ {
+ for (String base2 : codonVariants[1])
+ {
+ for (String base3 : codonVariants[2])
+ {
+ String codon = base1 + base2 + base3;
+ /*
+ * get peptide translation of codon e.g. GAT -> D
+ * note that variants which are not single alleles,
+ * e.g. multibase variants or HGMD_MUTATION etc
+ * are ignored here
+ */
+ String peptide = codon.contains("-") ? "-"
+ : (codon.length() > 3 ? null : ResidueProperties
+ .codonTranslate(codon));
+ if (peptide != null && !result.contains(peptide)
+ && !peptide.equalsIgnoreCase(residue))
+ {
+ result.add(peptide);
+ }
+ }
+ }
+ }
+
+ /*
+ * sort alphabetically with STOP at the end
+ */
+ Collections.sort(result, new Comparator<String>()
+ {
+
+ @Override
+ public int compare(String o1, String o2)
+ {
+ if ("STOP".equals(o1))
+ {
+ return 1;
+ }
+ else if ("STOP".equals(o2))
+ {
+ return -1;
+ }
+ else
+ {
+ return o1.compareTo(o2);
+ }
+ }
+ });
+ return result;
+ }
}