+ /**
+ * Reads the Preference value for the given key, with default specified if no
+ * preference set. The value is interpreted as a comma-separated list of
+ * regular expressions, and converted into a list of compiled patterns ready
+ * for matching. Patterns are forced to upper-case for non-case-sensitive
+ * matching.
+ * <p>
+ * This supports user-defined filters for fields of interest to capture while
+ * processing data. For example, VCF_FIELDS = AF,AC* would mean that VCF INFO
+ * fields with an ID of AF, or starting with AC, would be matched.
+ *
+ * @param key
+ * @param def
+ * @return
+ */
+ private List<Pattern> getFieldMatchers(String key, String def)
+ {
+ String pref = Cache.getDefault(key, def);
+ List<Pattern> patterns = new ArrayList<>();
+ String[] tokens = pref.split(",");
+ for (String token : tokens)
+ {
+ try
+ {
+ patterns.add(Pattern.compile(token.toUpperCase(Locale.ROOT)));
+ } catch (PatternSyntaxException e)
+ {
+ System.err.println("Invalid pattern ignored: " + token);
+ }
+ }
+ return patterns;
+ }
+
+ /**
+ * Transfers VCF features to sequences to which this sequence has a mapping.
+ *
+ * @param seq
+ */
+ protected void transferAddedFeatures(SequenceI seq)
+ {
+ List<DBRefEntry> dbrefs = seq.getDBRefs();
+ if (dbrefs == null)
+ {
+ return;
+ }
+ for (DBRefEntry dbref : dbrefs)
+ {
+ Mapping mapping = dbref.getMap();
+ if (mapping == null || mapping.getTo() == null)
+ {
+ continue;
+ }
+
+ SequenceI mapTo = mapping.getTo();
+ MapList map = mapping.getMap();
+ if (map.getFromRatio() == 3)
+ {
+ /*
+ * dna-to-peptide product mapping
+ */
+ // JAL-3187 render on the fly instead
+ // 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 variant features added
+ *
+ * @param seq
+ * @return
+ */
+ protected int loadSequenceVCF(SequenceI seq)
+ {
+ VCFMap vcfMap = getVcfMap(seq);
+ if (vcfMap == null)
+ {
+ return 0;
+ }
+
+ /*
+ * work with the dataset sequence here
+ */
+ SequenceI dss = seq.getDatasetSequence();
+ if (dss == null)
+ {
+ dss = seq;
+ }
+ return addVcfVariants(dss, vcfMap);
+ }
+
+ /**
+ * Answers a map from sequence coordinates to VCF chromosome ranges
+ *
+ * @param seq
+ * @return
+ */
+ private VCFMap getVcfMap(SequenceI seq)
+ {
+ /*
+ * simplest case: sequence has id and length matching a VCF contig
+ */
+ VCFMap vcfMap = null;
+ if (dictionary != null)
+ {
+ vcfMap = getContigMap(seq);
+ }
+ if (vcfMap != null)
+ {
+ return vcfMap;
+ }
+
+ /*
+ * otherwise, map to VCF from chromosomal coordinates
+ * of the sequence (if known)
+ */
+ GeneLociI seqCoords = seq.getGeneLoci();
+ if (seqCoords == null)
+ {
+ Cache.log.warn(String.format(
+ "Can't query VCF for %s as chromosome coordinates not known",
+ seq.getName()));
+ return null;
+ }
+
+ String species = seqCoords.getSpeciesId();
+ String chromosome = seqCoords.getChromosomeId();
+ String seqRef = seqCoords.getAssemblyId();
+ MapList map = seqCoords.getMapping();
+
+ // note this requires the configured species to match that
+ // returned with the Ensembl sequence; todo: support aliases?
+ if (!vcfSpecies.equalsIgnoreCase(species))
+ {
+ Cache.log.warn("No VCF loaded to " + seq.getName()
+ + " as species not matched");
+ return null;
+ }
+
+ if (seqRef.equalsIgnoreCase(vcfAssembly))
+ {
+ return new VCFMap(chromosome, map);
+ }
+
+ /*
+ * VCF data has a different reference assembly to the sequence:
+ * query Ensembl to map chromosomal coordinates from sequence to VCF
+ */
+ List<int[]> toVcfRanges = new ArrayList<>();
+ List<int[]> fromSequenceRanges = new ArrayList<>();
+
+ for (int[] range : map.getToRanges())
+ {
+ int[] fromRange = map.locateInFrom(range[0], range[1]);
+ if (fromRange == null)
+ {
+ // corrupted map?!?
+ continue;
+ }
+
+ int[] newRange = mapReferenceRange(range, chromosome, "human", seqRef,
+ vcfAssembly);
+ if (newRange == null)
+ {
+ Cache.log.error(
+ String.format("Failed to map %s:%s:%s:%d:%d to %s", species,
+ chromosome, seqRef, range[0], range[1],
+ vcfAssembly));
+ continue;
+ }
+ else
+ {
+ toVcfRanges.add(newRange);
+ fromSequenceRanges.add(fromRange);
+ }
+ }
+
+ return new VCFMap(chromosome,
+ new MapList(fromSequenceRanges, toVcfRanges, 1, 1));
+ }
+
+ /**
+ * If the sequence id matches a contig declared in the VCF file, and the
+ * sequence length matches the contig length, then returns a 1:1 map of the
+ * sequence to the contig, else returns null
+ *
+ * @param seq
+ * @return
+ */
+ private VCFMap getContigMap(SequenceI seq)
+ {
+ String id = seq.getName();
+ SAMSequenceRecord contig = dictionary.getSequence(id);
+ if (contig != null)
+ {
+ int len = seq.getLength();
+ if (len == contig.getSequenceLength())
+ {
+ MapList map = new MapList(new int[] { 1, len },
+ new int[]
+ { 1, len }, 1, 1);
+ return new VCFMap(id, map);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Queries the VCF reader for any variants that overlap the mapped chromosome
+ * ranges of the sequence, and adds as variant features. Returns the number of
+ * overlapping variants found.
+ *
+ * @param seq
+ * @param map
+ * mapping from sequence to VCF coordinates
+ * @return
+ */
+ protected int addVcfVariants(SequenceI seq, VCFMap map)
+ {
+ boolean forwardStrand = map.map.isToForwardStrand();
+
+ /*
+ * query the VCF for overlaps of each contiguous chromosomal region
+ */
+ int count = 0;
+
+ for (int[] range : map.map.getToRanges())
+ {
+ int vcfStart = Math.min(range[0], range[1]);
+ int vcfEnd = Math.max(range[0], range[1]);
+ try
+ {
+ CloseableIterator<VariantContext> variants = reader
+ .query(map.chromosome, vcfStart, vcfEnd);
+ while (variants.hasNext())
+ {
+ VariantContext variant = variants.next();
+
+ int[] featureRange = map.map.locateInFrom(variant.getStart(),
+ variant.getEnd());
+
+ /*
+ * only take features whose range is fully mappable to sequence positions
+ */
+ if (featureRange != null)
+ {
+ int featureStart = Math.min(featureRange[0], featureRange[1]);
+ int featureEnd = Math.max(featureRange[0], featureRange[1]);
+ if (featureEnd - featureStart == variant.getEnd()
+ - variant.getStart())
+ {
+ count += addAlleleFeatures(seq, variant, featureStart,
+ featureEnd, forwardStrand);
+ }
+ }
+ }
+ variants.close();
+ } catch (TribbleException e)
+ {
+ /*
+ * RuntimeException throwable by htsjdk
+ */
+ String msg = String.format("Error reading VCF for %s:%d-%d: %s ",
+ map.chromosome, vcfStart, vcfEnd,e.getLocalizedMessage());
+ Cache.log.error(msg);
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * 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 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. The additional data associated with this allele is extracted 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();
+
+ /*
+ * insertion after a genomic base, if on reverse strand, has to be
+ * converted to insertion of complement after the preceding position
+ */
+ int referenceLength = reference.length();
+ if (!forwardStrand && allele.length() > referenceLength
+ && allele.startsWith(reference))
+ {
+ featureStart -= referenceLength;
+ featureEnd = featureStart;
+ char insertAfter = seq.getCharAt(featureStart - seq.getStart());
+ reference = Dna.reverseComplement(String.valueOf(insertAfter));
+ allele = allele.substring(referenceLength) + reference;
+ }
+
+ /*
+ * build the ref,alt allele description e.g. "G,A", using the base
+ * complement if the sequence is on the reverse strand
+ */
+ StringBuilder sb = new StringBuilder();
+ 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
+
+ /*
+ * pick out the consequence data (if any) that is for the current allele
+ * and feature (transcript) that matches the current sequence
+ */
+ String consequence = getConsequenceForAlleleAndFeature(variant, CSQ_FIELD,
+ altAlleleIndex, csqAlleleFieldIndex,
+ csqAlleleNumberFieldIndex, seq.getName().toLowerCase(Locale.ROOT),
+ csqFeatureFieldIndex);
+
+ /*
+ * pick out the ontology term for the consequence type
+ */