import htsjdk.variant.variantcontext.VariantContext;
import htsjdk.variant.vcf.VCFHeader;
import htsjdk.variant.vcf.VCFHeaderLine;
+import htsjdk.variant.vcf.VCFHeaderLineCount;
+import htsjdk.variant.vcf.VCFInfoHeaderLine;
import jalview.analysis.AlignmentUtils;
+import jalview.analysis.Dna;
+import jalview.api.AlignViewControllerGuiI;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.DBRefEntry;
-import jalview.datamodel.GeneLoci;
+import jalview.datamodel.GeneLociI;
import jalview.datamodel.Mapping;
-import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.ext.ensembl.EnsemblMap;
import jalview.ext.htsjdk.VCFReader;
+import jalview.io.gff.Gff3Helper;
import jalview.io.gff.SequenceOntologyI;
import jalview.util.MapList;
+import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
*/
public class VCFLoader
{
+ /*
+ * keys to fields of VEP CSQ consequence data
+ * see https://www.ensembl.org/info/docs/tools/vep/vep_formats.html
+ */
+ private static final String ALLELE_KEY = "Allele";
+
+ private static final String ALLELE_NUM_KEY = "ALLELE_NUM"; // 0 (ref), 1...
+ private static final String FEATURE_KEY = "Feature"; // Ensembl stable id
+
+ /*
+ * what comes before column headings in CSQ Description field
+ */
+ private static final String FORMAT = "Format: ";
+
+ /*
+ * default VCF INFO key for VEP consequence data
+ * NB this can be overridden running VEP with --vcf_info_field
+ * - we don't handle this case (require CSQ identifier)
+ */
+ private static final String CSQ = "CSQ";
+
+ /*
+ * separator for fields in consequence data
+ */
+ private static final String PIPE = "|";
+
+ private static final String PIPE_REGEX = "\\" + PIPE;
+
+ /*
+ * key for Allele Frequency output by VEP
+ * see http://www.ensembl.org/info/docs/tools/vep/vep_formats.html
+ */
+ private static final String ALLELE_FREQUENCY_KEY = "AF";
+
+ /*
+ * delimiter that separates multiple consequence data blocks
+ */
+ private static final String COMMA = ",";
+
+ /*
+ * the feature group assigned to a VCF variant in Jalview
+ */
+ private static final String FEATURE_GROUP_VCF = "VCF";
+
+ /*
+ * internal delimiter used to build keys for assemblyMappings
+ *
+ */
private static final String EXCL = "!";
/*
- * the alignment we are associated VCF data with
+ * the alignment we are associating VCF data with
*/
private AlignmentI al;
*/
private Map<String, Map<int[], int[]>> assemblyMappings;
+ /*
+ * holds details of the VCF header lines (metadata)
+ */
+ private VCFHeader header;
+
+ /*
+ * the position (0...) of field in each block of
+ * CSQ (consequence) data (if declared in the VCF INFO header for CSQ)
+ * see http://www.ensembl.org/info/docs/tools/vep/vep_formats.html
+ */
+ private int csqAlleleFieldIndex = -1;
+ private int csqAlleleNumberFieldIndex = -1;
+ private int csqFeatureFieldIndex = -1;
+
/**
* Constructor given an alignment context
*
}
/**
- * Loads VCF on to an alignment - provided it can be related to one or more
- * sequence's chromosomal coordinates.
+ * Starts a new thread to query and load VCF variant data on to the alignment
* <p>
* This method is not thread safe - concurrent threads should use separate
* instances of this class.
*
* @param filePath
+ * @param gui
*/
- public void loadVCF(String filePath)
+ public void loadVCF(final String filePath,
+ final AlignViewControllerGuiI gui)
{
- VCFReader reader = null;
+ if (gui != null)
+ {
+ gui.setStatus(MessageManager.getString("label.searching_vcf"));
+ }
+
+ new Thread()
+ {
+
+ @Override
+ public void run()
+ {
+ VCFLoader.this.doLoad(filePath, gui);
+ }
+
+ }.start();
+ }
+ /**
+ * Loads VCF on to an alignment - provided it can be related to one or more
+ * sequence's chromosomal coordinates
+ *
+ * @param filePath
+ * @param gui
+ * optional callback handler for messages
+ */
+ protected void doLoad(String filePath, AlignViewControllerGuiI gui)
+ {
+ VCFReader reader = null;
try
{
- long start = System.currentTimeMillis();
+ // long start = System.currentTimeMillis();
reader = new VCFReader(filePath);
- VCFHeader header = reader.getFileHeader();
+ header = reader.getFileHeader();
VCFHeaderLine ref = header
.getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
+
+ /*
+ * get offset of CSQ ALLELE_NUM and Feature if declared
+ */
+ locateCsqFields();
+
// check if reference is wrt assembly19 (GRCh37)
// todo may need to allow user to specify reference assembly?
boolean isRefGrch37 = (ref != null && ref.getValue().contains(
*/
for (SequenceI seq : al.getSequences())
{
- int added = loadVCF(seq, reader, isRefGrch37);
+ int added = loadSequenceVCF(seq, reader, isRefGrch37);
if (added > 0)
{
seqCount++;
varCount += added;
- computePeptideVariants(seq);
+ transferAddedFeatures(seq);
+ }
+ }
+ if (gui != null)
+ {
+ // long elapsed = System.currentTimeMillis() - start;
+ String msg = MessageManager.formatMessage("label.added_vcf",
+ varCount, seqCount);
+ gui.setStatus(msg);
+ if (gui.getFeatureSettingsUI() != null)
+ {
+ gui.getFeatureSettingsUI().discoverAllFeatureData();
}
}
- long elapsed = System.currentTimeMillis() - start;
- System.out.println(String.format(
- "Added %d VCF variants to %d sequence(s) (%dms)", varCount,
- seqCount, elapsed));
-
- reader.close();
} catch (Throwable e)
{
System.err.println("Error processing VCF: " + e.getMessage());
e.printStackTrace();
+ if (gui != null)
+ {
+ gui.setStatus("Error occurred - see console for details");
+ }
+ } finally
+ {
+ if (reader != null)
+ {
+ try
+ {
+ reader.close();
+ } catch (IOException e)
+ {
+ // ignore
+ }
+ }
}
}
/**
- * (Re-)computes peptide variants from dna variants, for any protein sequence
- * to which the dna sequence has a mapping. Note that although duplicate
- * features may get computed, they will not be added, since duplicate sequence
- * features are ignored in Sequence.addSequenceFeature.
+ * Records the position of selected fields defined in the CSQ INFO header (if
+ * there is one). CSQ fields are declared in the CSQ INFO Description e.g.
+ * <p>
+ * Description="Consequence ...from ... VEP. Format: Allele|Consequence|...
+ */
+ protected void locateCsqFields()
+ {
+ VCFInfoHeaderLine csqInfo = header.getInfoHeaderLine(CSQ);
+ if (csqInfo == null)
+ {
+ return;
+ }
+
+ String desc = csqInfo.getDescription();
+ int formatPos = desc.indexOf(FORMAT);
+ if (formatPos == -1)
+ {
+ System.err.println("Parse error, failed to find " + FORMAT
+ + " in " + desc);
+ return;
+ }
+ desc = desc.substring(formatPos + FORMAT.length());
+
+ if (desc != null)
+ {
+ String[] format = desc.split(PIPE_REGEX);
+ int index = 0;
+ for (String field : format)
+ {
+ if (ALLELE_NUM_KEY.equals(field))
+ {
+ csqAlleleNumberFieldIndex = index;
+ }
+ if (ALLELE_KEY.equals(field))
+ {
+ csqAlleleFieldIndex = index;
+ }
+ if (FEATURE_KEY.equals(field))
+ {
+ csqFeatureFieldIndex = index;
+ }
+ index++;
+ }
+ }
+ }
+
+ /**
+ * Transfers VCF features to sequences to which this sequence has a mapping.
+ * If the mapping is 3:1, computes peptide variants from nucleotide variants.
*
- * @param dnaSeq
+ * @param seq
*/
- protected void computePeptideVariants(SequenceI dnaSeq)
+ protected void transferAddedFeatures(SequenceI seq)
{
- DBRefEntry[] dbrefs = dnaSeq.getDBRefs();
+ DBRefEntry[] dbrefs = seq.getDBRefs();
if (dbrefs == null)
{
return;
for (DBRefEntry dbref : dbrefs)
{
Mapping mapping = dbref.getMap();
- if (mapping == null || mapping.getTo() == null
- || mapping.getMap().getFromRatio() != 3)
+ if (mapping == null || mapping.getTo() == null)
{
continue;
}
- AlignmentUtils.computeProteinFeatures(dnaSeq, mapping.getTo(),
- mapping.getMap());
+
+ SequenceI mapTo = mapping.getTo();
+ MapList map = mapping.getMap();
+ if (map.getFromRatio() == 3)
+ {
+ /*
+ * dna-to-peptide product mapping
+ */
+ AlignmentUtils.computeProteinFeatures(seq, mapTo, map);
+ }
+ else
+ {
+ /*
+ * nucleotide-to-nucleotide mapping e.g. transcript to CDS
+ */
+ List<SequenceFeature> features = seq.getFeatures()
+ .getPositionalFeatures(SequenceOntologyI.SEQUENCE_VARIANT);
+ for (SequenceFeature sf : features)
+ {
+ if (FEATURE_GROUP_VCF.equals(sf.getFeatureGroup()))
+ {
+ transferFeature(sf, mapTo, map);
+ }
+ }
+ }
}
}
/**
* Tries to add overlapping variants read from a VCF file to the given
- * sequence, and returns the number of overlapping variants found. Note that
- * this requires the sequence to hold information as to its chromosomal
- * positions and reference, in order to be able to map the VCF variants to the
- * sequence.
+ * sequence, and returns the number of variant features added. Note that this
+ * requires the sequence to hold information as to its chromosomal positions
+ * and reference, in order to be able to map the VCF variants to the sequence.
*
* @param seq
* @param reader
* @param isVcfRefGrch37
* @return
*/
- protected int loadVCF(SequenceI seq, VCFReader reader,
+ protected int loadSequenceVCF(SequenceI seq, VCFReader reader,
boolean isVcfRefGrch37)
{
int count = 0;
- GeneLoci seqCoords = ((Sequence) seq).getGeneLoci();
+ GeneLociI seqCoords = seq.getGeneLoci();
if (seqCoords == null)
{
+ System.out.println(String.format(
+ "Can't query VCF for %s as chromosome coordinates not known",
+ seq.getName()));
return 0;
}
- MapList mapping = seqCoords.getMapping();
- List<int[]> seqChromosomalContigs = mapping.getToRanges();
+ List<int[]> seqChromosomalContigs = seqCoords.getMap().getToRanges();
for (int[] range : seqChromosomalContigs)
{
count += addVcfVariants(seq, reader, range, isVcfRefGrch37);
protected int addVcfVariants(SequenceI seq, VCFReader reader,
int[] range, boolean isVcfRefGrch37)
{
- GeneLoci seqCoords = ((Sequence) seq).getGeneLoci();
+ GeneLociI seqCoords = seq.getGeneLoci();
- String chromosome = seqCoords.getChromosome();
- String seqRef = seqCoords.getReference();
- String species = seqCoords.getSpecies();
-
- // TODO handle species properly
- if ("".equals(species))
- {
- species = "human";
- }
+ String chromosome = seqCoords.getChromosomeId();
+ String seqRef = seqCoords.getAssemblyId();
+ String species = seqCoords.getSpeciesId();
/*
* map chromosomal coordinates from GRCh38 (sequence) to
if (fromRef.equalsIgnoreCase(seqRef) && isVcfRefGrch37)
{
String toRef = "GRCh37";
- int[] newRange = mapReferenceRange(range, chromosome, species,
+ int[] newRange = mapReferenceRange(range, chromosome, "human",
fromRef, toRef);
if (newRange == null)
{
range = newRange;
}
+ boolean forwardStrand = range[0] <= range[1];
+
/*
* query the VCF for overlaps
+ * (convert a reverse strand range to forwards)
*/
int count = 0;
- MapList mapping = seqCoords.getMapping();
+ MapList mapping = seqCoords.getMap();
+ int fromLocus = Math.min(range[0], range[1]);
+ int toLocus = Math.max(range[0], range[1]);
CloseableIterator<VariantContext> variants = reader.query(chromosome,
- range[0], range[1]);
+ fromLocus, toLocus);
while (variants.hasNext())
{
/*
* get variant location in sequence chromosomal coordinates
*/
VariantContext variant = variants.next();
- count++;
+
int start = variant.getStart() - offset;
int end = variant.getEnd() - offset;
/*
* convert chromosomal location to sequence coordinates
+ * - may be reverse strand (convert to forward for sequence feature)
* - null if a partially overlapping feature
*/
int[] seqLocation = mapping.locateInFrom(start, end);
if (seqLocation != null)
{
- addVariantFeatures(seq, variant, seqLocation[0], seqLocation[1]);
+ int featureStart = Math.min(seqLocation[0], seqLocation[1]);
+ int featureEnd = Math.max(seqLocation[0], seqLocation[1]);
+ count += addAlleleFeatures(seq, variant, featureStart, featureEnd,
+ forwardStrand);
}
}
}
/**
- * Inspects the VCF variant record, and adds variant features to the sequence
+ * A convenience method to get the AF value for the given alternate allele
+ * index
+ *
+ * @param variant
+ * @param alleleIndex
+ * @return
+ */
+ protected float getAlleleFrequency(VariantContext variant, int alleleIndex)
+ {
+ float score = 0f;
+ String attributeValue = getAttributeValue(variant,
+ ALLELE_FREQUENCY_KEY, alleleIndex);
+ if (attributeValue != null)
+ {
+ try
+ {
+ score = Float.parseFloat(attributeValue);
+ } catch (NumberFormatException e)
+ {
+ // leave as 0
+ }
+ }
+
+ return score;
+ }
+
+ /**
+ * A convenience method to get an attribute value for an alternate allele
+ *
+ * @param variant
+ * @param attributeName
+ * @param alleleIndex
+ * @return
+ */
+ protected String getAttributeValue(VariantContext variant,
+ String attributeName, int alleleIndex)
+ {
+ Object att = variant.getAttribute(attributeName);
+
+ if (att instanceof String)
+ {
+ return (String) att;
+ }
+ else if (att instanceof ArrayList)
+ {
+ return ((List<String>) att).get(alleleIndex);
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds one variant feature for each allele in the VCF variant record, and
+ * returns the number of features added.
*
* @param seq
* @param variant
* @param featureStart
* @param featureEnd
+ * @param forwardStrand
+ * @return
*/
- protected void addVariantFeatures(SequenceI seq, VariantContext variant,
- int featureStart, int featureEnd)
+ protected int addAlleleFeatures(SequenceI seq, VariantContext variant,
+ int featureStart, int featureEnd, boolean forwardStrand)
{
+ int added = 0;
+
+ /*
+ * Javadoc says getAlternateAlleles() imposes no order on the list returned
+ * so we proceed defensively to get them in strict order
+ */
+ int altAlleleCount = variant.getAlternateAlleles().size();
+ for (int i = 0; i < altAlleleCount; i++)
+ {
+ added += addAlleleFeature(seq, variant, i, featureStart, featureEnd,
+ forwardStrand);
+ }
+ return added;
+ }
+
+ /**
+ * Inspects one allele and attempts to add a variant feature for it to the
+ * sequence. We extract as much as possible of the additional data associated
+ * with this allele to store in the feature's key-value map. Answers the
+ * number of features added (0 or 1).
+ *
+ * @param seq
+ * @param variant
+ * @param altAlleleIndex
+ * (0, 1..)
+ * @param featureStart
+ * @param featureEnd
+ * @param forwardStrand
+ * @return
+ */
+ protected int addAlleleFeature(SequenceI seq, VariantContext variant,
+ int altAlleleIndex, int featureStart, int featureEnd,
+ boolean forwardStrand)
+ {
+ String reference = variant.getReference().getBaseString();
+ Allele alt = variant.getAlternateAllele(altAlleleIndex);
+ String allele = alt.getBaseString();
+
+ /*
+ * build the ref,alt allele description e.g. "G,A", using the base
+ * complement if the sequence is on the reverse strand
+ */
+ // TODO check how structural variants are shown on reverse strand
StringBuilder sb = new StringBuilder();
- sb.append(variant.getReference().getBaseString());
+ sb.append(forwardStrand ? reference : Dna.reverseComplement(reference));
+ sb.append(COMMA);
+ sb.append(forwardStrand ? allele : Dna.reverseComplement(allele));
+ String alleles = sb.toString(); // e.g. G,A
+
+ String type = SequenceOntologyI.SEQUENCE_VARIANT;
+ float score = getAlleleFrequency(variant, altAlleleIndex);
+
+ SequenceFeature sf = new SequenceFeature(type, alleles, featureStart,
+ featureEnd, score, FEATURE_GROUP_VCF);
+
+ sf.setValue(Gff3Helper.ALLELES, alleles);
+
+ addAlleleProperties(variant, seq, sf, altAlleleIndex);
- int alleleCount = 0;
- for (Allele allele : variant.getAlleles())
+ seq.addSequenceFeature(sf);
+
+ return 1;
+ }
+
+ /**
+ * Add any allele-specific VCF key-value data to the sequence feature
+ *
+ * @param variant
+ * @param seq
+ * @param sf
+ * @param altAlelleIndex
+ * (0, 1..)
+ */
+ protected void addAlleleProperties(VariantContext variant, SequenceI seq,
+ SequenceFeature sf, final int altAlelleIndex)
+ {
+ Map<String, Object> atts = variant.getAttributes();
+
+ for (Entry<String, Object> att : atts.entrySet())
{
- if (!allele.isReference())
+ String key = att.getKey();
+
+ /*
+ * extract Consequence data (if present) that we are able to
+ * associated with the allele for this variant feature
+ */
+ if (CSQ.equals(key))
{
- sb.append(",").append(allele.getBaseString());
- alleleCount++;
+ addConsequences(variant, seq, sf, altAlelleIndex);
+ continue;
+ }
+
+ /*
+ * we extract values for other data which are allele-specific;
+ * these may be per alternate allele (INFO[key].Number = 'A')
+ * or per allele including reference (INFO[key].Number = 'R')
+ */
+ VCFInfoHeaderLine infoHeader = header.getInfoHeaderLine(key);
+ if (infoHeader == null)
+ {
+ /*
+ * can't be sure what data belongs to this allele, so
+ * play safe and don't take any
+ */
+ continue;
+ }
+
+ VCFHeaderLineCount number = infoHeader.getCountType();
+ int index = altAlelleIndex;
+ if (number == VCFHeaderLineCount.R)
+ {
+ /*
+ * one value per allele including reference, so bump index
+ * e.g. the 3rd value is for the 2nd alternate allele
+ */
+ index++;
+ }
+ else if (number != VCFHeaderLineCount.A)
+ {
+ /*
+ * don't save other values as not allele-related
+ */
+ continue;
+ }
+
+ /*
+ * take the index'th value
+ */
+ String value = getAttributeValue(variant, key, index);
+ if (value != null)
+ {
+ sf.setValue(key, value);
}
}
- String alleles = sb.toString(); // e.g. G,A,C
+ }
- String type = SequenceOntologyI.SEQUENCE_VARIANT;
- float score = 0f;
- if (alleleCount == 1)
+ /**
+ * Inspects CSQ data blocks (consequences) and adds attributes on the sequence
+ * feature for the current allele (and transcript if applicable)
+ * <p>
+ * Allele matching: if field ALLELE_NUM is present, it must match
+ * altAlleleIndex. If not present, then field Allele value must match the VCF
+ * Allele.
+ * <p>
+ * Transcript matching: if sequence name can be identified to at least one of
+ * the consequences' Feature values, then select only consequences that match
+ * the value (i.e. consequences for the current transcript sequence). If not,
+ * take all consequences (this is the case when adding features to the gene
+ * sequence).
+ *
+ * @param variant
+ * @param seq
+ * @param sf
+ * @param altAlelleIndex
+ * (0, 1..)
+ */
+ protected void addConsequences(VariantContext variant, SequenceI seq,
+ SequenceFeature sf, int altAlelleIndex)
+ {
+ Object value = variant.getAttribute(CSQ);
+
+ if (value == null || !(value instanceof ArrayList<?>))
{
- try
+ return;
+ }
+
+ List<String> consequences = (List<String>) value;
+
+ /*
+ * if CSQ data includes 'Feature', and any value matches the sequence name,
+ * then restrict consequence data to only the matching value (transcript)
+ * i.e. just pick out consequences for the transcript the variant feature is on
+ */
+ String seqName = seq.getName()== null ? "" : seq.getName().toLowerCase();
+ String matchFeature = null;
+ if (csqFeatureFieldIndex > -1)
+ {
+ for (String consequence : consequences)
{
- score = (float) variant.getAttributeAsDouble("AF", 0d);
- } catch (NumberFormatException e)
+ String[] csqFields = consequence.split(PIPE_REGEX);
+ if (csqFields.length > csqFeatureFieldIndex)
+ {
+ String featureIdentifier = csqFields[csqFeatureFieldIndex];
+ if (featureIdentifier.length() > 4
+ && seqName.indexOf(featureIdentifier.toLowerCase()) > -1)
+ {
+ matchFeature = featureIdentifier;
+ }
+ }
+ }
+ }
+
+ StringBuilder sb = new StringBuilder(128);
+ boolean found = false;
+
+ for (String consequence : consequences)
+ {
+ String[] csqFields = consequence.split(PIPE_REGEX);
+
+ if (includeConsequence(csqFields, matchFeature, variant,
+ altAlelleIndex))
{
- // leave score as 0
+ if (found)
+ {
+ sb.append(COMMA);
+ }
+ found = true;
+ sb.append(consequence);
}
}
- SequenceFeature sf = new SequenceFeature(type, alleles, featureStart,
- featureEnd, score, "VCF");
+ if (found)
+ {
+ sf.setValue(CSQ, sb.toString());
+ }
+ }
+
+ /**
+ * Answers true if we want to associate this block of consequence data with
+ * the specified alternate allele of the VCF variant.
+ * <p>
+ * If consequence data includes the ALLELE_NUM field, then this has to match
+ * altAlleleIndex. Otherwise the Allele field of the consequence data has to
+ * match the allele value.
+ * <p>
+ * Optionally (if matchFeature is not null), restrict to only include
+ * consequences whose Feature value matches. This allows us to attach
+ * consequences to their respective transcripts.
+ *
+ * @param csqFields
+ * @param matchFeature
+ * @param variant
+ * @param altAlelleIndex
+ * (0, 1..)
+ * @return
+ */
+ protected boolean includeConsequence(String[] csqFields,
+ String matchFeature, VariantContext variant, int altAlelleIndex)
+ {
/*
- * only add 'alleles' property if a SNP, as we can
- * only handle SNPs when computing peptide variants
+ * check consequence is for the current transcript
*/
- if (variant.isSNP())
+ if (matchFeature != null)
{
- sf.setValue("alleles", alleles);
+ if (csqFields.length <= csqFeatureFieldIndex)
+ {
+ return false;
+ }
+ String featureIdentifier = csqFields[csqFeatureFieldIndex];
+ if (!featureIdentifier.equals(matchFeature))
+ {
+ return false; // consequence is for a different transcript
+ }
}
- Map<String, Object> atts = variant.getAttributes();
- for (Entry<String, Object> att : atts.entrySet())
+ /*
+ * if ALLELE_NUM is present, it must match altAlleleIndex
+ * NB first alternate allele is 1 for ALLELE_NUM, 0 for altAlleleIndex
+ */
+ if (csqAlleleNumberFieldIndex > -1)
{
- sf.setValue(att.getKey(), att.getValue());
+ if (csqFields.length <= csqAlleleNumberFieldIndex)
+ {
+ return false;
+ }
+ String alleleNum = csqFields[csqAlleleNumberFieldIndex];
+ return String.valueOf(altAlelleIndex + 1).equals(alleleNum);
}
- seq.addSequenceFeature(sf);
+
+ /*
+ * else consequence allele must match variant allele
+ */
+ if (csqAlleleFieldIndex > -1 && csqFields.length > csqAlleleFieldIndex)
+ {
+ String csqAllele = csqFields[csqAlleleFieldIndex];
+ String vcfAllele = variant.getAlternateAllele(altAlelleIndex)
+ .getBaseString();
+ return csqAllele.equals(vcfAllele);
+ }
+
+ return false;
+ }
+
+ /**
+ * A convenience method to complement a dna base and return the string value
+ * of its complement
+ *
+ * @param reference
+ * @return
+ */
+ protected String complement(byte[] reference)
+ {
+ return String.valueOf(Dna.getComplement((char) reference[0]));
}
/**
* call (e.g.) http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37
*/
EnsemblMap mapper = new EnsemblMap();
- int[] mapping = mapper.getMapping(species, chromosome, fromRef, toRef,
+ int[] mapping = mapper.getAssemblyMapping(species, chromosome, fromRef,
+ toRef,
queryRange);
if (mapping == null)
/*
* save mapping for possible future re-use
*/
- String key = species + EXCL + chromosome + EXCL + fromRef + EXCL
- + toRef;
+ String key = makeRangesKey(chromosome, species, fromRef, toRef);
if (!assemblyMappings.containsKey(key))
{
assemblyMappings.put(key, new HashMap<int[], int[]>());
protected int[] findSubsumedRangeMapping(int[] queryRange, String chromosome,
String species, String fromRef, String toRef)
{
- String key = species + EXCL + chromosome + EXCL + fromRef + EXCL
- + toRef;
+ String key = makeRangesKey(chromosome, species, fromRef, toRef);
if (assemblyMappings.containsKey(key))
{
Map<int[], int[]> mappedRanges = assemblyMappings.get(key);
/*
* mapping is 1:1 in length, so we trust it to have no discontinuities
*/
- if (fromRange[0] <= queryRange[0] && fromRange[1] >= queryRange[1])
+ if (MappingUtils.rangeContains(fromRange, queryRange))
{
/*
* fromRange subsumes our query range
}
return null;
}
+
+ /**
+ * Transfers the sequence feature to the target sequence, locating its start
+ * and end range based on the mapping. Features which do not overlap the
+ * target sequence are ignored.
+ *
+ * @param sf
+ * @param targetSequence
+ * @param mapping
+ * mapping from the feature's coordinates to the target sequence
+ */
+ protected void transferFeature(SequenceFeature sf,
+ SequenceI targetSequence, MapList mapping)
+ {
+ int[] mappedRange = mapping.locateInTo(sf.getBegin(), sf.getEnd());
+
+ if (mappedRange != null)
+ {
+ String group = sf.getFeatureGroup();
+ int newBegin = Math.min(mappedRange[0], mappedRange[1]);
+ int newEnd = Math.max(mappedRange[0], mappedRange[1]);
+ SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
+ group, sf.getScore());
+ targetSequence.addSequenceFeature(copy);
+ }
+ }
+
+ /**
+ * Formats a ranges map lookup key
+ *
+ * @param chromosome
+ * @param species
+ * @param fromRef
+ * @param toRef
+ * @return
+ */
+ protected static String makeRangesKey(String chromosome, String species,
+ String fromRef, String toRef)
+ {
+ return species + EXCL + chromosome + EXCL + fromRef + EXCL
+ + toRef;
+ }
}