From ee40791776dfcdb8d192820c9a61f3a1b0a01012 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 09:55:42 -0500 Subject: [PATCH 001/113] Attributes are now Map not Map -- Allows us to avoid an unnecessary copy when creating InferredGeneticContext (whose name really needs to change). --- .../gatk/refdata/VariantContextAdaptors.java | 2 +- .../indels/SomaticIndelDetectorWalker.java | 2 +- .../varianteval/VariantEvalWalker.java | 2 +- .../evaluators/G1KPhaseITable.java | 14 +++++------- .../IntervalStratification.java | 4 ++-- .../sting/utils/codecs/vcf/VCF3Codec.java | 4 ++-- .../sting/utils/codecs/vcf/VCFCodec.java | 4 ++-- .../sting/utils/variantcontext/Genotype.java | 4 ++-- .../InferredGeneticContext.java | 22 ++++++------------- .../utils/variantcontext/MutableGenotype.java | 2 +- .../variantcontext/MutableVariantContext.java | 4 ++-- .../utils/variantcontext/VariantContext.java | 10 ++++----- .../variantcontext/VariantContextUtils.java | 2 +- .../utils/genotype/vcf/VCFWriterUnitTest.java | 4 ++-- 14 files changed, 35 insertions(+), 45 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index 7bf518fd5..941dc66b5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -257,7 +257,7 @@ public class VariantContextAdaptors { else genotypeAlleles.add(refAllele); } - Map attributes = new HashMap(); + Map attributes = new HashMap(); Collection genotypes = new ArrayList(); MutableGenotype call = new MutableGenotype(name, genotypeAlleles); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index 414ffa09c..a97117acd 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -1061,7 +1061,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { for ( String sample : normalSamples ) { - Map attrs = call.makeStatsAttributes(null); + Map attrs = call.makeStatsAttributes(null); if ( call.isCall() ) // we made a call - put actual het genotype here: genotypes.put(sample,new Genotype(sample,alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java index 4e4a1550d..4556692bd 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java @@ -186,7 +186,7 @@ public class VariantEvalWalker extends RodWalker implements Tr * File containing tribble-readable features for the IntervalStratificiation */ @Input(fullName="stratIntervals", shortName="stratIntervals", doc="File containing tribble-readable features for the IntervalStratificiation", required=false) - protected IntervalBinding intervalsFile = null; + public IntervalBinding intervalsFile = null; // Variables private Set jexlExpressions = new TreeSet(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java index 3ab618496..8cc321ef5 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java @@ -51,21 +51,21 @@ public class G1KPhaseITable extends VariantEvaluator { @DataPoint(description = "Number of SNPs") public long nSNPs = 0; @DataPoint(description = "SNP Novelty Rate") - public double SNPNoveltyRate = 0; + public String SNPNoveltyRate = "NA"; @DataPoint(description = "Mean number of SNPs per individual") public long nSNPsPerSample = 0; @DataPoint(description = "Number of Indels") public long nIndels = 0; @DataPoint(description = "Indel Novelty Rate") - public double IndelNoveltyRate = 0; + public String IndelNoveltyRate = "NA"; @DataPoint(description = "Mean number of Indels per individual") public long nIndelsPerSample = 0; @DataPoint(description = "Number of SVs") public long nSVs = 0; @DataPoint(description = "SV Novelty Rate") - public double SVNoveltyRate = 0; + public String SVNoveltyRate = "NA"; @DataPoint(description = "Mean number of SVs per individual") public long nSVsPerSample = 0; @@ -106,9 +106,6 @@ public class G1KPhaseITable extends VariantEvaluator { if ( eval == null || eval.isMonomorphic() ) return null; switch (eval.getType()) { -// case NO_VARIATION: -// // shouldn't get here -// break; case SNP: case INDEL: case SYMBOLIC: @@ -139,11 +136,12 @@ public class G1KPhaseITable extends VariantEvaluator { return (int)(Math.round(sum / (1.0 * countsPerSample.size()))); } - private final double noveltyRate(VariantContext.Type type) { + private final String noveltyRate(VariantContext.Type type) { int all = allVariantCounts.get(type); int known = knownVariantCounts.get(type); int novel = all - known; - return (novel / (1.0 * all)); + double rate = (novel / (1.0 * all)); + return all == 0 ? "NA" : String.format("%.2f", rate); } public void finalizeEvaluation() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/IntervalStratification.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/IntervalStratification.java index bf001588a..00a656cc6 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/IntervalStratification.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/IntervalStratification.java @@ -63,8 +63,8 @@ public class IntervalStratification extends VariantStratifier { if ( locs.isEmpty() ) throw new UserException.BadArgumentValue("stratIntervals", "Contains no intervals. Perhaps the file is malformed or empty?"); - logger.info(String.format("Creating IntervalStratification containing %d intervals covering %d bp", - locs.size(), IntervalUtils.intervalSize(locs))); + logger.info(String.format("Creating IntervalStratification %s containing %d intervals covering %d bp", + getVariantEvalWalker().intervalsFile.getSource(), locs.size(), IntervalUtils.intervalSize(locs))); // set up the map from contig -> interval tree for ( final String contig : getVariantEvalWalker().getContigNames() ) diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index e5b1a2de5..bed66a439 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -141,7 +141,7 @@ public class VCF3Codec extends AbstractVCFCodec { double GTQual = VariantContext.NO_NEG_LOG_10PERROR; Set genotypeFilters = null; - Map gtAttributes = null; + Map gtAttributes = null; String sampleName = sampleNameIterator.next(); // check to see if the value list is longer than the key list, which is a problem @@ -150,7 +150,7 @@ public class VCF3Codec extends AbstractVCFCodec { int genotypeAlleleLocation = -1; if (nGTKeys >= 1) { - gtAttributes = new HashMap(nGTKeys - 1); + gtAttributes = new HashMap(nGTKeys - 1); for (int i = 0; i < nGTKeys; i++) { final String gtKey = new String(genotypeKeyArray[i]); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index 42ea05355..58dfd3589 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -168,7 +168,7 @@ public class VCFCodec extends AbstractVCFCodec { double GTQual = VariantContext.NO_NEG_LOG_10PERROR; Set genotypeFilters = null; - Map gtAttributes = null; + Map gtAttributes = null; String sampleName = sampleNameIterator.next(); // check to see if the value list is longer than the key list, which is a problem @@ -177,7 +177,7 @@ public class VCFCodec extends AbstractVCFCodec { int genotypeAlleleLocation = -1; if (nGTKeys >= 1) { - gtAttributes = new HashMap(nGTKeys - 1); + gtAttributes = new HashMap(nGTKeys - 1); for (int i = 0; i < nGTKeys; i++) { final String gtKey = new String(genotypeKeyArray[i]); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java index e2e44e2b9..c59c002f2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -25,11 +25,11 @@ public class Genotype { protected boolean isPhased = false; protected boolean filtersWereAppliedToContext; - public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased) { + public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased) { this(sampleName, alleles, negLog10PError, filters, attributes, isPhased, null); } - public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { + public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { if ( alleles != null ) this.alleles = Collections.unmodifiableList(alleles); commonInfo = new InferredGeneticContext(sampleName, negLog10PError, filters, attributes); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java index bf16cd1cf..e7d9b3338 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java @@ -22,22 +22,14 @@ public final class InferredGeneticContext { private Set filters = NO_FILTERS; private Map attributes = NO_ATTRIBUTES; -// public InferredGeneticContext(String name) { -// this.name = name; -// } -// -// public InferredGeneticContext(String name, double negLog10PError) { -// this(name); -// setNegLog10PError(negLog10PError); -// } - - public InferredGeneticContext(String name, double negLog10PError, Set filters, Map attributes) { + public InferredGeneticContext(String name, double negLog10PError, Set filters, Map attributes) { this.name = name; setNegLog10PError(negLog10PError); - if ( filters != null ) - setFilters(filters); - if ( attributes != null ) - setAttributes(attributes); + if ( filters != null && ! filters.isEmpty() ) + this.filters = filters; + if ( attributes != null && ! attributes.isEmpty() ) { + this.attributes = attributes; + } } /** @@ -157,7 +149,7 @@ public final class InferredGeneticContext { if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable attributes = new HashMap(); - + attributes.put(key, value); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java index 14419a2a0..fdffb1e10 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java @@ -17,7 +17,7 @@ public class MutableGenotype extends Genotype { } - public MutableGenotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean genotypesArePhased) { + public MutableGenotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean genotypesArePhased) { super(sampleName, alleles, negLog10PError, filters, attributes, genotypesArePhased); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java index a752f4a1b..d563c5180 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java @@ -18,11 +18,11 @@ public class MutableVariantContext extends VariantContext { // // --------------------------------------------------------------------------------------------------------- - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); } - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index f52a7087b..ba96b13d8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -222,7 +222,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false); } @@ -239,7 +239,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false); } @@ -260,7 +260,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { this(source, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true); } @@ -277,7 +277,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { this(source, contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes, null, false); } @@ -334,7 +334,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ private VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, - double negLog10PError, Set filters, Map attributes, + double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } this.contig = contig; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 43f91041f..0bb01dbb5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -69,7 +69,7 @@ public class VariantContextUtils { * @param attributes attributes * @return VariantContext object */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes != null ? VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes); } diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index 35c6a4993..ea06d897e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -120,7 +120,7 @@ public class VCFWriterUnitTest extends BaseTest { GenomeLoc loc = genomeLocParser.createGenomeLoc("chr1",1); List alleles = new ArrayList(); Set filters = null; - Map attributes = new HashMap(); + Map attributes = new HashMap(); Map genotypes = new HashMap(); alleles.add(Allele.create("-",true)); @@ -128,7 +128,7 @@ public class VCFWriterUnitTest extends BaseTest { attributes.put("DP","50"); for (String name : header.getGenotypeSamples()) { - Map gtattributes = new HashMap(); + Map gtattributes = new HashMap(); gtattributes.put("BB","1"); Genotype gt = new Genotype(name,alleles.subList(1,2),0,null,gtattributes,true); From e216e85465a51d86860217d581c0eb5f64c28efa Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 09:56:00 -0500 Subject: [PATCH 002/113] First working version of VariantContextBenchmark --- .../VariantContextBenchmark.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java new file mode 100644 index 000000000..2f8419b5e --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext; + +import com.google.caliper.Param; +import com.google.caliper.SimpleBenchmark; +import com.google.caliper.runner.CaliperMain; +import org.broad.tribble.readers.AsciiLineReader; +import org.broadinstitute.sting.utils.codecs.vcf.VCFCodec; + +import java.io.*; +import java.util.*; + +/** + * Caliper microbenchmark of parsing a VCF file + */ +public class VariantContextBenchmark extends SimpleBenchmark { + @Param({"/Users/depristo/Desktop/broadLocal/localData/ALL.chr20.merged_beagle_mach.20101123.snps_indels_svs.genotypes.vcf"}) + String vcfFile; + + @Param({"1000"}) + int linesToRead; // set automatically by framework + + @Param({"100"}) + int nSamplesToTake; // set automatically by framework + + private String INPUT_STRING; + + private enum Operation { + READ, + READ_SUBSET + } + + @Override protected void setUp() { + // read it into a String so that we don't try to benchmark IO issues + try { + FileInputStream s = new FileInputStream(new File(vcfFile)); + AsciiLineReader lineReader = new AsciiLineReader(s); + int counter = 0; + StringBuffer sb = new StringBuffer(); + while (counter++ < linesToRead ) { + String line = lineReader.readLine(); + if ( line == null ) + break; + sb.append(line + "\n"); + } + s.close(); + INPUT_STRING = sb.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void parseGenotypes(VCFCodec codec, Operation op) { + try { + InputStream is = new ByteArrayInputStream(INPUT_STRING.getBytes()); + AsciiLineReader lineReader = new AsciiLineReader(is); + codec.readHeader(lineReader); + + int counter = 0; + List samples = null; + while (counter++ < linesToRead ) { + String line = lineReader.readLine(); + if ( line == null ) + break; + + VariantContext vc = (VariantContext)codec.decode(line); + if ( samples == null ) { + samples = new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake); + } + + if ( op == Operation.READ_SUBSET) + processOneVC(vc, samples); + } + } catch (Exception e) { + System.out.println("Benchmarking run failure because of " + e.getMessage()); + } + } + + public void timeOriginalRead(int rep) { + for ( int i = 0; i < rep; i++ ) + parseGenotypes(new VCFCodec(), Operation.READ); + } + + public void timeOriginalReadSubset(int rep) { + for ( int i = 0; i < rep; i++ ) + parseGenotypes(new VCFCodec(), Operation.READ_SUBSET); + } + + public static void main(String[] args) { + CaliperMain.main(VariantContextBenchmark.class, args); + } + + private static final void processOneVC(VariantContext vc, List samples) { + VariantContext sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values(), vc.getAlleles()); + sub.getNSamples(); + } + +} From ef9f8b5d4618f97e75fecef75f5357bd58e1fe43 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 10:07:11 -0500 Subject: [PATCH 003/113] Added subContextOfSamples to VariantContext -- This is a more convenient accesssor than subContextOfGenotypes, represents nearly all of the use cases of the former function, and potentially can be implemented more efficiently. --- .../varianteval/util/VariantEvalUtils.java | 2 +- .../walkers/variantutils/SelectVariants.java | 8 +------- .../utils/variantcontext/VariantContext.java | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index aa8c6cfb9..e700a733c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -277,7 +277,7 @@ public class VariantEvalUtils { * @return a new VariantContext with just the requested samples */ public VariantContext getSubsetOfVariantContext(VariantContext vc, Collection sampleNames) { - VariantContext vcsub = vc.subContextFromGenotypes(vc.getGenotypes(sampleNames).values(), vc.getAlleles()); + VariantContext vcsub = vc.subContextFromSamples(sampleNames, vc.getAlleles()); HashMap newAts = new HashMap(vcsub.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 609593acc..0efb46bf5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -659,13 +659,7 @@ public class SelectVariants extends RodWalker { if ( samples == null || samples.isEmpty() ) return vc; - ArrayList genotypes = new ArrayList(); - for ( Map.Entry genotypePair : vc.getGenotypes().entrySet() ) { - if ( samples.contains(genotypePair.getKey()) ) - genotypes.add(genotypePair.getValue()); - } - - VariantContext sub = vc.subContextFromGenotypes(genotypes, vc.getAlleles()); + VariantContext sub = vc.subContextFromSamples(samples); // if we have fewer alternate alleles in the selected VC than in the original VC, we need to strip out the GL/PLs (because they are no longer accurate) if ( vc.getAlleles().size() != sub.getAlleles().size() ) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index ba96b13d8..db398b478 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -446,9 +446,25 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return vc subcontext */ public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { - return new VariantContext(getSource(), contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), getReferenceBaseForIndel()); + return new VariantContext(getSource(), contig, start, stop, alleles, + genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, + getNegLog10PError(), + filtersWereApplied() ? getFilters() : null, + getAttributes(), + getReferenceBaseForIndel()); } + public VariantContext subContextFromSamples(Collection sampleNames, Collection alleles) { + return subContextFromGenotypes(getGenotypes(sampleNames).values(), alleles); + } + + public VariantContext subContextFromSamples(Collection sampleNames) { + return subContextFromGenotypes(getGenotypes(sampleNames).values()); + } + + public VariantContext subContextFromSample(String sampleName) { + return subContextFromGenotypes(getGenotype(sampleName)); + } /** * helper routine for subcontext From 4938569b3ac26eb1fb3e66ab5ffb47ac0723f03e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 10:22:19 -0500 Subject: [PATCH 004/113] More general handling of parameters for VariantContextBenchmark --- .../VariantContextBenchmark.java | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index 2f8419b5e..7ad2c5c1b 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -46,13 +46,24 @@ public class VariantContextBenchmark extends SimpleBenchmark { @Param({"100"}) int nSamplesToTake; // set automatically by framework + @Param({"READ", "READ_SUBSET"}) + Operation operation; // set automatically by framework + + @Param({"OF_GENOTYPES", "OF_SAMPLES"}) + SubContextOp subContextOp; // set automatically by framework + private String INPUT_STRING; - private enum Operation { + public enum Operation { READ, READ_SUBSET } + public enum SubContextOp { + OF_GENOTYPES, + OF_SAMPLES + } + @Override protected void setUp() { // read it into a String so that we don't try to benchmark IO issues try { @@ -73,7 +84,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { } } - private void parseGenotypes(VCFCodec codec, Operation op) { + private void parseGenotypes(VCFCodec codec, Operation op, SubContextOp subop ) { try { InputStream is = new ByteArrayInputStream(INPUT_STRING.getBytes()); AsciiLineReader lineReader = new AsciiLineReader(is); @@ -92,30 +103,36 @@ public class VariantContextBenchmark extends SimpleBenchmark { } if ( op == Operation.READ_SUBSET) - processOneVC(vc, samples); + processOneVC(vc, samples, subop); } } catch (Exception e) { System.out.println("Benchmarking run failure because of " + e.getMessage()); } } - public void timeOriginalRead(int rep) { + public void timeMe(int rep) { for ( int i = 0; i < rep; i++ ) - parseGenotypes(new VCFCodec(), Operation.READ); - } - - public void timeOriginalReadSubset(int rep) { - for ( int i = 0; i < rep; i++ ) - parseGenotypes(new VCFCodec(), Operation.READ_SUBSET); + parseGenotypes(new VCFCodec(), operation, subContextOp); } public static void main(String[] args) { CaliperMain.main(VariantContextBenchmark.class, args); } - private static final void processOneVC(VariantContext vc, List samples) { - VariantContext sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values(), vc.getAlleles()); + private static final void processOneVC(VariantContext vc, List samples, SubContextOp subop) { + VariantContext sub; + + switch ( subop ) { + case OF_GENOTYPES: + sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values(), vc.getAlleles()); + break; + case OF_SAMPLES: + sub = vc.subContextFromSamples(samples, vc.getAlleles()); + break; + default: + throw new RuntimeException("Unexpected op: " + subop); + } + sub.getNSamples(); } - } From fee9b367e49e8fdc798c9d511ecb1f7b9eed9efd Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 15:00:35 -0500 Subject: [PATCH 005/113] VariantContext genotypes are now stored as GenotypeMap objects -- Enables further sophisticated optimizations, as this class can be smarter about storing the data and will directly support operations like subset to samples -- All instances in the gatk that used Map now use GenotypeMap type. -- Amazingly, there were many places where HashMap is used, so that the order of the genotypes is technically undefined and could be dangerous. Now everything uses GenotypeMap with a specific ordering of samples (by name) -- Integrationtests updated and all pass --- .../gatk/refdata/VariantContextAdaptors.java | 4 +- .../gatk/walkers/annotator/AlleleBalance.java | 3 +- .../gatk/walkers/annotator/HardyWeinberg.java | 3 +- .../walkers/annotator/InbreedingCoeff.java | 3 +- .../gatk/walkers/annotator/QualByDepth.java | 3 +- .../gatk/walkers/annotator/RankSumTest.java | 3 +- .../annotator/VariantAnnotatorEngine.java | 5 +- .../beagle/BeagleOutputToVCFWalker.java | 10 +-- .../filters/VariantFiltrationWalker.java | 5 +- .../AlleleFrequencyCalculationModel.java | 7 +- .../genotyper/ExactAFCalculationModel.java | 11 ++-- .../genotyper/GridSearchAFEstimation.java | 9 +-- .../walkers/genotyper/UGCallVariants.java | 3 +- .../genotyper/UnifiedGenotyperEngine.java | 6 +- .../indels/SomaticIndelDetectorWalker.java | 5 +- .../walkers/phasing/PhaseByTransmission.java | 7 +- .../phasing/ReadBackedPhasingWalker.java | 11 ++-- .../evaluators/GenotypePhasingEvaluator.java | 5 +- .../variantutils/LeftAlignVariants.java | 3 +- .../walkers/variantutils/SelectVariants.java | 9 +-- .../walkers/variantutils/VariantsToVCF.java | 7 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 3 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 5 +- .../sting/utils/codecs/vcf/VCFCodec.java | 5 +- .../sting/utils/codecs/vcf/VCFParser.java | 3 +- .../broadinstitute/sting/utils/gcf/GCF.java | 7 +- .../utils/variantcontext/GenotypeMap.java | 66 +++++++++++++++++++ .../variantcontext/MutableVariantContext.java | 5 +- .../utils/variantcontext/VariantContext.java | 44 +++++-------- .../variantcontext/VariantContextUtils.java | 20 +++--- .../VariantAnnotatorIntegrationTest.java | 6 +- .../UnifiedGenotyperIntegrationTest.java | 19 +++--- .../utils/genotype/vcf/VCFWriterUnitTest.java | 3 +- .../VariantContextUtilsUnitTest.java | 3 +- 34 files changed, 186 insertions(+), 125 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index 941dc66b5..cb26f3bf5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -194,7 +194,7 @@ public class VariantContextAdaptors { return null; // we weren't given enough reference context to create the VariantContext Byte refBaseForIndel = new Byte(ref.getBases()[index]); - Map genotypes = null; + GenotypeMap genotypes = null; VariantContext vc = new VariantContext(name, dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0), alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attributes, refBaseForIndel); return vc; } else @@ -329,7 +329,7 @@ public class VariantContextAdaptors { String[] samples = hapmap.getSampleIDs(); String[] genotypeStrings = hapmap.getGenotypes(); - Map genotypes = new HashMap(samples.length); + GenotypeMap genotypes = GenotypeMap.create(samples.length); for ( int i = 0; i < samples.length; i++ ) { // ignore bad genotypes if ( genotypeStrings[i].contains("N") ) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java index 3a21e97a4..297490172 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java @@ -35,6 +35,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -54,7 +55,7 @@ public class AlleleBalance extends InfoFieldAnnotation { if ( !vc.isBiallelic() ) return null; - final Map genotypes = vc.getGenotypes(); + final GenotypeMap genotypes = vc.getGenotypes(); if ( !vc.hasGenotypes() ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java index f068ed895..6352fcf2a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java @@ -11,6 +11,7 @@ import org.broadinstitute.sting.utils.QualityUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -30,7 +31,7 @@ public class HardyWeinberg extends InfoFieldAnnotation implements WorkInProgress public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - final Map genotypes = vc.getGenotypes(); + final GenotypeMap genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() < MIN_SAMPLES ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java index 8728e5aa4..9935eced9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java @@ -10,6 +10,7 @@ import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -32,7 +33,7 @@ public class InbreedingCoeff extends InfoFieldAnnotation implements StandardAnno public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - final Map genotypes = vc.getGenotypes(); + final GenotypeMap genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() < MIN_SAMPLES ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java index b942d9817..e34ef5d45 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java @@ -9,6 +9,7 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.StandardAnnota import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -28,7 +29,7 @@ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotati if ( stratifiedContexts.size() == 0 ) return null; - final Map genotypes = vc.getGenotypes(); + final GenotypeMap genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java index 93e093248..f75997d57 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java @@ -13,6 +13,7 @@ import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.ArrayList; @@ -32,7 +33,7 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar if ( stratifiedContexts.size() == 0 ) return null; - final Map genotypes = vc.getGenotypes(); + final GenotypeMap genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index e4bc0d5d5..87b1366cf 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -34,6 +34,7 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.*; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; @@ -216,11 +217,11 @@ public class VariantAnnotatorEngine { } } - private Map annotateGenotypes(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { + private GenotypeMap annotateGenotypes(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { if ( requestedGenotypeAnnotations.size() == 0 ) return vc.getGenotypes(); - Map genotypes = new HashMap(vc.getNSamples()); + GenotypeMap genotypes = GenotypeMap.create(vc.getNSamples()); for ( Map.Entry g : vc.getGenotypes().entrySet() ) { Genotype genotype = g.getValue(); AlignmentContext context = stratifiedContexts.get(g.getKey()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 7f6dabeec..352b1790e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -36,10 +36,7 @@ import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -190,8 +187,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { byte refByte = ref.getBase(); // make new Genotypes based on Beagle results - Map genotypes = new HashMap(vc_input.getGenotypes().size()); - + GenotypeMap genotypes = GenotypeMap.create(vc_input.getGenotypes().size()); // for each genotype, create a new object with Beagle information on it @@ -200,7 +196,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { Double alleleFrequencyH = 0.0; int beagleVarCounts = 0; - Map hapmapGenotypes = null; + GenotypeMap hapmapGenotypes = null; if (vc_comp != null) { hapmapGenotypes = vc_comp.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index bf3606b54..9428fd7ee 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -37,6 +37,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -282,11 +283,11 @@ public class VariantFiltrationWalker extends RodWalker { VariantContext vc = context.getVariantContext(); // make new Genotypes based on filters - Map genotypes; + GenotypeMap genotypes; if ( genotypeFilterExps.size() == 0 ) { genotypes = null; } else { - genotypes = new HashMap(vc.getGenotypes().size()); + genotypes = GenotypeMap.create(vc.getGenotypes().size()); // for each genotype, check filters then create a new object for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java index 35a9fe31d..2bee98879 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java @@ -30,6 +30,7 @@ import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; @@ -85,7 +86,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * * @return calls */ - protected abstract Map assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, - int AFofMaxLikelihood); + protected abstract GenotypeMap assignGenotypes(VariantContext vc, + double[] log10AlleleFrequencyPosteriors, + int AFofMaxLikelihood); } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index 1c2d82ab7..0e3062cfc 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -33,6 +33,7 @@ import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; @@ -268,14 +269,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { * * @return calls */ - public Map assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, - int AFofMaxLikelihood) { + public GenotypeMap assignGenotypes(VariantContext vc, + double[] log10AlleleFrequencyPosteriors, + int AFofMaxLikelihood) { if ( !vc.isVariant() ) throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); - Map GLs = vc.getGenotypes(); + GenotypeMap GLs = vc.getGenotypes(); double[][] pathMetricArray = new double[GLs.size()+1][AFofMaxLikelihood+1]; int[][] tracebackArray = new int[GLs.size()+1][AFofMaxLikelihood+1]; @@ -342,7 +343,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - HashMap calls = new HashMap(); + GenotypeMap calls = GenotypeMap.create(); int startIdx = AFofMaxLikelihood; for (int k = sampleIdx; k > 0; k--) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java index 27842a8bf..bb31045a7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java @@ -34,6 +34,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; @@ -89,15 +90,15 @@ public class GridSearchAFEstimation extends AlleleFrequencyCalculationModel { * * @return calls */ - protected Map assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, - int AFofMaxLikelihood) { + protected GenotypeMap assignGenotypes(VariantContext vc, + double[] log10AlleleFrequencyPosteriors, + int AFofMaxLikelihood) { if ( !vc.isVariant() ) throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); Allele refAllele = vc.getReference(); Allele altAllele = vc.getAlternateAllele(0); - HashMap calls = new HashMap(); + GenotypeMap calls = GenotypeMap.create(); // first, the potential alt calls for ( String sample : AFMatrix.getSamples() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index d88e55687..c54089350 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -36,6 +36,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -128,7 +129,7 @@ public class UGCallVariants extends RodWalker { return null; VariantContext variantVC = null; - Map genotypes = new HashMap(); + GenotypeMap genotypes = GenotypeMap.create(); for ( VariantContext vc : VCs ) { if ( variantVC == null && vc.isVariant() ) variantVC = vc; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index cee128a6a..bc9a5f65b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -265,7 +265,7 @@ public class UnifiedGenotyperEngine { alleles.add(refAllele); boolean addedAltAlleles = false; - HashMap genotypes = new HashMap(); + GenotypeMap genotypes = GenotypeMap.create(); for ( MultiallelicGenotypeLikelihoods GL : GLs.values() ) { if ( !addedAltAlleles ) { addedAltAlleles = true; @@ -354,7 +354,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - Map genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypeMap genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); // print out stats if we have a writer if ( verboseWriter != null ) @@ -491,7 +491,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - Map genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypeMap genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); // *** note that calculating strand bias involves overwriting data structures, so we do that last HashMap attributes = new HashMap(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index a97117acd..ee5562ba2 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -60,6 +60,7 @@ import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -1057,7 +1058,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { stop += event_length; } - Map genotypes = new HashMap(); + GenotypeMap genotypes = GenotypeMap.create(); for ( String sample : normalSamples ) { @@ -1147,7 +1148,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { homRefAlleles.add( alleles.get(0)); homRefAlleles.add( alleles.get(0)); - Map genotypes = new HashMap(); + GenotypeMap genotypes = GenotypeMap.create(); for ( String sample : normalSamples ) { genotypes.put(sample,new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsNormal,false)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 3eedc2a28..6b52fcf62 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -12,10 +12,7 @@ import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.text.XReadLines; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.File; import java.io.FileNotFoundException; @@ -296,7 +293,7 @@ public class PhaseByTransmission extends RodWalker { if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); - Map genotypeMap = vc.getGenotypes(); + GenotypeMap genotypeMap = vc.getGenotypes(); for (Trio trio : trios) { Genotype mother = vc.getGenotype(trio.getMother()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 68fbe8ce2..2aa96379c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -41,10 +41,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.*; import java.util.*; @@ -355,7 +352,7 @@ public class ReadBackedPhasingWalker extends RodWalker sampGenotypes = vc.getGenotypes(); + GenotypeMap sampGenotypes = vc.getGenotypes(); Map samplePhaseStats = new TreeMap(); for (Map.Entry sampGtEntry : sampGenotypes.entrySet()) { String samp = sampGtEntry.getKey(); @@ -1126,7 +1123,7 @@ public class ReadBackedPhasingWalker extends RodWalker alleles; - private Map genotypes; + private GenotypeMap genotypes; private double negLog10PError; private Set filters; private Map attributes; @@ -1137,7 +1134,7 @@ public class ReadBackedPhasingWalker extends RodWalker(vc.getGenotypes()); // since vc.getGenotypes() is unmodifiable + this.genotypes = GenotypeMap.create(vc.getGenotypes()); // since vc.getGenotypes() is unmodifiable this.negLog10PError = vc.getNegLog10PError(); this.filters = vc.filtersWereApplied() ? vc.getFilters() : null; this.attributes = new HashMap(vc.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java index e69dbfb28..ad9ad62b3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java @@ -14,6 +14,7 @@ import org.broadinstitute.sting.gatk.walkers.varianteval.util.TableType; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.HashMap; @@ -91,13 +92,13 @@ public class GenotypePhasingEvaluator extends VariantEvaluator { Set allSamples = new HashSet(); - Map compSampGenotypes = null; + GenotypeMap compSampGenotypes = null; if (isRelevantToPhasing(comp)) { allSamples.addAll(comp.getSampleNames()); compSampGenotypes = comp.getGenotypes(); } - Map evalSampGenotypes = null; + GenotypeMap evalSampGenotypes = null; if (isRelevantToPhasing(eval)) { allSamples.addAll(eval.getSampleNames()); evalSampGenotypes = eval.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java index c9f330db5..64f54e611 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java @@ -40,6 +40,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; @@ -210,7 +211,7 @@ public class LeftAlignVariants extends RodWalker { } // create new Genotype objects - Map newGenotypes = new HashMap(vc.getNSamples()); + GenotypeMap newGenotypes = GenotypeMap.create(vc.getNSamples()); for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { List newAlleles = new ArrayList(); for ( Allele allele : genotype.getValue().getAlleles() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 0efb46bf5..3c5a55134 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -33,7 +33,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.text.XReadLines; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.utils.MendelianViolation; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import org.broadinstitute.sting.commandline.Argument; import org.broadinstitute.sting.commandline.Output; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; @@ -41,9 +41,6 @@ import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.SampleUtils; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; import java.io.File; import java.io.FileNotFoundException; @@ -561,7 +558,7 @@ public class SelectVariants extends RodWalker { return (compVCs == null || compVCs.isEmpty()); // check if we find it in the variant rod - Map genotypes = vc.getGenotypes(samples); + GenotypeMap genotypes = vc.getGenotypes(samples); for (Genotype g : genotypes.values()) { if (sampleHasVariant(g)) { // There is a variant called (or filtered with not exclude filtered option set) that is not HomRef for at least one of the samples. @@ -659,7 +656,7 @@ public class SelectVariants extends RodWalker { if ( samples == null || samples.isEmpty() ) return vc; - VariantContext sub = vc.subContextFromSamples(samples); + VariantContext sub = vc.subContextFromSamples(samples, vc.getAlleles()); // if we have fewer alternate alleles in the selected VC than in the original VC, we need to strip out the GL/PLs (because they are no longer accurate) if ( vc.getAlleles().size() != sub.getAlleles().size() ) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java index 9b33f8537..7f1fc9d16 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java @@ -42,10 +42,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.hapmap.RawHapMapFeature; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.File; import java.util.*; @@ -133,7 +130,7 @@ public class VariantsToVCF extends RodWalker { // set the appropriate sample name if necessary if ( sampleName != null && vc.hasGenotypes() && vc.hasGenotype(variants.getName()) ) { Genotype g = Genotype.modifyName(vc.getGenotype(variants.getName()), sampleName); - Map genotypes = new HashMap(); + GenotypeMap genotypes = GenotypeMap.create(1); genotypes.put(sampleName, g); vc = VariantContext.modifyGenotypes(vc, genotypes); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index 3377172dd..c285a9f68 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -12,6 +12,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -76,7 +77,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, * @param pos position * @return a mapping of sample name to genotype object */ - public abstract Map createGenotypeMap(String str, List alleles, String chr, int pos); + public abstract GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos); /** diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index bed66a439..fcfc0c6fc 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -5,6 +5,7 @@ import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; @@ -118,13 +119,13 @@ public class VCF3Codec extends AbstractVCFCodec { * @param pos position * @return a mapping of sample name to genotype object */ - public Map createGenotypeMap(String str, List alleles, String chr, int pos) { + public GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - Map genotypes = new LinkedHashMap(nParts); + GenotypeMap genotypes = GenotypeMap.create(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index 58dfd3589..eefb929bb 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -5,6 +5,7 @@ import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; @@ -145,13 +146,13 @@ public class VCFCodec extends AbstractVCFCodec { * @param alleles the list of alleles * @return a mapping of sample name to genotype object */ - public Map createGenotypeMap(String str, List alleles, String chr, int pos) { + public GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - Map genotypes = new LinkedHashMap(nParts); + GenotypeMap genotypes = GenotypeMap.create(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java index 1dba351e2..2887c5360 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java @@ -2,6 +2,7 @@ package org.broadinstitute.sting.utils.codecs.vcf; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import java.util.List; import java.util.Map; @@ -20,6 +21,6 @@ public interface VCFParser { * @param pos position * @return a mapping of sample name to genotype object */ - public Map createGenotypeMap(String str, List alleles, String chr, int pos); + public GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos); } diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index ef0d9ca42..7f15b4f5e 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -28,6 +28,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.StandardVCFWriter; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -145,16 +146,16 @@ public class GCF { Map attributes = new HashMap(); attributes.put("INFO", info); Byte refPadByte = refPad == 0 ? null : refPad; - Map genotypes = decodeGenotypes(header); + GenotypeMap genotypes = decodeGenotypes(header); return new VariantContext(source, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); } - private Map decodeGenotypes(final GCFHeader header) { + private GenotypeMap decodeGenotypes(final GCFHeader header) { if ( genotypes.isEmpty() ) return VariantContext.NO_GENOTYPES; else { - Map map = new TreeMap(); + GenotypeMap map = GenotypeMap.create(); for ( int i = 0; i < genotypes.size(); i++ ) { final String sampleName = header.getSample(i); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java new file mode 100644 index 000000000..319a3b8e8 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext; + +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +/** + * + */ +public class GenotypeMap extends TreeMap implements Map { + public final static GenotypeMap NO_GENOTYPES = new GenotypeMap(); + + public static final GenotypeMap create() { + return new GenotypeMap(); + } + + public static final GenotypeMap create(int nGenotypes) { + return new GenotypeMap(); + } + + public static final GenotypeMap create(final GenotypeMap genotypes) { + return create(genotypes.values()); + } + + public static final GenotypeMap create(final Map genotypes) { + return create(genotypes.values()); + } + + public static final GenotypeMap create(final Collection genotypes) { + if ( genotypes == null ) + return null; // todo -- really should return an empty map + else { + GenotypeMap genotypeMap = new GenotypeMap(); + for ( final Genotype g : genotypes ) { + if ( genotypeMap.containsKey(g.getSampleName() ) ) + throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); + genotypeMap.put(g.getSampleName(), g); + } + return genotypeMap; + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java index d563c5180..5059fc81c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java @@ -22,7 +22,7 @@ public class MutableVariantContext extends VariantContext { super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); } - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes) { super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); } @@ -72,7 +72,7 @@ public class MutableVariantContext extends VariantContext { } public void clearGenotypes() { - genotypes = new TreeMap(); + genotypes = GenotypeMap.create(); } /** @@ -98,7 +98,6 @@ public class MutableVariantContext extends VariantContext { * @param genotypes */ public void addGenotypes(Map genotypes) { - for ( Map.Entry elt : genotypes.entrySet() ) { addGenotype(elt.getValue()); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index db398b478..4682aad27 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -184,12 +184,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati final protected List alleles; /** A mapping from sampleName -> genotype objects for all genotypes associated with this context */ - protected Map genotypes = null; + protected GenotypeMap genotypes = null; /** Counts for each of the possible Genotype types in this context */ protected int[] genotypeCounts = null; - public final static Map NO_GENOTYPES = Collections.unmodifiableMap(new HashMap()); + public final static GenotypeMap NO_GENOTYPES = GenotypeMap.NO_GENOTYPES; // a fast cached access point to the ref / alt alleles for biallelic case private Allele REF = null; @@ -222,7 +222,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false); } @@ -239,7 +239,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes) { this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false); } @@ -278,7 +278,9 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes, null, false); + this(source, contig, start, stop, alleles, + GenotypeMap.create(genotypes), + negLog10PError, filters, attributes, null, false); } /** @@ -333,7 +335,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param genotypesAreUnparsed true if the genotypes have not yet been parsed */ private VariantContext(String source, String contig, long start, long stop, - Collection alleles, Map genotypes, + Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } @@ -357,9 +359,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati // we need to make this a LinkedHashSet in case the user prefers a given ordering of alleles this.alleles = makeAlleles(alleles); - if ( genotypes == null ) { genotypes = NO_GENOTYPES; } - this.genotypes = Collections.unmodifiableMap(genotypes); + this.genotypes = genotypes; // cache the REF and ALT alleles int nAlleles = alleles.size(); @@ -382,7 +383,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - public static VariantContext modifyGenotypes(VariantContext vc, Map genotypes) { + public static VariantContext modifyGenotypes(VariantContext vc, GenotypeMap genotypes) { return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), false); } @@ -447,7 +448,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { return new VariantContext(getSource(), contig, start, stop, alleles, - genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, + GenotypeMap.create(genotypes), getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), @@ -879,7 +880,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati /** * @return set of all Genotypes associated with this context */ - public Map getGenotypes() { + public GenotypeMap getGenotypes() { loadGenotypes(); return genotypes; } @@ -898,7 +899,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public Map getGenotypes(String sampleName) { + public GenotypeMap getGenotypes(String sampleName) { return getGenotypes(Arrays.asList(sampleName)); } @@ -910,8 +911,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public Map getGenotypes(Collection sampleNames) { - HashMap map = new HashMap(); + public GenotypeMap getGenotypes(Collection sampleNames) { + GenotypeMap map = GenotypeMap.create(sampleNames.size()); for ( String name : sampleNames ) { if ( map.containsKey(name) ) throw new IllegalArgumentException("Duplicate names detected in requested samples " + sampleNames); @@ -1402,16 +1403,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati return alleleList; } - public static Map genotypeCollectionToMap(Map dest, Collection genotypes) { - for ( Genotype g : genotypes ) { - if ( dest.containsKey(g.getSampleName() ) ) - throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); - dest.put(g.getSampleName(), g); - } - - return dest; - } - // --------------------------------------------------------------------------------------------------------- // // tribble integration routines -- not for public consumption @@ -1464,9 +1455,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati Byte refByte = inputVC.getReferenceBaseForIndel(); List alleles = new ArrayList(); - Map genotypes = new TreeMap(); - - Map inputGenotypes = inputVC.getGenotypes(); + GenotypeMap genotypes = GenotypeMap.create(); + GenotypeMap inputGenotypes = inputVC.getGenotypes(); for (Allele a : inputVC.getAlleles()) { // get bases for current allele and create a new one with trimmed bases diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 0bb01dbb5..ac28928ff 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -70,7 +70,7 @@ public class VariantContextUtils { * @return VariantContext object */ public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes != null ? VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes); + return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, GenotypeMap.create(genotypes), negLog10PError, filters, attributes); } /** @@ -404,7 +404,7 @@ public class VariantContextUtils { */ public static VariantContext masterMerge(Collection unsortedVCs, String masterName) { VariantContext master = findMaster(unsortedVCs, masterName); - Map genotypes = master.getGenotypes(); + GenotypeMap genotypes = master.getGenotypes(); for (Genotype g : genotypes.values()) { genotypes.put(g.getSampleName(), new MutableGenotype(g)); } @@ -526,7 +526,7 @@ public class VariantContextUtils { final Map attributesWithMaxAC = new TreeMap(); double negLog10PError = -1; VariantContext vcWithMaxAC = null; - Map genotypes = new TreeMap(); + GenotypeMap genotypes = GenotypeMap.create(); // counting the number of filtered and variant VCs int nFiltered = 0; @@ -716,7 +716,7 @@ public class VariantContextUtils { // nothing to do if we don't need to trim bases if (trimVC) { List alleles = new ArrayList(); - Map genotypes = new TreeMap(); + GenotypeMap genotypes = GenotypeMap.create(); // set the reference base for indels in the attributes Map attributes = new TreeMap(inputVC.getAttributes()); @@ -770,8 +770,8 @@ public class VariantContextUtils { return inputVC; } - public static Map stripPLs(Map genotypes) { - Map newGs = new HashMap(genotypes.size()); + public static GenotypeMap stripPLs(GenotypeMap genotypes) { + GenotypeMap newGs = GenotypeMap.create(genotypes.size()); for ( Map.Entry g : genotypes.entrySet() ) { newGs.put(g.getKey(), g.getValue().hasLikelihoods() ? removePLs(g.getValue()) : g.getValue()); @@ -951,7 +951,7 @@ public class VariantContextUtils { } } - private static void mergeGenotypes(Map mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { + private static void mergeGenotypes(GenotypeMap mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { for ( Genotype g : oneVC.getGenotypes().values() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); if ( ! mergedGenotypes.containsKey(name) ) { @@ -992,7 +992,7 @@ public class VariantContextUtils { } // create new Genotype objects - Map newGenotypes = new HashMap(vc.getNSamples()); + GenotypeMap newGenotypes = GenotypeMap.create(vc.getNSamples()); for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { List newAlleles = new ArrayList(); for ( Allele allele : genotype.getValue().getAlleles() ) { @@ -1012,7 +1012,7 @@ public class VariantContextUtils { if ( allowedAttributes == null ) return vc; - Map newGenotypes = new HashMap(vc.getNSamples()); + GenotypeMap newGenotypes = GenotypeMap.create(vc.getNSamples()); for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { Map attrs = new HashMap(); for ( Map.Entry attr : genotype.getValue().getAttributes().entrySet() ) { @@ -1091,7 +1091,7 @@ public class VariantContextUtils { } MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added - Map mergedGenotypes = new HashMap(); + GenotypeMap mergedGenotypes = GenotypeMap.create(); for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { String sample = gt1Entry.getKey(); Genotype gt1 = gt1Entry.getValue(); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index 189f643d4..28c0a31a6 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -32,7 +32,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testHasAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("8e7de435105499cd71ffc099e268a83e")); + Arrays.asList("a6687f0d3830fa6e518b7874857f6f70")); executeTest("test file has annotations, asking for annotations, #1", spec); } @@ -64,7 +64,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("fd1ffb669800c2e07df1e2719aa38e49")); + Arrays.asList("b59508cf66da6b2de280a79b3b7d85b1")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -80,7 +80,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testExcludeAnnotations() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard -XA FisherStrand -XA ReadPosRankSumTest --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("b49fe03aa4b675db80a9db38a3552c95")); + Arrays.asList("b8e18b23568e4d2381f51d4430213040")); executeTest("test exclude annotations", spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index b80f214b1..0110b847d 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -30,20 +30,23 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testMultiSamplePilot1() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -o %s -L 1:10,022,000-10,025,000", 1, - Arrays.asList("b27939251539439a382538e507e03507")); + Arrays.asList("c93def488de12fb3b8199e001b7a24a8")); executeTest("test MultiSample Pilot1", spec); } @Test - public void testWithAllelesPassedIn() { + public void testWithAllelesPassedIn1() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("8de2602679ffc92388da0b6cb4325ef6")); + Arrays.asList("cef4bd72cbbe72f28e9c72e2818b4708")); executeTest("test MultiSample Pilot2 with alleles passed in", spec1); + } + @Test + public void testWithAllelesPassedIn2() { WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("6458f3b8fe4954e2ffc2af972aaab19e")); + Arrays.asList("14f5cdfc6818cbba600cbdf5fe285275")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } @@ -261,7 +264,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("118918f2e9e56a3cfc5ccb2856d529c8")); + Arrays.asList("81a1035e59cd883e413e62d34265c1a2")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -271,7 +274,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { baseCommandIndels + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("a20799237accd52c1b8c2ac096309c8f")); + Arrays.asList("102b7d915f21dff0a9b6ea64c4c7d409")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -281,7 +284,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2.20101123.indels.sites.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,080,000", 1, - Arrays.asList("18ef8181157b4ac3eb8492f538467f92")); + Arrays.asList("5900344f97bbac35d147a0a7c2bf1d0c")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -290,7 +293,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec4 = new WalkerTest.WalkerTestSpec( baseCommandIndelsb37 + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2_chr20_100_110K.20101123.indels.sites.vcf -I " + validationDataLocation + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("ad884e511a751b05e64db5314314365a")); + Arrays.asList("45e7bf21cd6358921626404e7ae76c69")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index ea06d897e..b658da1d3 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -4,6 +4,7 @@ import org.broad.tribble.Tribble; import org.broad.tribble.readers.AsciiLineReader; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -121,7 +122,7 @@ public class VCFWriterUnitTest extends BaseTest { List alleles = new ArrayList(); Set filters = null; Map attributes = new HashMap(); - Map genotypes = new HashMap(); + GenotypeMap genotypes = GenotypeMap.create(); alleles.add(Allele.create("-",true)); alleles.add(Allele.create("CC",false)); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index 845d9c216..bfda19b6b 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -99,8 +99,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { int start = 10; int stop = start; // alleles.contains(ATC) ? start + 3 : start; return new VariantContext(source, "1", start, stop, alleles, - genotypes == null ? null : VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes), - 1.0, filters, null, Cref.getBases()[0]); + GenotypeMap.create(genotypes), 1.0, filters, null, Cref.getBases()[0]); } // -------------------------------------------------------------------------------- From 1202a809cb8ac10193a4fb2cd49f3b843e18f82e Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Sun, 13 Nov 2011 22:27:49 -0500 Subject: [PATCH 006/113] Added Basic Unit Tests for ReadClipper Tests some but not all functions Some tests have been disabled because they are not working --- .../utils/clipreads/ReadClipperUnitTest.java | 152 +++++++++++------- 1 file changed, 94 insertions(+), 58 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java index f625af23c..0c71a845e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -30,9 +30,12 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; -import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; +import java.util.LinkedList; +import java.util.List; + /** * Created by IntelliJ IDEA. * User: roger @@ -44,44 +47,57 @@ public class ReadClipperUnitTest extends BaseTest { // TODO: Add error messages on failed tests + + //int debug = 0; + GATKSAMRecord read, expected; ReadClipper readClipper; final static String BASES = "ACTG"; final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 - @BeforeClass + // What the test read looks like + // Ref: 1 2 3 4 5 6 7 8 + // Read: 0 1 2 3 - - - - + // ----------------------------- + // Bases: A C T G - - - - + // Quals: ! + 5 ? - - - - + + @BeforeTest public void init() { SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, BASES.length()); - read.setReadUnmappedFlag(true); read.setReadBases(new String(BASES).getBytes()); read.setBaseQualityString(new String(QUALS)); readClipper = new ReadClipper(read); + //logger.warn(read.getCigarString()); } - @Test ( enabled = false ) + @Test ( enabled = true ) public void testHardClipBothEndsByReferenceCoordinates() { + init(); logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); - + //int debug = 1; //Clip whole read - Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(0,0), new GATKSAMRecord(read.getHeader())); + Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(1,1), new GATKSAMRecord(read.getHeader())); //clip 1 base - expected = readClipper.hardClipBothEndsByReferenceCoordinates(0,3); + expected = readClipper.hardClipBothEndsByReferenceCoordinates(1,4); Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); Assert.assertEquals(expected.getCigarString(), "1H2M1H"); } - @Test ( enabled = false ) + @Test ( enabled = false ) // TODO This fails at hardClipCigar and returns a NullPointerException public void testHardClipByReadCoordinates() { + init(); logger.warn("Executing testHardClipByReadCoordinates"); //Clip whole read Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); //clip 1 base at start + System.out.println(readClipper.read.getCigarString()); expected = readClipper.hardClipByReadCoordinates(0,0); Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); @@ -107,83 +123,101 @@ public class ReadClipperUnitTest extends BaseTest { } - @Test ( enabled = false ) + public void testIfEqual( GATKSAMRecord read, byte[] readBases, String baseQuals, String cigar) { + Assert.assertEquals(read.getReadBases(), readBases); + Assert.assertEquals(read.getBaseQualityString(), baseQuals); + Assert.assertEquals(read.getCigarString(), cigar); + } + + public class testParameter { + int inputStart; + int inputStop; + int substringStart; + int substringStop; + String cigar; + + public testParameter(int InputStart, int InputStop, int SubstringStart, int SubstringStop, String Cigar) { + inputStart = InputStart; + inputStop = InputStop; + substringStart = SubstringStart; + substringStop = SubstringStop; + cigar = Cigar; + } + } + + @Test ( enabled = true ) public void testHardClipByReferenceCoordinates() { logger.warn("Executing testHardClipByReferenceCoordinates"); - + //logger.warn(debug); //Clip whole read Assert.assertEquals(readClipper.hardClipByReferenceCoordinates(1,4), new GATKSAMRecord(read.getHeader())); - //clip 1 base at start - expected = readClipper.hardClipByReferenceCoordinates(-1,1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); + List testList = new LinkedList(); + testList.add(new testParameter(-1,1,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(4,-1,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(-1,2,2,4,"2H2M"));//clip 2 bases at start + testList.add(new testParameter(3,-1,0,2,"2M2H"));//clip 2 bases at end - //clip 1 base at end - expected = readClipper.hardClipByReferenceCoordinates(3,-1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); - - //clip 2 bases at start - expected = readClipper.hardClipByReferenceCoordinates(-1,2); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); - - //clip 2 bases at end - expected = readClipper.hardClipByReferenceCoordinates(2,-1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReferenceCoordinates(p.inputStart,p.inputStop), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } } - @Test ( enabled = false ) + @Test ( enabled = true ) public void testHardClipByReferenceCoordinatesLeftTail() { + init(); logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); //Clip whole read Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(4), new GATKSAMRecord(read.getHeader())); - //clip 1 base at start - expected = readClipper.hardClipByReferenceCoordinatesLeftTail(1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); + List testList = new LinkedList(); + testList.add(new testParameter(1,-1,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(2,-1,2,4,"2H2M"));//clip 2 bases at start - //clip 2 bases at start - expected = readClipper.hardClipByReferenceCoordinatesLeftTail(2); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReferenceCoordinatesLeftTail(p.inputStart), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } } - @Test ( enabled = false ) + @Test ( enabled = true ) public void testHardClipByReferenceCoordinatesRightTail() { + init(); logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); //Clip whole read Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(1), new GATKSAMRecord(read.getHeader())); - //clip 1 base at end - expected = readClipper.hardClipByReferenceCoordinatesRightTail(3); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); + List testList = new LinkedList(); + testList.add(new testParameter(-1,4,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(-1,3,0,2,"2M2H"));//clip 2 bases at end - //clip 2 bases at end - expected = readClipper.hardClipByReferenceCoordinatesRightTail(2); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReferenceCoordinatesRightTail(p.inputStop), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } } - @Test ( enabled = false ) + @Test ( enabled = false ) // TODO This function is returning null reads public void testHardClipLowQualEnds() { + init(); logger.warn("Executing testHardClipByReferenceCoordinates"); @@ -192,6 +226,7 @@ public class ReadClipperUnitTest extends BaseTest { //clip 1 base at start expected = readClipper.hardClipLowQualEnds((byte)34); + logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); Assert.assertEquals(expected.getCigarString(), "1H3M"); @@ -203,10 +238,11 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertEquals(expected.getCigarString(), "2H2M"); // Reverse Quals sequence - readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + //readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 //clip 1 base at end - expected = readClipper.hardClipLowQualEnds((byte)34); + expected = readClipper.hardClipLowQualEnds((byte)'!'); + logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); Assert.assertEquals(expected.getCigarString(), "3M1H"); @@ -220,4 +256,4 @@ public class ReadClipperUnitTest extends BaseTest { // revert Qual sequence readClipper.getRead().setBaseQualityString(QUALS); } -} +} \ No newline at end of file From 3d2970453b79fff8cd89dd5e5984d3cacb9b58ea Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 14 Nov 2011 09:41:54 -0500 Subject: [PATCH 007/113] Misc minor cleanup --- .../sting/gatk/walkers/recalibration/CycleCovariate.java | 2 -- .../org/broadinstitute/sting/utils/pileup/PileupElement.java | 4 ++-- .../src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java index e10334a77..6b4fec04e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java @@ -6,9 +6,7 @@ import org.broadinstitute.sting.utils.NGSPlatform; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import java.util.Arrays; import java.util.EnumSet; -import java.util.List; /* * Copyright (c) 2009 The Broad Institute diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java index daf6606ef..bab20b9e8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java @@ -95,11 +95,11 @@ public class PileupElement implements Comparable { // -------------------------------------------------------------------------- public boolean isReducedRead() { - return ((GATKSAMRecord)read).isReducedRead(); + return read.isReducedRead(); } public int getRepresentativeCount() { - return isReducedRead() ? ((GATKSAMRecord)read).getReducedCount(offset) : 1; + return isReducedRead() ? read.getReducedCount(offset) : 1; } } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java index e125b8c80..8d9018045 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -243,7 +243,7 @@ public class ReadUtils { public static GATKSAMRecord hardClipAdaptorSequence(final GATKSAMRecord read, int adaptorLength) { Pair adaptorBoundaries = getAdaptorBoundaries(read, adaptorLength); - GATKSAMRecord result = (GATKSAMRecord)read; + GATKSAMRecord result = read; if ( adaptorBoundaries != null ) { if ( read.getReadNegativeStrandFlag() && adaptorBoundaries.second >= read.getAlignmentStart() && adaptorBoundaries.first < read.getAlignmentEnd() ) From 7aee80cd3b909c4982d932f2a9bea6ab762b408d Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 14 Nov 2011 12:23:46 -0500 Subject: [PATCH 008/113] Fix to deal with reduced reads containing a deletion --- .../org/broadinstitute/sting/utils/pileup/PileupElement.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java index bab20b9e8..2d13d6e59 100755 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java @@ -99,7 +99,8 @@ public class PileupElement implements Comparable { } public int getRepresentativeCount() { - return isReducedRead() ? read.getReducedCount(offset) : 1; + // TODO -- if we ever decide to reduce the representation of deletions then this will need to be fixed + return (!isDeletion() && isReducedRead()) ? read.getReducedCount(offset) : 1; } } \ No newline at end of file From 79987d685cd76163514fc57e0e5211712ac8e3f6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 12:55:03 -0500 Subject: [PATCH 009/113] GenotypeMap contains a Map, not extends it -- On path to replacing it with GenotypeCollection --- .../utils/variantcontext/GenotypeMap.java | 137 +++++++++++++++++- .../UnifiedGenotyperIntegrationTest.java | 2 +- 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java index 319a3b8e8..cb7250bdb 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java @@ -24,21 +24,46 @@ package org.broadinstitute.sting.utils.variantcontext; -import java.util.Collection; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; /** * */ -public class GenotypeMap extends TreeMap implements Map { +public class GenotypeMap implements Map { + final TreeMap genotypes; + boolean immutable = false; public final static GenotypeMap NO_GENOTYPES = new GenotypeMap(); + // --------------------------------------------------------------------------- + // + // private constructors -- you have to use static create methods to make these classes + // + // --------------------------------------------------------------------------- + + private GenotypeMap() { + this(false); + } + + private GenotypeMap(boolean immutable) { + this(new TreeMap(), immutable); + } + + private GenotypeMap(final TreeMap genotypes, final boolean immutable) { + this.genotypes = genotypes; + this.immutable = immutable; + } + + // --------------------------------------------------------------------------- + // + // public static factory methods + // + // --------------------------------------------------------------------------- + public static final GenotypeMap create() { return new GenotypeMap(); } - public static final GenotypeMap create(int nGenotypes) { + public static final GenotypeMap create(final int nGenotypes) { return new GenotypeMap(); } @@ -46,6 +71,9 @@ public class GenotypeMap extends TreeMap implements Map genotypes) { return create(genotypes.values()); } @@ -54,13 +82,110 @@ public class GenotypeMap extends TreeMap implements Map map) { + checkImmutability(); + genotypes.putAll(map); + } + + @Override + public Set keySet() { + return Collections.unmodifiableSet(genotypes.keySet()); + } + + @Override + public Collection values() { + return genotypes.values(); + } + + @Override + public Set> entrySet() { + return genotypes.entrySet(); + } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index 0110b847d..1c01fbdd4 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -46,7 +46,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithAllelesPassedIn2() { WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("14f5cdfc6818cbba600cbdf5fe285275")); + Arrays.asList("9834f0cef1cd6ba4943a5aaee1ee8be8")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } From b11c5355278275d6a772a930c72be2125ca852b6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 13:16:36 -0500 Subject: [PATCH 011/113] Deleted MutableGenotype -- This class wasn't really used anywhere, and so removed to control code bloat. --- .../gatk/refdata/VariantContextAdaptors.java | 7 +- .../sting/utils/variantcontext/Genotype.java | 5 + .../utils/variantcontext/MutableGenotype.java | 68 ---------- .../variantcontext/VariantContextUtils.java | 121 ++++-------------- 4 files changed, 32 insertions(+), 169 deletions(-) delete mode 100755 public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index cb26f3bf5..c4ba5d6d1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -259,12 +259,7 @@ public class VariantContextAdaptors { Map attributes = new HashMap(); Collection genotypes = new ArrayList(); - MutableGenotype call = new MutableGenotype(name, genotypeAlleles); - - // set the likelihoods, depth, and RMS mapping quality values - //call.putAttribute(CalledGenotype.POSTERIORS_ATTRIBUTE_KEY,geli.getLikelihoods()); - //call.putAttribute(GeliTextWriter.MAXIMUM_MAPPING_QUALITY_ATTRIBUTE_KEY,geli.getMaximumMappingQual()); - //call.putAttribute(GeliTextWriter.READ_COUNT_ATTRIBUTE_KEY,geli.getDepthOfCoverage()); + Genotype call = new Genotype(name, genotypeAlleles); // add the call to the genotype list, and then use this list to create a VariantContext genotypes.add(call); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java index c59c002f2..eabd2dbad 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -59,6 +59,11 @@ public class Genotype { this(sampleName, alleles, NO_NEG_LOG_10PERROR, null, null, false); } + public Genotype(String sampleName, Genotype parent) { + this(sampleName, parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); + } + + // --------------------------------------------------------------------------------------------------------- // diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java deleted file mode 100755 index fdffb1e10..000000000 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext; - -import java.util.*; - -/** - * This class emcompasses all the basic information about a genotype. It is immutable. - * - * @author Mark DePristo - */ -public class MutableGenotype extends Genotype { - public MutableGenotype(Genotype parent) { - super(parent.getSampleName(), parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); - } - - public MutableGenotype(String sampleName, Genotype parent) { - super(sampleName, parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); - } - - - public MutableGenotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean genotypesArePhased) { - super(sampleName, alleles, negLog10PError, filters, attributes, genotypesArePhased); - } - - public MutableGenotype(String sampleName, List alleles, double negLog10PError) { - super(sampleName, alleles, negLog10PError); - } - - public MutableGenotype(String sampleName, List alleles) { - super(sampleName, alleles); - } - - public Genotype unmodifiableGenotype() { - return new Genotype(getSampleName(), getAlleles(), getNegLog10PError(), getFilters(), getAttributes(), isPhased()); - } - - - /** - * - * @param alleles list of alleles - */ - public void setAlleles(List alleles) { - this.alleles = new ArrayList(alleles); - validate(); - } - - public void setPhase(boolean isPhased) { - super.isPhased = isPhased; - } - - // --------------------------------------------------------------------------------------------------------- - // - // InferredGeneticContext mutation operators - // - // --------------------------------------------------------------------------------------------------------- - public void setName(String name) { commonInfo.setName(name); } - public void addFilter(String filter) { commonInfo.addFilter(filter); } - public void addFilters(Collection filters) { commonInfo.addFilters(filters); } - public void clearFilters() { commonInfo.clearFilters(); } - public void setFilters(Collection filters) { commonInfo.setFilters(filters); } - public void setAttributes(Map map) { commonInfo.setAttributes(map); } - public void clearAttributes() { commonInfo.clearAttributes(); } - public void putAttribute(String key, Object value) { commonInfo.putAttribute(key, value); } - public void removeAttribute(String key) { commonInfo.removeAttribute(key); } - public void putAttributes(Map map) { commonInfo.putAttributes(map); } - public void setNegLog10PError(double negLog10PError) { commonInfo.setNegLog10PError(negLog10PError); } - public void putAttribute(String key, Object value, boolean allowOverwrites) { commonInfo.putAttribute(key, value, allowOverwrites); } - -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index ac28928ff..996628b23 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -330,35 +330,36 @@ public class VariantContextUtils { return pruneVariantContext(vc, null); } - public static VariantContext pruneVariantContext(final VariantContext vc, final Collection keysToPreserve ) { - final MutableVariantContext mvc = new MutableVariantContext(vc); - - if ( keysToPreserve == null || keysToPreserve.size() == 0 ) - mvc.clearAttributes(); - else { - final Map d = mvc.getAttributes(); - mvc.clearAttributes(); - for ( String key : keysToPreserve ) - if ( d.containsKey(key) ) - mvc.putAttribute(key, d.get(key)); + private final static Map subsetAttributes(final InferredGeneticContext igc, final Collection keysToPreserve) { + Map attributes = new HashMap(keysToPreserve.size()); + for ( final String key : keysToPreserve ) { + if ( igc.hasAttribute(key) ) + attributes.put(key, igc.getAttribute(key)); } + return attributes; + } + + public static VariantContext pruneVariantContext(final VariantContext vc, Collection keysToPreserve ) { + if ( keysToPreserve == null ) keysToPreserve = Collections.emptyList(); + + // VC info + final Map attributes = subsetAttributes(vc.commonInfo, keysToPreserve); // this must be done as the ID is stored in the attributes field - if ( vc.hasID() ) mvc.setID(vc.getID()); + // todo -- remove me when ID becomes a first class field in VC + if ( vc.hasID() ) attributes.put(VariantContext.ID_KEY, vc.getID()); - Collection gs = mvc.getGenotypes().values(); - mvc.clearGenotypes(); - for ( Genotype g : gs ) { - MutableGenotype mg = new MutableGenotype(g); - mg.clearAttributes(); - if ( keysToPreserve != null ) - for ( String key : keysToPreserve ) - if ( g.hasAttribute(key) ) - mg.putAttribute(key, g.getAttribute(key)); - mvc.addGenotype(mg); + // Genotypes + final GenotypeMap genotypes = GenotypeMap.create(vc.getNSamples()); + for ( final Genotype g : vc.getGenotypes().values() ) { + Map genotypeAttributes = subsetAttributes(g.commonInfo, keysToPreserve); + genotypes.put(g.getSampleName(), + new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.getFilters(), + genotypeAttributes, g.isPhased())); } - return mvc; + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), + vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.getFilters(), attributes); } public enum GenotypeMergeType { @@ -391,75 +392,6 @@ public class VariantContextUtils { KEEP_IF_ALL_UNFILTERED } - /** - * Performs a master merge on the VCs. Here there is a master input [contains all of the information] and many - * VCs containing partial, extra genotype information which should be added to the master. For example, - * we scatter out the phasing algorithm over some samples in the master, producing a minimal VCF with phasing - * information per genotype. The master merge will add the PQ information from each genotype record, where - * appropriate, to the master VC. - * - * @param unsortedVCs collection of VCs - * @param masterName name of master VC - * @return master-merged VC - */ - public static VariantContext masterMerge(Collection unsortedVCs, String masterName) { - VariantContext master = findMaster(unsortedVCs, masterName); - GenotypeMap genotypes = master.getGenotypes(); - for (Genotype g : genotypes.values()) { - genotypes.put(g.getSampleName(), new MutableGenotype(g)); - } - - Map masterAttributes = new HashMap(master.getAttributes()); - - for (VariantContext vc : unsortedVCs) { - if (!vc.getSource().equals(masterName)) { - for (Genotype g : vc.getGenotypes().values()) { - MutableGenotype masterG = (MutableGenotype) genotypes.get(g.getSampleName()); - for (Map.Entry attr : g.getAttributes().entrySet()) { - if (!masterG.hasAttribute(attr.getKey())) { - //System.out.printf("Adding GT attribute %s to masterG %s, new %s%n", attr, masterG, g); - masterG.putAttribute(attr.getKey(), attr.getValue()); - } - } - - if (masterG.isPhased() != g.isPhased()) { - if (masterG.sameGenotype(g)) { - // System.out.printf("Updating phasing %s to masterG %s, new %s%n", g.isPhased(), masterG, g); - masterG.setAlleles(g.getAlleles()); - masterG.setPhase(g.isPhased()); - } - //else System.out.println("WARNING: Not updating phase, since genotypes differ between master file and auxiliary info file!"); - } - -// if ( MathUtils.compareDoubles(masterG.getNegLog10PError(), g.getNegLog10PError()) != 0 ) { -// System.out.printf("Updating GQ %s to masterG %s, new %s%n", g.getNegLog10PError(), masterG, g); -// masterG.setNegLog10PError(g.getNegLog10PError()); -// } - - } - - for (Map.Entry attr : vc.getAttributes().entrySet()) { - if (!masterAttributes.containsKey(attr.getKey())) { - //System.out.printf("Adding VC attribute %s to master %s, new %s%n", attr, master, vc); - masterAttributes.put(attr.getKey(), attr.getValue()); - } - } - } - } - - return new VariantContext(master.getSource(), master.getChr(), master.getStart(), master.getEnd(), master.getAlleles(), genotypes, master.getNegLog10PError(), master.getFilters(), masterAttributes); - } - - private static VariantContext findMaster(Collection unsortedVCs, String masterName) { - for (VariantContext vc : unsortedVCs) { - if (vc.getSource().equals(masterName)) { - return vc; - } - } - - throw new ReviewedStingException(String.format("Couldn't find master VCF %s at %s", masterName, unsortedVCs.iterator().next())); - } - /** * Merges VariantContexts into a single hybrid. Takes genotypes for common samples in priority order, if provided. * If uniqifySamples is true, the priority order is ignored and names are created by concatenating the VC name with @@ -959,9 +891,8 @@ public class VariantContextUtils { Genotype newG = g; if ( uniqifySamples || alleleMapping.needsRemapping() ) { - MutableGenotype mutG = new MutableGenotype(name, g); - if ( alleleMapping.needsRemapping() ) mutG.setAlleles(alleleMapping.remap(g.getAlleles())); - newG = mutG; + final List alleles = alleleMapping.needsRemapping() ? alleleMapping.remap(g.getAlleles()) : g.getAlleles(); + newG = new Genotype(name, alleles, g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased()); } mergedGenotypes.put(name, newG); From 077397cb4b214c0a9d4a8a9db5fb619b0fd62319 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 14:19:06 -0500 Subject: [PATCH 012/113] Deleted MutableVariantContext -- All methods that used this capable now use VariantContext directly instead --- .../validation/GenotypeAndValidateWalker.java | 5 +- .../variantcontext/MutableVariantContext.java | 212 ------------------ .../utils/variantcontext/VariantContext.java | 10 + .../VariantContextUnitTest.java | 20 +- .../VariantContextUtilsUnitTest.java | 8 +- 5 files changed, 22 insertions(+), 233 deletions(-) delete mode 100755 public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableVariantContext.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java index e64d00bf5..8f9f3f1af 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java @@ -39,7 +39,6 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; import org.broadinstitute.sting.utils.codecs.vcf.VCFUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; -import org.broadinstitute.sting.utils.variantcontext.MutableVariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -466,9 +465,7 @@ public class GenotypeAndValidateWalker extends RodWalker alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); - } - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes) { - super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); - } - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles) { - super(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { - super(source, contig, start, stop, alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - public MutableVariantContext(VariantContext parent) { - super(parent.getSource(), parent.contig, parent.start, parent.stop, parent.getAlleles(), parent.getGenotypes(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.getReferenceBaseForIndel()); - } - - /** - * Sets the alleles segregating in this context to the collect of alleles. Each of which must be unique according - * to equals() in Allele. Validate() should be called when you are done modifying the context. - * - * @param alleles - */ - public void setAlleles(Collection alleles) { - this.alleles.clear(); - for ( Allele a : alleles ) - addAllele(a); - } - - /** - * Adds allele to the segregating allele list in this context to the collection of alleles. The new - * allele must be be unique according to equals() in Allele. - * Validate() should be called when you are done modifying the context. - * - * @param allele - */ - public void addAllele(Allele allele) { - final boolean allowDuplicates = false; // used to be a parameter - - type = null; - - for ( Allele a : alleles ) { - if ( a.basesMatch(allele) && ! allowDuplicates ) - throw new IllegalArgumentException("Duplicate allele added to VariantContext" + this); - } - - // we are a novel allele - alleles.add(allele); - } - - public void clearGenotypes() { - genotypes = GenotypeMap.create(); - } - - /** - * Adds this single genotype to the context, not allowing duplicate genotypes to be added - * @param genotype - */ - public void addGenotypes(Genotype genotype) { - putGenotype(genotype.getSampleName(), genotype, false); - } - - /** - * Adds these genotypes to the context, not allowing duplicate genotypes to be added - * @param genotypes - */ - public void addGenotypes(Collection genotypes) { - for ( Genotype g : genotypes ) { - addGenotype(g); - } - } - - /** - * Adds these genotype to the context, not allowing duplicate genotypes to be added. - * @param genotypes - */ - public void addGenotypes(Map genotypes) { - for ( Map.Entry elt : genotypes.entrySet() ) { - addGenotype(elt.getValue()); - } - } - - /** - * Adds these genotypes to the context. - * - * @param genotypes - */ - public void putGenotypes(Map genotypes) { - for ( Map.Entry g : genotypes.entrySet() ) - putGenotype(g.getKey(), g.getValue()); - } - - /** - * Adds these genotypes to the context. - * - * @param genotypes - */ - public void putGenotypes(Collection genotypes) { - for ( Genotype g : genotypes ) - putGenotype(g); - } - - /** - * Adds this genotype to the context, throwing an error if it's already bound. - * - * @param genotype - */ - public void addGenotype(Genotype genotype) { - addGenotype(genotype.getSampleName(), genotype); - } - - /** - * Adds this genotype to the context, throwing an error if it's already bound. - * - * @param genotype - */ - public void addGenotype(String sampleName, Genotype genotype) { - putGenotype(sampleName, genotype, false); - } - - /** - * Adds this genotype to the context. - * - * @param genotype - */ - public void putGenotype(Genotype genotype) { - putGenotype(genotype.getSampleName(), genotype); - } - - /** - * Adds this genotype to the context. - * - * @param genotype - */ - public void putGenotype(String sampleName, Genotype genotype) { - putGenotype(sampleName, genotype, true); - } - - private void putGenotype(String sampleName, Genotype genotype, boolean allowOverwrites) { - if ( hasGenotype(sampleName) && ! allowOverwrites ) - throw new IllegalStateException("Attempting to overwrite sample->genotype binding: " + sampleName + " this=" + this); - - if ( ! sampleName.equals(genotype.getSampleName()) ) - throw new IllegalStateException("Sample name doesn't equal genotype.getSample(): " + sampleName + " genotype=" + genotype); - - this.genotypes.put(sampleName, genotype); - } - - /** - * Removes the binding from sampleName to genotype. If this doesn't exist, throws an IllegalArgumentException - * @param sampleName - */ - public void removeGenotype(String sampleName) { - if ( ! this.genotypes.containsKey(sampleName) ) - throw new IllegalArgumentException("Sample name isn't contained in genotypes " + sampleName + " genotypes =" + genotypes); - - this.genotypes.remove(sampleName); - } - - /** - * Removes genotype from the context. If this doesn't exist, throws an IllegalArgumentException - * @param genotype - */ - public void removeGenotype(Genotype genotype) { - removeGenotype(genotype.getSampleName()); - } - - // todo -- add replace genotype routine - - // --------------------------------------------------------------------------------------------------------- - // - // InferredGeneticContext mutation operators - // - // --------------------------------------------------------------------------------------------------------- - - public void setSource(String source) { commonInfo.setName(source); } - public void addFilter(String filter) { commonInfo.addFilter(filter); } - public void addFilters(Collection filters) { commonInfo.addFilters(filters); } - public void clearFilters() { commonInfo.clearFilters(); } - public void setFilters(Collection filters) { commonInfo.setFilters(filters); } - public void setAttributes(Map map) { commonInfo.setAttributes(map); } - public void clearAttributes() { commonInfo.clearAttributes(); } - public void putAttribute(String key, Object value) { commonInfo.putAttribute(key, value); } - public void removeAttribute(String key) { commonInfo.removeAttribute(key); } - public void putAttributes(Map map) { commonInfo.putAttributes(map); } - public void setNegLog10PError(double negLog10PError) { commonInfo.setNegLog10PError(negLog10PError); } - public void putAttribute(String key, Object value, boolean allowOverwrites) { commonInfo.putAttribute(key, value, allowOverwrites); } - public void setID(String id) { putAttribute(ID_KEY, id, true); } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 4682aad27..1766dc2bf 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -399,6 +399,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true); } + public static VariantContext modifyAttribute(VariantContext vc, final String key, final Object value) { + Map attributes = new HashMap(vc.getAttributes()); + attributes.put(key, value); + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true); + } + public static VariantContext modifyReferencePadding(VariantContext vc, Byte b) { return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true); } @@ -407,6 +413,10 @@ public class VariantContext implements Feature { // to enable tribble intergrati return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true); } + public static VariantContext modifyID(final VariantContext vc, final String id) { + return modifyAttribute(vc, ID_KEY, id); + } + // --------------------------------------------------------------------------------------------------------- // // Selectors diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index a4d78b637..092bd362e 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -11,10 +11,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.Assert; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; public class VariantContextUnitTest extends BaseTest { @@ -402,29 +399,26 @@ public class VariantContextUnitTest extends BaseTest { List alleles = Arrays.asList(Aref, T, del); Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - MutableVariantContext vc = new MutableVariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2)); + + VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2)); Assert.assertTrue(vc.isNotFiltered()); Assert.assertFalse(vc.isFiltered()); Assert.assertEquals(0, vc.getFilters().size()); - vc.addFilter("BAD_SNP_BAD!"); + Set filters = new HashSet(Arrays.asList("BAD_SNP_BAD!")); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); Assert.assertEquals(1, vc.getFilters().size()); - vc.addFilters(Arrays.asList("REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); + filters = new HashSet(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); Assert.assertEquals(3, vc.getFilters().size()); - - vc.clearFilters(); - - Assert.assertTrue(vc.isNotFiltered()); - Assert.assertFalse(vc.isFiltered()); - Assert.assertEquals(0, vc.getFilters().size()); } @Test diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index bfda19b6b..48ddb7efc 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -245,13 +245,13 @@ public class VariantContextUtilsUnitTest extends BaseTest { @Test(dataProvider = "simplemergersiddata") public void testRSIDMerge(SimpleMergeRSIDTest cfg) { - final VariantContext snpVC1 = makeVC("snpvc1", Arrays.asList(Aref, T)); + VariantContext snpVC1 = makeVC("snpvc1", Arrays.asList(Aref, T)); final List inputs = new ArrayList(); for ( final String id : cfg.inputs ) { - MutableVariantContext vc = new MutableVariantContext(snpVC1); - if ( ! id.equals(".") ) vc.setID(id); - inputs.add(vc); + if ( id.equals(".") ) + snpVC1 = VariantContext.modifyID(snpVC1, id); + inputs.add(snpVC1); } final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, From 9b5c79b49de7976fb167fb2c74985e2a3dcd36be Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 14:28:52 -0500 Subject: [PATCH 013/113] Renamed InferredGeneticContext to CommonInfo -- I have no idea why I named this InferredGeneticContext, a totally meaningless term -- Renamed to CommonInfo. -- Made package protected, as no one should use this outside of VariantContext and Genotype -- UGEngine was using IGC constant, but it's now using the public one in VariantContext. --- .../genotyper/UnifiedGenotyperEngine.java | 2 +- ...redGeneticContext.java => CommonInfo.java} | 4 +-- .../sting/utils/variantcontext/Genotype.java | 6 ++--- .../utils/variantcontext/VariantContext.java | 10 +++---- .../variantcontext/VariantContextUtils.java | 6 ++--- .../VariantContextUnitTest.java | 26 +++++++++---------- 6 files changed, 27 insertions(+), 27 deletions(-) rename public/java/src/org/broadinstitute/sting/utils/variantcontext/{InferredGeneticContext.java => CommonInfo.java} (98%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index bc9a5f65b..10bd7c8ae 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -229,7 +229,7 @@ public class UnifiedGenotyperEngine { VariantContext vcInput = UnifiedGenotyperEngine.getVCFromAllelesRod(tracker, ref, rawContext.getLocation(), false, logger, UAC.alleles); if ( vcInput == null ) return null; - vc = new VariantContext("UG_call", vcInput.getChr(), vcInput.getStart(), vcInput.getEnd(), vcInput.getAlleles(), InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, ref.getBase()); + vc = new VariantContext("UG_call", vcInput.getChr(), vcInput.getStart(), vcInput.getEnd(), vcInput.getAlleles(), VariantContext.NO_NEG_LOG_10PERROR, null, null, ref.getBase()); } else { // deal with bad/non-standard reference bases diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java similarity index 98% rename from public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java rename to public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java index e7d9b3338..57edbbfcc 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java @@ -11,7 +11,7 @@ import java.util.*; * * @author depristo */ -public final class InferredGeneticContext { +final class CommonInfo { public static final double NO_NEG_LOG_10PERROR = -1.0; private static Set NO_FILTERS = Collections.unmodifiableSet(new HashSet()); @@ -22,7 +22,7 @@ public final class InferredGeneticContext { private Set filters = NO_FILTERS; private Map attributes = NO_ATTRIBUTES; - public InferredGeneticContext(String name, double negLog10PError, Set filters, Map attributes) { + public CommonInfo(String name, double negLog10PError, Set filters, Map attributes) { this.name = name; setNegLog10PError(negLog10PError); if ( filters != null && ! filters.isEmpty() ) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java index eabd2dbad..28f2d85fc 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -17,8 +17,8 @@ public class Genotype { public final static String PHASED_ALLELE_SEPARATOR = "|"; public final static String UNPHASED_ALLELE_SEPARATOR = "/"; - protected InferredGeneticContext commonInfo; - public final static double NO_NEG_LOG_10PERROR = InferredGeneticContext.NO_NEG_LOG_10PERROR; + protected CommonInfo commonInfo; + public final static double NO_NEG_LOG_10PERROR = CommonInfo.NO_NEG_LOG_10PERROR; protected List alleles = null; // new ArrayList(); protected Type type = null; @@ -32,7 +32,7 @@ public class Genotype { public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { if ( alleles != null ) this.alleles = Collections.unmodifiableList(alleles); - commonInfo = new InferredGeneticContext(sampleName, negLog10PError, filters, attributes); + commonInfo = new CommonInfo(sampleName, negLog10PError, filters, attributes); if ( log10Likelihoods != null ) commonInfo.putAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); filtersWereAppliedToContext = filters != null; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 1766dc2bf..47b792e0b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -162,8 +162,8 @@ import java.util.*; * @author depristo */ public class VariantContext implements Feature { // to enable tribble intergration - protected InferredGeneticContext commonInfo = null; - public final static double NO_NEG_LOG_10PERROR = InferredGeneticContext.NO_NEG_LOG_10PERROR; + protected CommonInfo commonInfo = null; + public final static double NO_NEG_LOG_10PERROR = CommonInfo.NO_NEG_LOG_10PERROR; public final static String UNPARSED_GENOTYPE_MAP_KEY = "_UNPARSED_GENOTYPE_MAP_"; public final static String UNPARSED_GENOTYPE_PARSER_KEY = "_UNPARSED_GENOTYPE_PARSER_"; public final static String ID_KEY = "ID"; @@ -293,7 +293,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param alleles alleles */ public VariantContext(String source, String contig, long start, long stop, Collection alleles) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, null, false); + this(source, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null, null, false); } /** @@ -307,7 +307,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param genotypes genotypes */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { - this(source, contig, start, stop, alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + this(source, contig, start, stop, alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); } /** @@ -350,7 +350,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati attributes.remove(UNPARSED_GENOTYPE_PARSER_KEY); } - this.commonInfo = new InferredGeneticContext(source, negLog10PError, filters, attributes); + this.commonInfo = new CommonInfo(source, negLog10PError, filters, attributes); filtersWereAppliedToContext = filters != null; REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 996628b23..37d8acbe2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -81,7 +81,7 @@ public class VariantContextUtils { * @return VariantContext object */ public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles) { - return new VariantContext (name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, VariantContext.NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + return new VariantContext (name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, VariantContext.NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null); } /** @@ -93,7 +93,7 @@ public class VariantContextUtils { * @return VariantContext object */ public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); } /** @@ -330,7 +330,7 @@ public class VariantContextUtils { return pruneVariantContext(vc, null); } - private final static Map subsetAttributes(final InferredGeneticContext igc, final Collection keysToPreserve) { + private final static Map subsetAttributes(final CommonInfo igc, final Collection keysToPreserve) { Map attributes = new HashMap(keysToPreserve.size()); for ( final String key : keysToPreserve ) { if ( igc.hasAttribute(key) ) diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 092bd362e..77980267c 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -90,45 +90,45 @@ public class VariantContextUnitTest extends BaseTest { // test INDELs alleles = Arrays.asList(Aref, ATC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(Tref, TA, TC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, AC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, Allele.create("ATCTC")); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); // test MIXED alleles = Arrays.asList(TAref, T, TC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(TAref, T, AC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(ACref, ATC, AT); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(Aref, T, symbolic); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); // test SYMBOLIC alleles = Arrays.asList(Tref, symbolic); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.SYMBOLIC); } @@ -199,7 +199,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingDeletionVariantContext() { List alleles = Arrays.asList(ATCref, del); - VariantContext vc = new VariantContext("test", delLoc, delLocStart, delLocStop, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContext("test", delLoc, delLocStart, delLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getChr(), delLoc); Assert.assertEquals(vc.getStart(), delLocStart); @@ -226,7 +226,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingInsertionVariantContext() { List alleles = Arrays.asList(delRef, ATC); - VariantContext vc = new VariantContext("test", insLoc, insLocStart, insLocStop, alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContext("test", insLoc, insLocStart, insLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getChr(), insLoc); Assert.assertEquals(vc.getStart(), insLocStart); @@ -514,7 +514,7 @@ public class VariantContextUnitTest extends BaseTest { @Test(dataProvider = "getAlleles") public void testMergeAlleles(GetAllelesTest cfg) { final List altAlleles = cfg.alleles.subList(1, cfg.alleles.size()); - final VariantContext vc = new VariantContext("test", snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + final VariantContext vc = new VariantContext("test", snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getAlleles(), cfg.alleles, "VC alleles not the same as input alleles"); Assert.assertEquals(vc.getNAlleles(), cfg.alleles.size(), "VC getNAlleles not the same as input alleles size"); From 7b2a7cfbe763cfa8aeca37205dfe9b1ffedee094 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 14 Nov 2011 14:31:27 -0500 Subject: [PATCH 014/113] Transfer headers from the resource VCF when possible when using expressions. While there, VA was modified so that it didn't assume that the ID field was present in the VC's info map in preparation for Mark's upcoming changes. --- .../walkers/annotator/VariantAnnotator.java | 25 +++++++++++++++++-- .../annotator/VariantAnnotatorEngine.java | 23 +++++++++++------ .../VariantAnnotatorIntegrationTest.java | 2 +- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java index ea11391d9..20e72dd57 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java @@ -222,8 +222,29 @@ public class VariantAnnotator extends RodWalker implements Ann if ( isUniqueHeaderLine(line, hInfo) ) hInfo.add(line); } - for ( String expression : expressionsToUse ) - hInfo.add(new VCFInfoHeaderLine(expression, VCFHeaderLineCount.UNBOUNDED, VCFHeaderLineType.String, "Value transferred from another external VCF resource")); + // for the expressions, pull the info header line from the header of the resource rod + for ( VariantAnnotatorEngine.VAExpression expression : engine.getRequestedExpressions() ) { + // special case the ID field + if ( expression.fieldName.equals("ID") ) { + hInfo.add(new VCFInfoHeaderLine(expression.fullName, 1, VCFHeaderLineType.String, "ID field transferred from external VCF resource")); + continue; + } + VCFInfoHeaderLine targetHeaderLine = null; + for ( VCFHeaderLine line : VCFUtils.getHeaderFields(getToolkit(), Arrays.asList(expression.binding.getName())) ) { + if ( line instanceof VCFInfoHeaderLine ) { + VCFInfoHeaderLine infoline = (VCFInfoHeaderLine)line; + if ( infoline.getName().equals(expression.fieldName) ) { + targetHeaderLine = infoline; + break; + } + } + } + + if ( targetHeaderLine != null ) + hInfo.add(new VCFInfoHeaderLine(expression.fullName, targetHeaderLine.getCountType(), targetHeaderLine.getType(), targetHeaderLine.getDescription())); + else + hInfo.add(new VCFInfoHeaderLine(expression.fullName, VCFHeaderLineCount.UNBOUNDED, VCFHeaderLineType.String, "Value transferred from another external VCF resource")); + } engine.invokeAnnotationInitializationMethods(hInfo); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index e4bc0d5d5..20f28007a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -49,20 +49,20 @@ public class VariantAnnotatorEngine { private AnnotatorCompatibleWalker walker; private GenomeAnalysisEngine toolkit; - private static class VAExpression { + protected static class VAExpression { public String fullName, fieldName; public RodBinding binding; - public VAExpression(String fullEpression, List> bindings) { - int indexOfDot = fullEpression.lastIndexOf("."); + public VAExpression(String fullExpression, List> bindings) { + int indexOfDot = fullExpression.lastIndexOf("."); if ( indexOfDot == -1 ) - throw new UserException.BadArgumentValue(fullEpression, "it should be in rodname.value format"); + throw new UserException.BadArgumentValue(fullExpression, "it should be in rodname.value format"); - fullName = fullEpression; - fieldName = fullEpression.substring(indexOfDot+1); + fullName = fullExpression; + fieldName = fullExpression.substring(indexOfDot+1); - String bindingName = fullEpression.substring(0, indexOfDot); + String bindingName = fullExpression.substring(0, indexOfDot); for ( RodBinding rod : bindings ) { if ( rod.getName().equals(bindingName) ) { binding = rod; @@ -97,6 +97,8 @@ public class VariantAnnotatorEngine { requestedExpressions.add(new VAExpression(expression, walker.getResourceRodBindings())); } + protected List getRequestedExpressions() { return requestedExpressions; } + private void initializeAnnotations(List annotationGroupsToUse, List annotationsToUse, List annotationsToExclude) { AnnotationInterfaceManager.validateAnnotations(annotationGroupsToUse, annotationsToUse); requestedInfoAnnotations = AnnotationInterfaceManager.createInfoFieldAnnotations(annotationGroupsToUse, annotationsToUse); @@ -211,8 +213,13 @@ public class VariantAnnotatorEngine { continue; VariantContext vc = VCs.iterator().next(); - if ( vc.hasAttribute(expression.fieldName) ) + // special-case the ID field + if ( expression.fieldName.equals("ID") ) { + if ( vc.hasID() ) + infoAnnotations.put(expression.fullName, vc.getID()); + } else if ( vc.hasAttribute(expression.fieldName) ) { infoAnnotations.put(expression.fullName, vc.getAttribute(expression.fieldName)); + } } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index 189f643d4..bde4c4a8f 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -128,7 +128,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testUsingExpressionWithID() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.ID -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("4a6f0675242f685e9072c1da5ad9e715")); + Arrays.asList("1b4921085b26cbfe07d53b7c947de1e5")); executeTest("using expression with ID", spec); } From 4dc9dbe890480eea992d89f740fe20f06bd2a086 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 14 Nov 2011 14:42:12 -0500 Subject: [PATCH 015/113] One quick fix to previous commit --- .../sting/gatk/walkers/annotator/VariantAnnotator.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java index 20e72dd57..c9ea7a3b5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotator.java @@ -240,10 +240,14 @@ public class VariantAnnotator extends RodWalker implements Ann } } - if ( targetHeaderLine != null ) - hInfo.add(new VCFInfoHeaderLine(expression.fullName, targetHeaderLine.getCountType(), targetHeaderLine.getType(), targetHeaderLine.getDescription())); - else + if ( targetHeaderLine != null ) { + if ( targetHeaderLine.getCountType() == VCFHeaderLineCount.INTEGER ) + hInfo.add(new VCFInfoHeaderLine(expression.fullName, targetHeaderLine.getCount(), targetHeaderLine.getType(), targetHeaderLine.getDescription())); + else + hInfo.add(new VCFInfoHeaderLine(expression.fullName, targetHeaderLine.getCountType(), targetHeaderLine.getType(), targetHeaderLine.getDescription())); + } else { hInfo.add(new VCFInfoHeaderLine(expression.fullName, VCFHeaderLineCount.UNBOUNDED, VCFHeaderLineType.String, "Value transferred from another external VCF resource")); + } } engine.invokeAnnotationInitializationMethods(hInfo); From 1fbdcb4f43a51ffea081241e8d06be63a9a23a50 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 15:32:03 -0500 Subject: [PATCH 017/113] GenotypeMap -> GenotypeCollection --- .../gatk/refdata/VariantContextAdaptors.java | 4 +- .../gatk/walkers/annotator/AlleleBalance.java | 14 +- .../gatk/walkers/annotator/HardyWeinberg.java | 8 +- .../walkers/annotator/InbreedingCoeff.java | 4 +- .../gatk/walkers/annotator/QualByDepth.java | 4 +- .../gatk/walkers/annotator/RankSumTest.java | 4 +- .../annotator/VariantAnnotatorEngine.java | 6 +- .../beagle/BeagleOutputToVCFWalker.java | 4 +- .../filters/VariantFiltrationWalker.java | 6 +- .../AlleleFrequencyCalculationModel.java | 7 +- .../genotyper/ExactAFCalculationModel.java | 10 +- .../genotyper/GridSearchAFEstimation.java | 8 +- .../walkers/genotyper/UGCallVariants.java | 4 +- .../genotyper/UnifiedGenotyperEngine.java | 6 +- .../indels/SomaticIndelDetectorWalker.java | 6 +- .../walkers/phasing/PhaseByTransmission.java | 10 +- .../phasing/ReadBackedPhasingWalker.java | 6 +- .../evaluators/GenotypePhasingEvaluator.java | 6 +- .../variantutils/LeftAlignVariants.java | 4 +- .../walkers/variantutils/SelectVariants.java | 4 +- .../walkers/variantutils/VariantsToVCF.java | 2 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 5 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 7 +- .../sting/utils/codecs/vcf/VCFCodec.java | 7 +- .../sting/utils/codecs/vcf/VCFParser.java | 6 +- .../broadinstitute/sting/utils/gcf/GCF.java | 8 +- .../variantcontext/GenotypeCollection.java | 325 ++++++++++++++++++ .../utils/variantcontext/GenotypeMap.java | 191 ---------- .../utils/variantcontext/VariantContext.java | 28 +- .../variantcontext/VariantContextUtils.java | 20 +- .../utils/genotype/vcf/VCFWriterUnitTest.java | 4 +- .../VariantContextUtilsUnitTest.java | 2 +- 32 files changed, 424 insertions(+), 306 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java delete mode 100644 public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index c4ba5d6d1..523da7492 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -194,7 +194,7 @@ public class VariantContextAdaptors { return null; // we weren't given enough reference context to create the VariantContext Byte refBaseForIndel = new Byte(ref.getBases()[index]); - GenotypeMap genotypes = null; + GenotypeCollection genotypes = null; VariantContext vc = new VariantContext(name, dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0), alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attributes, refBaseForIndel); return vc; } else @@ -324,7 +324,7 @@ public class VariantContextAdaptors { String[] samples = hapmap.getSampleIDs(); String[] genotypeStrings = hapmap.getGenotypes(); - GenotypeMap genotypes = GenotypeMap.create(samples.length); + GenotypeCollection genotypes = GenotypeCollection.create(samples.length); for ( int i = 0; i < samples.length; i++ ) { // ignore bad genotypes if ( genotypeStrings[i].contains("N") ) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java index 297490172..c345c8741 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java @@ -35,7 +35,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -55,18 +55,18 @@ public class AlleleBalance extends InfoFieldAnnotation { if ( !vc.isBiallelic() ) return null; - final GenotypeMap genotypes = vc.getGenotypes(); + final GenotypeCollection genotypes = vc.getGenotypes(); if ( !vc.hasGenotypes() ) return null; double ratio = 0.0; double totalWeights = 0.0; - for ( Map.Entry genotype : genotypes.entrySet() ) { + for ( Genotype genotype : genotypes ) { // we care only about het calls - if ( !genotype.getValue().isHet() ) + if ( !genotype.isHet() ) continue; - AlignmentContext context = stratifiedContexts.get(genotype.getKey()); + AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) continue; @@ -85,8 +85,8 @@ public class AlleleBalance extends InfoFieldAnnotation { continue; // weight the allele balance by genotype quality so that e.g. mis-called homs don't affect the ratio too much - ratio += genotype.getValue().getNegLog10PError() * ((double)refCount / (double)(refCount + altCount)); - totalWeights += genotype.getValue().getNegLog10PError(); + ratio += genotype.getNegLog10PError() * ((double)refCount / (double)(refCount + altCount)); + totalWeights += genotype.getNegLog10PError(); } else if ( vc.isIndel() && context.hasExtendedEventPileup() ) { final ReadBackedExtendedEventPileup indelPileup = context.getExtendedEventPileup(); if ( indelPileup == null ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java index 6352fcf2a..164c77d1c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java @@ -11,7 +11,7 @@ import org.broadinstitute.sting.utils.QualityUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -31,16 +31,14 @@ public class HardyWeinberg extends InfoFieldAnnotation implements WorkInProgress public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - final GenotypeMap genotypes = vc.getGenotypes(); + final GenotypeCollection genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() < MIN_SAMPLES ) return null; int refCount = 0; int hetCount = 0; int homCount = 0; - for ( Map.Entry genotype : genotypes.entrySet() ) { - Genotype g = genotype.getValue(); - + for ( final Genotype g : genotypes ) { if ( g.isNoCall() ) continue; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java index 9935eced9..a21d7106c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java @@ -10,7 +10,7 @@ import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -33,7 +33,7 @@ public class InbreedingCoeff extends InfoFieldAnnotation implements StandardAnno public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - final GenotypeMap genotypes = vc.getGenotypes(); + final GenotypeCollection genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() < MIN_SAMPLES ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java index e34ef5d45..dae041155 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java @@ -9,7 +9,7 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.StandardAnnota import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -29,7 +29,7 @@ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotati if ( stratifiedContexts.size() == 0 ) return null; - final GenotypeMap genotypes = vc.getGenotypes(); + final GenotypeCollection genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java index f75997d57..8182747f4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java @@ -13,7 +13,7 @@ import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.ArrayList; @@ -33,7 +33,7 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar if ( stratifiedContexts.size() == 0 ) return null; - final GenotypeMap genotypes = vc.getGenotypes(); + final GenotypeCollection genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index 87b1366cf..9fb25d605 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -34,7 +34,7 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.*; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; @@ -217,11 +217,11 @@ public class VariantAnnotatorEngine { } } - private GenotypeMap annotateGenotypes(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { + private GenotypeCollection annotateGenotypes(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { if ( requestedGenotypeAnnotations.size() == 0 ) return vc.getGenotypes(); - GenotypeMap genotypes = GenotypeMap.create(vc.getNSamples()); + GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); for ( Map.Entry g : vc.getGenotypes().entrySet() ) { Genotype genotype = g.getValue(); AlignmentContext context = stratifiedContexts.get(g.getKey()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 352b1790e..89dd114cc 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -187,7 +187,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { byte refByte = ref.getBase(); // make new Genotypes based on Beagle results - GenotypeMap genotypes = GenotypeMap.create(vc_input.getGenotypes().size()); + GenotypeCollection genotypes = GenotypeCollection.create(vc_input.getGenotypes().size()); // for each genotype, create a new object with Beagle information on it @@ -196,7 +196,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { Double alleleFrequencyH = 0.0; int beagleVarCounts = 0; - GenotypeMap hapmapGenotypes = null; + GenotypeCollection hapmapGenotypes = null; if (vc_comp != null) { hapmapGenotypes = vc_comp.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index 9428fd7ee..c1fbc9ac6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -37,7 +37,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -283,11 +283,11 @@ public class VariantFiltrationWalker extends RodWalker { VariantContext vc = context.getVariantContext(); // make new Genotypes based on filters - GenotypeMap genotypes; + GenotypeCollection genotypes; if ( genotypeFilterExps.size() == 0 ) { genotypes = null; } else { - genotypes = GenotypeMap.create(vc.getGenotypes().size()); + genotypes = GenotypeCollection.create(vc.getGenotypes().size()); // for each genotype, check filters then create a new object for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java index 2bee98879..d0f45092b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java @@ -26,17 +26,14 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; import java.util.List; import java.util.Map; -import java.util.Set; /** @@ -86,7 +83,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * * @return calls */ - protected abstract GenotypeMap assignGenotypes(VariantContext vc, + protected abstract GenotypeCollection assignGenotypes(VariantContext vc, double[] log10AlleleFrequencyPosteriors, int AFofMaxLikelihood); } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index 0e3062cfc..bb2516f7c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -26,14 +26,12 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; @@ -269,14 +267,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { * * @return calls */ - public GenotypeMap assignGenotypes(VariantContext vc, + public GenotypeCollection assignGenotypes(VariantContext vc, double[] log10AlleleFrequencyPosteriors, int AFofMaxLikelihood) { if ( !vc.isVariant() ) throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); - GenotypeMap GLs = vc.getGenotypes(); + GenotypeCollection GLs = vc.getGenotypes(); double[][] pathMetricArray = new double[GLs.size()+1][AFofMaxLikelihood+1]; int[][] tracebackArray = new int[GLs.size()+1][AFofMaxLikelihood+1]; @@ -343,7 +341,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - GenotypeMap calls = GenotypeMap.create(); + GenotypeCollection calls = GenotypeCollection.create(); int startIdx = AFofMaxLikelihood; for (int k = sampleIdx; k > 0; k--) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java index bb31045a7..48df8dcb9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java @@ -26,15 +26,13 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; @@ -90,7 +88,7 @@ public class GridSearchAFEstimation extends AlleleFrequencyCalculationModel { * * @return calls */ - protected GenotypeMap assignGenotypes(VariantContext vc, + protected GenotypeCollection assignGenotypes(VariantContext vc, double[] log10AlleleFrequencyPosteriors, int AFofMaxLikelihood) { if ( !vc.isVariant() ) @@ -98,7 +96,7 @@ public class GridSearchAFEstimation extends AlleleFrequencyCalculationModel { Allele refAllele = vc.getReference(); Allele altAllele = vc.getAlternateAllele(0); - GenotypeMap calls = GenotypeMap.create(); + GenotypeCollection calls = GenotypeCollection.create(); // first, the potential alt calls for ( String sample : AFMatrix.getSamples() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index c54089350..81310f15a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -36,7 +36,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -129,7 +129,7 @@ public class UGCallVariants extends RodWalker { return null; VariantContext variantVC = null; - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); for ( VariantContext vc : VCs ) { if ( variantVC == null && vc.isVariant() ) variantVC = vc; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index 10bd7c8ae..72b88100b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -265,7 +265,7 @@ public class UnifiedGenotyperEngine { alleles.add(refAllele); boolean addedAltAlleles = false; - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); for ( MultiallelicGenotypeLikelihoods GL : GLs.values() ) { if ( !addedAltAlleles ) { addedAltAlleles = true; @@ -354,7 +354,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypeMap genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypeCollection genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); // print out stats if we have a writer if ( verboseWriter != null ) @@ -491,7 +491,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypeMap genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypeCollection genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); // *** note that calculating strand bias involves overwriting data structures, so we do that last HashMap attributes = new HashMap(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index ee5562ba2..7425258d4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -60,7 +60,7 @@ import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -1058,7 +1058,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { stop += event_length; } - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); for ( String sample : normalSamples ) { @@ -1148,7 +1148,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { homRefAlleles.add( alleles.get(0)); homRefAlleles.add( alleles.get(0)); - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); for ( String sample : normalSamples ) { genotypes.put(sample,new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsNormal,false)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 6b52fcf62..b35c54f94 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -293,7 +293,7 @@ public class PhaseByTransmission extends RodWalker { if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); - GenotypeMap genotypeMap = vc.getGenotypes(); + GenotypeCollection genotypeCollection = vc.getGenotypes(); for (Trio trio : trios) { Genotype mother = vc.getGenotype(trio.getMother()); @@ -306,12 +306,12 @@ public class PhaseByTransmission extends RodWalker { Genotype phasedFather = trioGenotypes.get(1); Genotype phasedChild = trioGenotypes.get(2); - genotypeMap.put(phasedMother.getSampleName(), phasedMother); - genotypeMap.put(phasedFather.getSampleName(), phasedFather); - genotypeMap.put(phasedChild.getSampleName(), phasedChild); + genotypeCollection.put(phasedMother.getSampleName(), phasedMother); + genotypeCollection.put(phasedFather.getSampleName(), phasedFather); + genotypeCollection.put(phasedChild.getSampleName(), phasedChild); } - VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypeMap); + VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypeCollection); vcfWriter.add(newvc); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 2aa96379c..132ed1582 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -352,7 +352,7 @@ public class ReadBackedPhasingWalker extends RodWalker samplePhaseStats = new TreeMap(); for (Map.Entry sampGtEntry : sampGenotypes.entrySet()) { String samp = sampGtEntry.getKey(); @@ -1123,7 +1123,7 @@ public class ReadBackedPhasingWalker extends RodWalker alleles; - private GenotypeMap genotypes; + private GenotypeCollection genotypes; private double negLog10PError; private Set filters; private Map attributes; @@ -1134,7 +1134,7 @@ public class ReadBackedPhasingWalker extends RodWalker(vc.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java index ad9ad62b3..08d62154d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java @@ -14,7 +14,7 @@ import org.broadinstitute.sting.gatk.walkers.varianteval.util.TableType; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.HashMap; @@ -92,13 +92,13 @@ public class GenotypePhasingEvaluator extends VariantEvaluator { Set allSamples = new HashSet(); - GenotypeMap compSampGenotypes = null; + GenotypeCollection compSampGenotypes = null; if (isRelevantToPhasing(comp)) { allSamples.addAll(comp.getSampleNames()); compSampGenotypes = comp.getGenotypes(); } - GenotypeMap evalSampGenotypes = null; + GenotypeCollection evalSampGenotypes = null; if (isRelevantToPhasing(eval)) { allSamples.addAll(eval.getSampleNames()); evalSampGenotypes = eval.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java index 64f54e611..f87ec5d3b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java @@ -40,7 +40,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; @@ -211,7 +211,7 @@ public class LeftAlignVariants extends RodWalker { } // create new Genotype objects - GenotypeMap newGenotypes = GenotypeMap.create(vc.getNSamples()); + GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { List newAlleles = new ArrayList(); for ( Allele allele : genotype.getValue().getAlleles() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 3c5a55134..3764a9998 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -24,10 +24,8 @@ package org.broadinstitute.sting.gatk.walkers.variantutils; -import org.apache.poi.hpsf.Variant; import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; -import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.text.XReadLines; @@ -558,7 +556,7 @@ public class SelectVariants extends RodWalker { return (compVCs == null || compVCs.isEmpty()); // check if we find it in the variant rod - GenotypeMap genotypes = vc.getGenotypes(samples); + GenotypeCollection genotypes = vc.getGenotypes(samples); for (Genotype g : genotypes.values()) { if (sampleHasVariant(g)) { // There is a variant called (or filtered with not exclude filtered option set) that is not HomRef for at least one of the samples. diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java index 7f1fc9d16..ff7fb3434 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java @@ -130,7 +130,7 @@ public class VariantsToVCF extends RodWalker { // set the appropriate sample name if necessary if ( sampleName != null && vc.hasGenotypes() && vc.hasGenotype(variants.getName()) ) { Genotype g = Genotype.modifyName(vc.getGenotype(variants.getName()), sampleName); - GenotypeMap genotypes = GenotypeMap.create(1); + GenotypeCollection genotypes = GenotypeCollection.create(1); genotypes.put(sampleName, g); vc = VariantContext.modifyGenotypes(vc, genotypes); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index c285a9f68..ad14e059b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -11,8 +11,7 @@ import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -77,7 +76,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, * @param pos position * @return a mapping of sample name to genotype object */ - public abstract GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos); + public abstract GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos); /** diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index fcfc0c6fc..302b93da7 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -5,11 +5,10 @@ import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.util.*; @@ -119,13 +118,13 @@ public class VCF3Codec extends AbstractVCFCodec { * @param pos position * @return a mapping of sample name to genotype object */ - public GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos) { + public GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - GenotypeMap genotypes = GenotypeMap.create(nParts); + GenotypeCollection genotypes = GenotypeCollection.create(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index eefb929bb..8256c9cac 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -5,11 +5,10 @@ import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.util.*; @@ -146,13 +145,13 @@ public class VCFCodec extends AbstractVCFCodec { * @param alleles the list of alleles * @return a mapping of sample name to genotype object */ - public GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos) { + public GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - GenotypeMap genotypes = GenotypeMap.create(nParts); + GenotypeCollection genotypes = GenotypeCollection.create(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java index 2887c5360..86dd5d4f7 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java @@ -1,11 +1,9 @@ package org.broadinstitute.sting.utils.codecs.vcf; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import java.util.List; -import java.util.Map; /** @@ -21,6 +19,6 @@ public interface VCFParser { * @param pos position * @return a mapping of sample name to genotype object */ - public GenotypeMap createGenotypeMap(String str, List alleles, String chr, int pos); + public GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos); } diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index 7f15b4f5e..9a900d734 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -28,7 +28,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.StandardVCFWriter; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -146,16 +146,16 @@ public class GCF { Map attributes = new HashMap(); attributes.put("INFO", info); Byte refPadByte = refPad == 0 ? null : refPad; - GenotypeMap genotypes = decodeGenotypes(header); + GenotypeCollection genotypes = decodeGenotypes(header); return new VariantContext(source, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); } - private GenotypeMap decodeGenotypes(final GCFHeader header) { + private GenotypeCollection decodeGenotypes(final GCFHeader header) { if ( genotypes.isEmpty() ) return VariantContext.NO_GENOTYPES; else { - GenotypeMap map = GenotypeMap.create(); + GenotypeCollection map = GenotypeCollection.create(); for ( int i = 0; i < genotypes.size(); i++ ) { final String sampleName = header.getSample(i); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java new file mode 100644 index 000000000..a83356647 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext; + +import java.util.*; + +/** + * + */ +public class GenotypeCollection implements List { + public final static GenotypeCollection NO_GENOTYPES = new GenotypeCollection(); + + Map sampleNameToOffset = null; + boolean cacheIsInvalid = true; + final ArrayList genotypes; + boolean immutable = false; + + // --------------------------------------------------------------------------- + // + // private constructors -- you have to use static create methods to make these classes + // + // --------------------------------------------------------------------------- + + private GenotypeCollection() { + this(10, false); + } + + private GenotypeCollection(final int n, final boolean immutable) { + this(new ArrayList(n), immutable); + } + + private GenotypeCollection(final ArrayList genotypes, final boolean immutable) { + this.genotypes = genotypes; + this.immutable = immutable; + } + + // --------------------------------------------------------------------------- + // + // public static factory methods + // + // --------------------------------------------------------------------------- + + public static final GenotypeCollection create() { + return new GenotypeCollection(); + } + + public static final GenotypeCollection create(final int nGenotypes) { + return new GenotypeCollection(nGenotypes, true); + } + + // todo -- differentiate between empty constructor and copy constructor + // todo -- create constructor (Genotype ... genotypes) + + public static final GenotypeCollection create(final ArrayList genotypes) { + return new GenotypeCollection(genotypes, true); + } + + public static final GenotypeCollection copy(final GenotypeCollection toCopy) { + return create(toCopy.genotypes); + } + +// public static final GenotypeMap create(final Collection genotypes) { +// if ( genotypes == null ) +// return null; // todo -- really should return an empty map +// else { +// GenotypeMap genotypeMap = new GenotypeMap(genotypes.size(), false); +// for ( final Genotype g : genotypes ) { +// if ( genotypeMap.containsKey(g.getSampleName() ) ) +// throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); +// genotypeMap.put(g.getSampleName(), g); +// } +// +// //return genotypeMap.immutable(); // todo enable when we have time to dive into mutability issue +// return genotypeMap; +// } +// } + + // --------------------------------------------------------------------------- + // + // Mutability methods + // + // --------------------------------------------------------------------------- + + public final GenotypeCollection mutable() { + immutable = false; + return this; + } + + public final GenotypeCollection immutable() { + immutable = true; + return this; + } + + public boolean isMutable() { + return ! immutable; + } + + public final void checkImmutability() { + if ( immutable ) + throw new IllegalAccessError("GenotypeMap is currently immutable, but a mutator method was invoked on it"); + } + + // --------------------------------------------------------------------------- + // + // caches + // + // --------------------------------------------------------------------------- + + private void invalidateCaches() { + cacheIsInvalid = true; + if ( sampleNameToOffset != null ) sampleNameToOffset.clear(); + } + + private void buildCache() { + cacheIsInvalid = false; + + if ( sampleNameToOffset == null ) + sampleNameToOffset = new HashMap(genotypes.size()); + + for ( int i = 0; i < genotypes.size(); i++ ) + sampleNameToOffset.put(genotypes.get(i).getSampleName(), i); + } + + + // --------------------------------------------------------------------------- + // + // Map methods + // + // --------------------------------------------------------------------------- + + @Override + public void clear() { + checkImmutability(); + genotypes.clear(); + } + + @Override + public int size() { + return genotypes.size(); + } + + @Override + public boolean isEmpty() { + return genotypes.isEmpty(); + } + + @Override + public boolean add(final Genotype genotype) { + checkImmutability(); + invalidateCaches(); + return genotypes.add(genotype); + } + + @Override + public void add(final int i, final Genotype genotype) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection genotypes) { + checkImmutability(); + invalidateCaches(); + return this.genotypes.addAll(genotypes); + } + + @Override + public boolean addAll(final int i, final Collection genotypes) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(final Object o) { + return this.genotypes.contains(o); + } + + @Override + public boolean containsAll(final Collection objects) { + return this.genotypes.containsAll(objects); + } + + @Override + public Genotype get(final int i) { + return genotypes.get(i); + } + + public Genotype get(final String sampleName) { + buildCache(); + Integer offset = sampleNameToOffset.get(sampleName); + if ( offset == null ) + throw new IllegalArgumentException("Sample " + sampleName + " not found in this GenotypeCollection"); + return genotypes.get(offset); + } + + + @Override + public int indexOf(final Object o) { + return genotypes.indexOf(o); + } + + @Override + public Iterator iterator() { + return genotypes.iterator(); + } + + @Override + public int lastIndexOf(final Object o) { + return genotypes.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + // todo -- must be immutable + return genotypes.listIterator(); + } + + @Override + public ListIterator listIterator(final int i) { + // todo -- must be immutable + return genotypes.listIterator(i); + } + + @Override + public Genotype remove(final int i) { + checkImmutability(); + invalidateCaches(); + return genotypes.remove(i); + } + + @Override + public boolean remove(final Object o) { + checkImmutability(); + invalidateCaches(); + return genotypes.remove(o); + } + + @Override + public boolean removeAll(final Collection objects) { + checkImmutability(); + invalidateCaches(); + return genotypes.removeAll(objects); + } + + @Override + public boolean retainAll(final Collection objects) { + checkImmutability(); + invalidateCaches(); + return genotypes.retainAll(objects); + } + + @Override + public Genotype set(final int i, final Genotype genotype) { + checkImmutability(); + invalidateCaches(); + return genotypes.set(i, genotype); + } + + @Override + public List subList(final int i, final int i1) { + return genotypes.subList(i, i1); + } + + @Override + public Object[] toArray() { + return genotypes.toArray(); + } + + @Override + public T[] toArray(final T[] ts) { + return genotypes.toArray(ts); + } + + public Iterable iterateInOrder(final Iterable sampleNamesInOrder) { + return new Iterable() { + @Override + public Iterator iterator() { + return new InOrderIterator(sampleNamesInOrder.iterator()); + } + }; + } + + private final class InOrderIterator implements Iterator { + final Iterator sampleNamesInOrder; + + private InOrderIterator(final Iterator sampleNamesInOrder) { + this.sampleNamesInOrder = sampleNamesInOrder; + } + + @Override + public boolean hasNext() { + return sampleNamesInOrder.hasNext(); + } + + @Override + public Genotype next() { + return get(sampleNamesInOrder.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java deleted file mode 100644 index cb7250bdb..000000000 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeMap.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2011, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext; - -import java.util.*; - -/** - * - */ -public class GenotypeMap implements Map { - final TreeMap genotypes; - boolean immutable = false; - public final static GenotypeMap NO_GENOTYPES = new GenotypeMap(); - - // --------------------------------------------------------------------------- - // - // private constructors -- you have to use static create methods to make these classes - // - // --------------------------------------------------------------------------- - - private GenotypeMap() { - this(false); - } - - private GenotypeMap(boolean immutable) { - this(new TreeMap(), immutable); - } - - private GenotypeMap(final TreeMap genotypes, final boolean immutable) { - this.genotypes = genotypes; - this.immutable = immutable; - } - - // --------------------------------------------------------------------------- - // - // public static factory methods - // - // --------------------------------------------------------------------------- - - public static final GenotypeMap create() { - return new GenotypeMap(); - } - - public static final GenotypeMap create(final int nGenotypes) { - return new GenotypeMap(); - } - - public static final GenotypeMap create(final GenotypeMap genotypes) { - return create(genotypes.values()); - } - - // todo -- differentiate between empty constructor and copy constructor - // todo -- create constructor (Genotype ... genotypes) - - public static final GenotypeMap create(final Map genotypes) { - return create(genotypes.values()); - } - - public static final GenotypeMap create(final Collection genotypes) { - if ( genotypes == null ) - return null; // todo -- really should return an empty map - else { - GenotypeMap genotypeMap = new GenotypeMap().mutable(); - for ( final Genotype g : genotypes ) { - if ( genotypeMap.containsKey(g.getSampleName() ) ) - throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); - genotypeMap.put(g.getSampleName(), g); - } - - //return genotypeMap.immutable(); // todo enable when we have time to dive into mutability issue - return genotypeMap; - } - } - - // --------------------------------------------------------------------------- - // - // Mutability methods - // - // --------------------------------------------------------------------------- - - public final GenotypeMap mutable() { - immutable = false; - return this; - } - - public final GenotypeMap immutable() { - immutable = true; - return this; - } - - public boolean isMutable() { - return ! immutable; - } - - public final void checkImmutability() { - if ( immutable ) - throw new IllegalAccessError("GenotypeMap is currently immutable, but a mutator method was invoked on it"); - } - - // --------------------------------------------------------------------------- - // - // Map methods - // - // --------------------------------------------------------------------------- - - @Override - public void clear() { - checkImmutability(); - genotypes.clear(); - } - - @Override - public int size() { - return genotypes.size(); - } - - @Override - public boolean isEmpty() { - return genotypes.isEmpty(); - } - - @Override - public boolean containsKey(final Object o) { - return genotypes.containsKey(o); - } - - @Override - public boolean containsValue(final Object o) { - return genotypes.containsValue(o); - } - - @Override - public Genotype get(final Object o) { - return genotypes.get(o); - } - - @Override - public Genotype put(final String s, final Genotype genotype) { - checkImmutability(); - return genotypes.put(s, genotype); - } - - @Override - public Genotype remove(final Object o) { - checkImmutability(); - return genotypes.remove(o); - } - - @Override - public void putAll(final Map map) { - checkImmutability(); - genotypes.putAll(map); - } - - @Override - public Set keySet() { - return Collections.unmodifiableSet(genotypes.keySet()); - } - - @Override - public Collection values() { - return genotypes.values(); - } - - @Override - public Set> entrySet() { - return genotypes.entrySet(); - } -} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 47b792e0b..5bd29a0ce 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -184,12 +184,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati final protected List alleles; /** A mapping from sampleName -> genotype objects for all genotypes associated with this context */ - protected GenotypeMap genotypes = null; + protected GenotypeCollection genotypes = null; /** Counts for each of the possible Genotype types in this context */ protected int[] genotypeCounts = null; - public final static GenotypeMap NO_GENOTYPES = GenotypeMap.NO_GENOTYPES; + public final static GenotypeCollection NO_GENOTYPES = GenotypeCollection.NO_GENOTYPES; // a fast cached access point to the ref / alt alleles for biallelic case private Allele REF = null; @@ -222,7 +222,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false); } @@ -239,7 +239,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeMap genotypes, double negLog10PError, Set filters, Map attributes) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes) { this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false); } @@ -279,7 +279,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { this(source, contig, start, stop, alleles, - GenotypeMap.create(genotypes), + GenotypeCollection.create(genotypes), negLog10PError, filters, attributes, null, false); } @@ -335,7 +335,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param genotypesAreUnparsed true if the genotypes have not yet been parsed */ private VariantContext(String source, String contig, long start, long stop, - Collection alleles, GenotypeMap genotypes, + Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } @@ -383,7 +383,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - public static VariantContext modifyGenotypes(VariantContext vc, GenotypeMap genotypes) { + public static VariantContext modifyGenotypes(VariantContext vc, GenotypeCollection genotypes) { return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), false); } @@ -458,7 +458,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { return new VariantContext(getSource(), contig, start, stop, alleles, - GenotypeMap.create(genotypes), + GenotypeCollection.create(genotypes), getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), @@ -890,7 +890,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati /** * @return set of all Genotypes associated with this context */ - public GenotypeMap getGenotypes() { + public GenotypeCollection getGenotypes() { loadGenotypes(); return genotypes; } @@ -909,7 +909,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public GenotypeMap getGenotypes(String sampleName) { + public GenotypeCollection getGenotypes(String sampleName) { return getGenotypes(Arrays.asList(sampleName)); } @@ -921,8 +921,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public GenotypeMap getGenotypes(Collection sampleNames) { - GenotypeMap map = GenotypeMap.create(sampleNames.size()); + public GenotypeCollection getGenotypes(Collection sampleNames) { + GenotypeCollection map = GenotypeCollection.create(sampleNames.size()); for ( String name : sampleNames ) { if ( map.containsKey(name) ) throw new IllegalArgumentException("Duplicate names detected in requested samples " + sampleNames); @@ -1465,8 +1465,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati Byte refByte = inputVC.getReferenceBaseForIndel(); List alleles = new ArrayList(); - GenotypeMap genotypes = GenotypeMap.create(); - GenotypeMap inputGenotypes = inputVC.getGenotypes(); + GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypeCollection inputGenotypes = inputVC.getGenotypes(); for (Allele a : inputVC.getAlleles()) { // get bases for current allele and create a new one with trimmed bases diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 37d8acbe2..89db22bd6 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -70,7 +70,7 @@ public class VariantContextUtils { * @return VariantContext object */ public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, GenotypeMap.create(genotypes), negLog10PError, filters, attributes); + return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, GenotypeCollection.create(genotypes), negLog10PError, filters, attributes); } /** @@ -350,7 +350,7 @@ public class VariantContextUtils { if ( vc.hasID() ) attributes.put(VariantContext.ID_KEY, vc.getID()); // Genotypes - final GenotypeMap genotypes = GenotypeMap.create(vc.getNSamples()); + final GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); for ( final Genotype g : vc.getGenotypes().values() ) { Map genotypeAttributes = subsetAttributes(g.commonInfo, keysToPreserve); genotypes.put(g.getSampleName(), @@ -458,7 +458,7 @@ public class VariantContextUtils { final Map attributesWithMaxAC = new TreeMap(); double negLog10PError = -1; VariantContext vcWithMaxAC = null; - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); // counting the number of filtered and variant VCs int nFiltered = 0; @@ -648,7 +648,7 @@ public class VariantContextUtils { // nothing to do if we don't need to trim bases if (trimVC) { List alleles = new ArrayList(); - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); // set the reference base for indels in the attributes Map attributes = new TreeMap(inputVC.getAttributes()); @@ -702,8 +702,8 @@ public class VariantContextUtils { return inputVC; } - public static GenotypeMap stripPLs(GenotypeMap genotypes) { - GenotypeMap newGs = GenotypeMap.create(genotypes.size()); + public static GenotypeCollection stripPLs(GenotypeCollection genotypes) { + GenotypeCollection newGs = GenotypeCollection.create(genotypes.size()); for ( Map.Entry g : genotypes.entrySet() ) { newGs.put(g.getKey(), g.getValue().hasLikelihoods() ? removePLs(g.getValue()) : g.getValue()); @@ -883,7 +883,7 @@ public class VariantContextUtils { } } - private static void mergeGenotypes(GenotypeMap mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { + private static void mergeGenotypes(GenotypeCollection mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { for ( Genotype g : oneVC.getGenotypes().values() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); if ( ! mergedGenotypes.containsKey(name) ) { @@ -923,7 +923,7 @@ public class VariantContextUtils { } // create new Genotype objects - GenotypeMap newGenotypes = GenotypeMap.create(vc.getNSamples()); + GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { List newAlleles = new ArrayList(); for ( Allele allele : genotype.getValue().getAlleles() ) { @@ -943,7 +943,7 @@ public class VariantContextUtils { if ( allowedAttributes == null ) return vc; - GenotypeMap newGenotypes = GenotypeMap.create(vc.getNSamples()); + GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { Map attrs = new HashMap(); for ( Map.Entry attr : genotype.getValue().getAttributes().entrySet() ) { @@ -1022,7 +1022,7 @@ public class VariantContextUtils { } MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added - GenotypeMap mergedGenotypes = GenotypeMap.create(); + GenotypeCollection mergedGenotypes = GenotypeCollection.create(); for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { String sample = gt1Entry.getKey(); Genotype gt1 = gt1Entry.getValue(); diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index b658da1d3..d698d12a3 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -4,7 +4,7 @@ import org.broad.tribble.Tribble; import org.broad.tribble.readers.AsciiLineReader; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeMap; +import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -122,7 +122,7 @@ public class VCFWriterUnitTest extends BaseTest { List alleles = new ArrayList(); Set filters = null; Map attributes = new HashMap(); - GenotypeMap genotypes = GenotypeMap.create(); + GenotypeCollection genotypes = GenotypeCollection.create(); alleles.add(Allele.create("-",true)); alleles.add(Allele.create("CC",false)); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index 48ddb7efc..f5e485587 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -99,7 +99,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { int start = 10; int stop = start; // alleles.contains(ATC) ? start + 3 : start; return new VariantContext(source, "1", start, stop, alleles, - GenotypeMap.create(genotypes), 1.0, filters, null, Cref.getBases()[0]); + GenotypeCollection.create(genotypes), 1.0, filters, null, Cref.getBases()[0]); } // -------------------------------------------------------------------------------- From ab0ee9b847f1574e4bf2a6866022e5a959f58072 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Mon, 14 Nov 2011 15:10:50 -0500 Subject: [PATCH 018/113] Perform only necessary validation in VariantContext modify methods --- .../utils/variantcontext/VariantContext.java | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index f52a7087b..204b4b841 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -223,7 +223,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param referenceBaseForIndel padded reference base */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false); + this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); } /** @@ -240,7 +240,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false); + this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); } /** @@ -261,7 +261,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param referenceBaseForIndel padded reference base */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true); + this(source, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true, true); } /** @@ -278,7 +278,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes, null, false); + this(source, contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes, null, false, true); } /** @@ -291,7 +291,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param alleles alleles */ public VariantContext(String source, String contig, long start, long stop, Collection alleles) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, null, false); + this(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, null, false, true); } /** @@ -314,7 +314,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param other the VariantContext to copy */ public VariantContext(VariantContext other) { - this(other.getSource(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false); + this(other.getSource(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, true); } /** @@ -331,11 +331,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base * @param genotypesAreUnparsed true if the genotypes have not yet been parsed + * @param performValidation if true, call validate() as the final step in construction */ private VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, - Byte referenceBaseForIndel, boolean genotypesAreUnparsed) { + Byte referenceBaseForIndel, boolean genotypesAreUnparsed, + boolean performValidation ) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } this.contig = contig; this.start = start; @@ -371,39 +373,57 @@ public class VariantContext implements Feature { // to enable tribble intergrati } } - validate(); + if ( performValidation ) { + validate(); + } } // --------------------------------------------------------------------------------------------------------- // // Partial-cloning routines (because Variant Context is immutable). + // + // IMPORTANT: These routines assume that the VariantContext on which they're called is already valid. + // Due to this assumption, they explicitly tell the constructor NOT to perform validation by + // calling validate(), and instead perform validation only on the data that's changed. + // // Note that we don't call vc.getGenotypes() because that triggers the lazy loading. // Also note that we need to create a new attributes map because it's unmodifiable and the constructor may try to modify it. // // --------------------------------------------------------------------------------------------------------- public static VariantContext modifyGenotypes(VariantContext vc, Map genotypes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), false); + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), false, false); + modifiedVC.validateGenotypes(); + return modifiedVC; } public static VariantContext modifyLocation(VariantContext vc, String chr, int start, int end) { - return new VariantContext(vc.getSource(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true); + VariantContext modifiedVC = new VariantContext(vc.getSource(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); + + // Since start and end have changed, we need to call both validateAlleles() and validateReferencePadding(), + // since those validation routines rely on the values of start and end: + modifiedVC.validateAlleles(); + modifiedVC.validateReferencePadding(); + + return modifiedVC; } public static VariantContext modifyFilters(VariantContext vc, Set filters) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true); + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); } public static VariantContext modifyAttributes(VariantContext vc, Map attributes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true); + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); } public static VariantContext modifyReferencePadding(VariantContext vc, Byte b) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true); + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true, false); + modifiedVC.validateReferencePadding(); + return modifiedVC; } public static VariantContext modifyPErrorFiltersAndAttributes(VariantContext vc, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true); + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true, false); } // --------------------------------------------------------------------------------------------------------- From f0234ab67f6161db6f5865dcb04ca0dc8cce33da Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 17:42:55 -0500 Subject: [PATCH 019/113] GenotypeMap -> GenotypeCollection part 2 -- Code actually builds --- .../gatk/refdata/VariantContextAdaptors.java | 11 +- .../walkers/annotator/HaplotypeScore.java | 5 +- .../walkers/annotator/InbreedingCoeff.java | 3 +- .../gatk/walkers/annotator/QualByDepth.java | 6 +- .../gatk/walkers/annotator/RankSumTest.java | 8 +- .../annotator/VariantAnnotatorEngine.java | 9 +- .../beagle/BeagleOutputToVCFWalker.java | 11 +- .../beagle/ProduceBeagleInputWalker.java | 13 +- .../walkers/diffengine/VCFDiffableReader.java | 2 +- .../filters/VariantFiltrationWalker.java | 9 +- .../AlleleFrequencyCalculationModel.java | 4 +- .../genotyper/ExactAFCalculationModel.java | 31 +- .../genotyper/GridSearchAFEstimation.java | 270 ------------------ .../walkers/genotyper/UGCallVariants.java | 13 +- .../genotyper/UnifiedGenotyperEngine.java | 5 +- .../indels/SomaticIndelDetectorWalker.java | 9 +- ...eSegregatingAlternateAllelesVCFWriter.java | 2 +- .../walkers/phasing/PhaseByTransmission.java | 6 +- .../phasing/ReadBackedPhasingWalker.java | 16 +- .../varianteval/evaluators/CountVariants.java | 4 +- .../evaluators/G1KPhaseITable.java | 2 +- .../evaluators/GenotypeConcordance.java | 21 +- .../evaluators/ThetaVariantEvaluator.java | 2 +- .../varianteval/util/VariantEvalUtils.java | 4 +- .../variantutils/LeftAlignVariants.java | 6 +- .../walkers/variantutils/SelectVariants.java | 2 +- .../walkers/variantutils/VariantsToVCF.java | 3 +- .../utils/codecs/vcf/StandardVCFWriter.java | 2 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 2 +- .../sting/utils/codecs/vcf/VCFCodec.java | 8 +- .../broadinstitute/sting/utils/gcf/GCF.java | 6 +- .../variantcontext/GenotypeCollection.java | 61 +++- .../utils/variantcontext/VariantContext.java | 212 ++++++-------- .../variantcontext/VariantContextUtils.java | 66 ++--- .../walkers/qc/TestVariantContextWalker.java | 13 +- .../utils/genotype/vcf/VCFWriterUnitTest.java | 4 +- .../VariantContextUtilsUnitTest.java | 12 +- 37 files changed, 282 insertions(+), 581 deletions(-) delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index 523da7492..164e1f1cb 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -202,15 +202,6 @@ public class VariantContextAdaptors { } } - public static VCFHeader createVCFHeader(Set hInfo, VariantContext vc) { - HashSet names = new LinkedHashSet(); - for ( Genotype g : vc.getGenotypesSortedByName() ) { - names.add(g.getSampleName()); - } - - return new VCFHeader(hInfo == null ? new HashSet() : hInfo, names); - } - // -------------------------------------------------------------------------------------------------------------- // // GELI to VariantContext @@ -353,7 +344,7 @@ public class VariantContextAdaptors { } Genotype g = new Genotype(samples[i], myAlleles); - genotypes.put(samples[i], g); + genotypes.add(g); } HashMap attrs = new HashMap(1); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HaplotypeScore.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HaplotypeScore.java index 94b0636f4..551f8e2cf 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HaplotypeScore.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HaplotypeScore.java @@ -89,9 +89,8 @@ public class HaplotypeScore extends InfoFieldAnnotation implements StandardAnnot final MathUtils.RunningAverage scoreRA = new MathUtils.RunningAverage(); if (haplotypes != null) { - final Set> genotypes = vc.getGenotypes().entrySet(); - for ( final Map.Entry genotype : genotypes ) { - final AlignmentContext thisContext = stratifiedContexts.get(genotype.getKey()); + for ( final Genotype genotype : vc.getGenotypes()) { + final AlignmentContext thisContext = stratifiedContexts.get(genotype.getSampleName()); if ( thisContext != null ) { final ReadBackedPileup thisPileup; if (thisContext.hasExtendedEventPileup()) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java index a21d7106c..917a75294 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java @@ -52,8 +52,7 @@ public class InbreedingCoeff extends InfoFieldAnnotation implements StandardAnno double hetCount = 0.0; double homCount = 0.0; int N = 0; // number of samples that have likelihoods - for ( final Map.Entry genotypeMap : genotypes.entrySet() ) { - Genotype g = genotypeMap.getValue(); + for ( final Genotype g : genotypes ) { if ( g.isNoCall() || !g.hasLikelihoods() ) continue; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java index dae041155..3a1f2cc87 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java @@ -35,13 +35,13 @@ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotati int depth = 0; - for ( Map.Entry genotype : genotypes.entrySet() ) { + for ( final Genotype genotype : genotypes ) { // we care only about variant calls with likelihoods - if ( genotype.getValue().isHomRef() ) + if ( genotype.isHomRef() ) continue; - AlignmentContext context = stratifiedContexts.get(genotype.getKey()); + AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) continue; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java index 8182747f4..97e014373 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java @@ -43,8 +43,8 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar if (vc.isSNP() && vc.isBiallelic()) { // todo - no current support for multiallelic snps - for ( final Map.Entry genotype : genotypes.entrySet() ) { - final AlignmentContext context = stratifiedContexts.get(genotype.getKey()); + for ( final Genotype genotype : genotypes ) { + final AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) { continue; } @@ -53,8 +53,8 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar } else if (vc.isIndel() || vc.isMixed()) { - for ( final Map.Entry genotype : genotypes.entrySet() ) { - final AlignmentContext context = stratifiedContexts.get(genotype.getKey()); + for ( final Genotype genotype : genotypes ) { + final AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) { continue; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index 6f481b872..a0bd69be7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -229,11 +229,10 @@ public class VariantAnnotatorEngine { return vc.getGenotypes(); GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); - for ( Map.Entry g : vc.getGenotypes().entrySet() ) { - Genotype genotype = g.getValue(); - AlignmentContext context = stratifiedContexts.get(g.getKey()); + for ( final Genotype genotype : vc.getGenotypes() ) { + AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) { - genotypes.put(g.getKey(), genotype); + genotypes.add(genotype); continue; } @@ -243,7 +242,7 @@ public class VariantAnnotatorEngine { if ( result != null ) genotypeAnnotations.putAll(result); } - genotypes.put(g.getKey(), new Genotype(g.getKey(), genotype.getAlleles(), genotype.getNegLog10PError(), genotype.getFilters(), genotypeAnnotations, genotype.isPhased())); + genotypes.add(new Genotype(genotype.getSampleName(), genotype.getAlleles(), genotype.getNegLog10PError(), genotype.getFilters(), genotypeAnnotations, genotype.isPhased())); } return genotypes; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 89dd114cc..c03621280 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -202,9 +202,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { hapmapGenotypes = vc_comp.getGenotypes(); } - for ( Map.Entry originalGenotypes : vc_input.getGenotypes().entrySet() ) { - - Genotype g = originalGenotypes.getValue(); + for ( final Genotype g : vc_input.getGenotypes() ) { Set filters = new LinkedHashSet(g.getFilters()); boolean genotypeIsPhased = true; @@ -214,7 +212,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { // use sample as key into genotypes structure if (vc_comp != null) { - if (vc_input.getGenotypes().containsKey(sample) && hapmapGenotypes.containsKey(sample)) { + if (vc_input.getGenotypes().containsSample(sample) && hapmapGenotypes.containsSample(sample)) { Genotype hapmapGenotype = hapmapGenotypes.get(sample); if (hapmapGenotype.isCalled()){ @@ -325,13 +323,12 @@ public class BeagleOutputToVCFWalker extends RodWalker { else { originalAttributes.put("OG","."); } - Genotype imputedGenotype = new Genotype(originalGenotypes.getKey(), alleles, genotypeQuality, filters,originalAttributes , genotypeIsPhased); + Genotype imputedGenotype = new Genotype(g.getSampleName(), alleles, genotypeQuality, filters,originalAttributes , genotypeIsPhased); if ( imputedGenotype.isHet() || imputedGenotype.isHomVar() ) { beagleVarCounts++; } - genotypes.put(originalGenotypes.getKey(), imputedGenotype); - + genotypes.add(imputedGenotype); } VariantContext filteredVC; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java index b722220f9..f7a84ee08 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java @@ -39,10 +39,7 @@ import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.StingException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.File; import java.io.PrintStream; @@ -245,18 +242,18 @@ public class ProduceBeagleInputWalker extends RodWalker { } if ( markers != null ) markers.append("\n"); - Map preferredGenotypes = preferredVC.getGenotypes(); - Map otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; + GenotypeCollection preferredGenotypes = preferredVC.getGenotypes(); + GenotypeCollection otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; for ( String sample : samples ) { boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSample(sample).getGender() == Gender.MALE; Genotype genotype; boolean isValidation; // use sample as key into genotypes structure - if ( preferredGenotypes.keySet().contains(sample) ) { + if ( preferredGenotypes.containsSample(sample) ) { genotype = preferredGenotypes.get(sample); isValidation = isValidationSite; - } else if ( otherGenotypes != null && otherGenotypes.keySet().contains(sample) ) { + } else if ( otherGenotypes != null && otherGenotypes.containsSample(sample) ) { genotype = otherGenotypes.get(sample); isValidation = ! isValidationSite; } else { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java index a447d17af..2587b30ef 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java @@ -102,7 +102,7 @@ public class VCFDiffableReader implements DiffableReader { vcRoot.add(attribute.getKey(), attribute.getValue()); } - for (Genotype g : vc.getGenotypes().values() ) { + for (Genotype g : vc.getGenotypes() ) { DiffNode gRoot = DiffNode.empty(g.getSampleName(), vcRoot); gRoot.add("GT", g.getGenotypeString()); gRoot.add("GQ", g.hasNegLog10PError() ? g.getNegLog10PError() * 10 : VCFConstants.MISSING_VALUE_v4 ); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index c1fbc9ac6..81bff7aaa 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -290,10 +290,7 @@ public class VariantFiltrationWalker extends RodWalker { genotypes = GenotypeCollection.create(vc.getGenotypes().size()); // for each genotype, check filters then create a new object - for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { - - Genotype g = genotype.getValue(); - + for ( final Genotype g : vc.getGenotypes() ) { if ( g.isCalled() ) { Set filters = new LinkedHashSet(g.getFilters()); @@ -301,9 +298,9 @@ public class VariantFiltrationWalker extends RodWalker { if ( VariantContextUtils.match(vc, g, exp) ) filters.add(exp.name); } - genotypes.put(genotype.getKey(), new Genotype(genotype.getKey(), g.getAlleles(), g.getNegLog10PError(), filters, g.getAttributes(), g.isPhased())); + genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), filters, g.getAttributes(), g.isPhased())); } else { - genotypes.put(genotype.getKey(), g); + genotypes.add(g); } } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java index d0f45092b..b81c1d4c3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java @@ -45,8 +45,6 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { public enum Model { /** The default model with the best performance in all cases */ EXACT, - /** For posterity we have kept around the older GRID_SEARCH model, but this gives inferior results and shouldn't be used. */ - GRID_SEARCH } protected int N; @@ -71,7 +69,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * @param log10AlleleFrequencyPriors priors * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store results */ - protected abstract void getLog10PNonRef(Map GLs, List Alleles, + protected abstract void getLog10PNonRef(GenotypeCollection GLs, List Alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index bb2516f7c..f0c73cd5f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -50,7 +50,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { super(UAC, N, logger, verboseWriter); } - public void getLog10PNonRef(Map GLs, List alleles, + public void getLog10PNonRef(GenotypeCollection GLs, List alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); @@ -94,11 +94,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - private static final ArrayList getGLs(Map GLs) { + private static final ArrayList getGLs(GenotypeCollection GLs) { ArrayList genotypeLikelihoods = new ArrayList(); genotypeLikelihoods.add(new double[]{0.0,0.0,0.0}); // dummy - for ( Genotype sample : GLs.values() ) { + for ( Genotype sample : GLs ) { if ( sample.hasLikelihoods() ) { double[] gls = sample.getLikelihoods().getAsVector(); @@ -154,7 +154,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - public int linearExact(Map GLs, + public int linearExact(GenotypeCollection GLs, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors, int idxAA, int idxAB, int idxBB) { final ArrayList genotypeLikelihoods = getGLs(GLs); @@ -290,16 +290,16 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // todo = can't deal with optimal dynamic programming solution with multiallelic records if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { - sampleIndices.addAll(GLs.keySet()); + sampleIndices.addAll(GLs.getSampleNames()); sampleIdx = GLs.size(); } else { - for ( Map.Entry sample : GLs.entrySet() ) { - if ( !sample.getValue().hasLikelihoods() ) + for ( final Genotype genotype : GLs ) { + if ( !genotype.hasLikelihoods() ) continue; - double[] likelihoods = sample.getValue().getLikelihoods().getAsVector(); + double[] likelihoods = genotype.getLikelihoods().getAsVector(); if (MathUtils.sum(likelihoods) > SUM_GL_THRESH_NOCALL) { //System.out.print(sample.getKey()+":"); @@ -311,7 +311,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { continue; } - sampleIndices.add(sample.getKey()); + sampleIndices.add(genotype.getSampleName()); for (int k=0; k <= AFofMaxLikelihood; k++) { @@ -415,17 +415,16 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { qual = -1.0 * Math.log10(1.0 - chosenGenotype); } //System.out.println(myAlleles.toString()); - calls.put(sample, new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); + calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); } - for ( Map.Entry sample : GLs.entrySet() ) { - - if ( !sample.getValue().hasLikelihoods() ) + for ( final Genotype genotype : GLs ) { + if ( !genotype.hasLikelihoods() ) continue; - Genotype g = GLs.get(sample.getKey()); + Genotype g = GLs.get(genotype.getSampleName()); - double[] likelihoods = sample.getValue().getLikelihoods().getAsVector(); + double[] likelihoods = genotype.getLikelihoods().getAsVector(); if (MathUtils.sum(likelihoods) <= SUM_GL_THRESH_NOCALL) continue; // regular likelihoods @@ -436,7 +435,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { myAlleles.add(Allele.NO_CALL); myAlleles.add(Allele.NO_CALL); //System.out.println(myAlleles.toString()); - calls.put(sample.getKey(), new Genotype(sample.getKey(), myAlleles, qual, null, g.getAttributes(), false)); + calls.add(new Genotype(genotype.getSampleName(), myAlleles, qual, null, g.getAttributes(), false)); } return calls; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java deleted file mode 100755 index 48df8dcb9..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GridSearchAFEstimation.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2010. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR - * THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.genotyper; - -import org.apache.log4j.Logger; -import org.broadinstitute.sting.utils.MathUtils; -import org.broadinstitute.sting.utils.collections.Pair; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; - -import java.io.PrintStream; -import java.util.*; - -public class GridSearchAFEstimation extends AlleleFrequencyCalculationModel { - - // for use in optimizing the P(D|AF) calculations: - // how much off from the max likelihoods do we need to be before we can quit calculating? - protected static final double LOG10_OPTIMIZATION_EPSILON = 8.0; - - private AlleleFrequencyMatrix AFMatrix; - - protected GridSearchAFEstimation(UnifiedArgumentCollection UAC, int N, Logger logger, PrintStream verboseWriter) { - super(UAC, N, logger, verboseWriter); - AFMatrix = new AlleleFrequencyMatrix(N); - } - - protected void getLog10PNonRef(Map GLs, List alleles, - double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors) { - initializeAFMatrix(GLs); - - // first, calculate for AF=0 (no change to matrix) - log10AlleleFrequencyPosteriors[0] = AFMatrix.getLikelihoodsOfFrequency() + log10AlleleFrequencyPriors[0]; - double maxLikelihoodSeen = log10AlleleFrequencyPosteriors[0]; - - int maxAlleleFrequencyToTest = AFMatrix.getSamples().size() * 2; - - // for each minor allele frequency, calculate log10PofDgivenAFi - for (int i = 1; i <= maxAlleleFrequencyToTest; i++) { - // add one more alternate allele - AFMatrix.incrementFrequency(); - - // calculate new likelihoods - log10AlleleFrequencyPosteriors[i] = AFMatrix.getLikelihoodsOfFrequency() + log10AlleleFrequencyPriors[i]; - - // an optimization to speed up the calculation: if we are beyond the local maximum such - // that subsequent likelihoods won't factor into the confidence score, just quit - if ( maxLikelihoodSeen - log10AlleleFrequencyPosteriors[i] > LOG10_OPTIMIZATION_EPSILON ) - return; - - if ( log10AlleleFrequencyPosteriors[i] > maxLikelihoodSeen ) - maxLikelihoodSeen = log10AlleleFrequencyPosteriors[i]; - } - } - - /** - * Overrides the super class - * @param vc variant context with genotype likelihoods - * @param log10AlleleFrequencyPosteriors allele frequency results - * @param AFofMaxLikelihood allele frequency of max likelihood - * - * @return calls - */ - protected GenotypeCollection assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, - int AFofMaxLikelihood) { - if ( !vc.isVariant() ) - throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); - - Allele refAllele = vc.getReference(); - Allele altAllele = vc.getAlternateAllele(0); - GenotypeCollection calls = GenotypeCollection.create(); - - // first, the potential alt calls - for ( String sample : AFMatrix.getSamples() ) { - Genotype g = vc.getGenotype(sample); - - // set the genotype and confidence - Pair AFbasedGenotype = AFMatrix.getGenotype(AFofMaxLikelihood, sample); - ArrayList myAlleles = new ArrayList(); - if ( AFbasedGenotype.first == GenotypeType.AA.ordinal() ) { - myAlleles.add(refAllele); - myAlleles.add(refAllele); - } else if ( AFbasedGenotype.first == GenotypeType.AB.ordinal() ) { - myAlleles.add(refAllele); - myAlleles.add(altAllele); - } else { // ( AFbasedGenotype.first == GenotypeType.BB.ordinal() ) - myAlleles.add(altAllele); - myAlleles.add(altAllele); - } - - calls.put(sample, new Genotype(sample, myAlleles, AFbasedGenotype.second, null, g.getAttributes(), false)); - } - - return calls; - } - - private void initializeAFMatrix(Map GLs) { - AFMatrix.clear(); - - for ( Genotype g : GLs.values() ) { - if ( g.hasLikelihoods() ) - AFMatrix.setLikelihoods(g.getLikelihoods().getAsVector(), g.getSampleName()); - } - } - - protected static class AlleleFrequencyMatrix { - - private double[][] matrix; // allele frequency matrix - private int[] indexes; // matrix to maintain which genotype is active - private int maxN; // total possible frequencies in data - private int frequency; // current frequency - - // data structures necessary to maintain a list of the best genotypes and their scores - private ArrayList samples = new ArrayList(); - private HashMap>> samplesToGenotypesPerAF = new HashMap>>(); - - public AlleleFrequencyMatrix(int N) { - maxN = N; - matrix = new double[N][3]; - indexes = new int[N]; - clear(); - } - - public List getSamples() { return samples; } - - public void clear() { - frequency = 0; - for (int i = 0; i < maxN; i++) - indexes[i] = 0; - samples.clear(); - samplesToGenotypesPerAF.clear(); - } - - public void setLikelihoods(double[] GLs, String sample) { - int index = samples.size(); - samples.add(sample); - matrix[index][GenotypeType.AA.ordinal()] = GLs[0]; - matrix[index][GenotypeType.AB.ordinal()] = GLs[1]; - matrix[index][GenotypeType.BB.ordinal()] = GLs[2]; - } - - public void incrementFrequency() { - int N = samples.size(); - if ( frequency == 2 * N ) - throw new ReviewedStingException("Frequency was incremented past N; how is this possible?"); - frequency++; - - double greedy = VALUE_NOT_CALCULATED; - int greedyIndex = -1; - for (int i = 0; i < N; i++) { - - if ( indexes[i] == GenotypeType.AB.ordinal() ) { - if ( matrix[i][GenotypeType.BB.ordinal()] - matrix[i][GenotypeType.AB.ordinal()] > greedy ) { - greedy = matrix[i][GenotypeType.BB.ordinal()] - matrix[i][GenotypeType.AB.ordinal()]; - greedyIndex = i; - } - } - else if ( indexes[i] == GenotypeType.AA.ordinal() ) { - if ( matrix[i][GenotypeType.AB.ordinal()] - matrix[i][GenotypeType.AA.ordinal()] > greedy ) { - greedy = matrix[i][GenotypeType.AB.ordinal()] - matrix[i][GenotypeType.AA.ordinal()]; - greedyIndex = i; - } - // note that we currently don't bother with breaking ties between samples - // (which would be done by looking at the HOM_VAR value) because it's highly - // unlikely that a collision will both occur and that the difference will - // be significant at HOM_VAR... - } - // if this person is already hom var, he can't add another alternate allele - // so we can ignore that case - } - if ( greedyIndex == -1 ) - throw new ReviewedStingException("There is no best choice for a new alternate allele; how is this possible?"); - - if ( indexes[greedyIndex] == GenotypeType.AB.ordinal() ) - indexes[greedyIndex] = GenotypeType.BB.ordinal(); - else - indexes[greedyIndex] = GenotypeType.AB.ordinal(); - } - - public double getLikelihoodsOfFrequency() { - double likelihoods = 0.0; - int N = samples.size(); - for (int i = 0; i < N; i++) - likelihoods += matrix[i][indexes[i]]; - - /* - System.out.println(frequency); - for (int i = 0; i < N; i++) { - System.out.print(samples.get(i)); - for (int j=0; j < 3; j++) { - System.out.print(String.valueOf(matrix[i][j])); - System.out.print(indexes[i] == j ? "* " : " "); - } - System.out.println(); - } - System.out.println(likelihoods); - System.out.println(); - */ - - recordGenotypes(); - - return likelihoods; - } - - public Pair getGenotype(int frequency, String sample) { - return samplesToGenotypesPerAF.get(frequency).get(sample); - } - - private void recordGenotypes() { - HashMap> samplesToGenotypes = new HashMap>(); - - int index = 0; - for ( String sample : samples ) { - int genotype = indexes[index]; - - double score; - - int maxEntry = MathUtils.maxElementIndex(matrix[index]); - // if the max value is for the most likely genotype, we can compute next vs. next best - if ( genotype == maxEntry ) { - if ( genotype == GenotypeType.AA.ordinal() ) - score = matrix[index][genotype] - Math.max(matrix[index][GenotypeType.AB.ordinal()], matrix[index][GenotypeType.BB.ordinal()]); - else if ( genotype == GenotypeType.AB.ordinal() ) - score = matrix[index][genotype] - Math.max(matrix[index][GenotypeType.AA.ordinal()], matrix[index][GenotypeType.BB.ordinal()]); - else // ( genotype == GenotypeType.HOM.ordinal() ) - score = matrix[index][genotype] - Math.max(matrix[index][GenotypeType.AA.ordinal()], matrix[index][GenotypeType.AB.ordinal()]); - } - // otherwise, we need to calculate the probability of the genotype - else { - double[] normalized = MathUtils.normalizeFromLog10(matrix[index]); - double chosenGenotype = normalized[genotype]; - score = -1.0 * Math.log10(1.0 - chosenGenotype); - } - - samplesToGenotypes.put(sample, new Pair(genotype, Math.abs(score))); - index++; - } - - samplesToGenotypesPerAF.put(frequency, samplesToGenotypes); - } - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index 81310f15a..71ad0d75a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -133,7 +133,7 @@ public class UGCallVariants extends RodWalker { for ( VariantContext vc : VCs ) { if ( variantVC == null && vc.isVariant() ) variantVC = vc; - genotypes.putAll(getGenotypesWithGLs(vc.getGenotypes())); + genotypes.addAll(getGenotypesWithGLs(vc.getGenotypes())); } if ( variantVC == null ) { @@ -143,13 +143,12 @@ public class UGCallVariants extends RodWalker { return new VariantContext("VCwithGLs", variantVC.getChr(), variantVC.getStart(), variantVC.getEnd(), variantVC.getAlleles(), genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null); } - private static Map getGenotypesWithGLs(Map genotypes) { - Map genotypesWithGLs = new HashMap(); - for ( Map.Entry g : genotypes.entrySet() ) { - if ( g.getValue().hasLikelihoods() && g.getValue().getLikelihoods().getAsVector() != null ) - genotypesWithGLs.put(g.getKey(), g.getValue()); + private static GenotypeCollection getGenotypesWithGLs(GenotypeCollection genotypes) { + GenotypeCollection genotypesWithGLs = GenotypeCollection.create(genotypes.size()); + for ( final Genotype g : genotypes ) { + if ( g.hasLikelihoods() && g.getLikelihoods().getAsVector() != null ) + genotypesWithGLs.add(g); } - return genotypesWithGLs; } } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index 72b88100b..a89545a66 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -281,7 +281,7 @@ public class UnifiedGenotyperEngine { attributes.put(VCFConstants.DEPTH_KEY, GL.getDepth()); attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, likelihoods); - genotypes.put(GL.getSample(), new Genotype(GL.getSample(), noCall, Genotype.NO_NEG_LOG_10PERROR, null, attributes, false)); + genotypes.add(new Genotype(GL.getSample(), noCall, Genotype.NO_NEG_LOG_10PERROR, null, attributes, false)); } GenomeLoc loc = refContext.getLocus(); @@ -811,9 +811,6 @@ public class UnifiedGenotyperEngine { case EXACT: afcm = new ExactAFCalculationModel(UAC, N, logger, verboseWriter); break; - case GRID_SEARCH: - afcm = new GridSearchAFEstimation(UAC, N, logger, verboseWriter); - break; default: throw new IllegalArgumentException("Unexpected AlleleFrequencyCalculationModel " + UAC.AFmodel); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index 7425258d4..dda4a7a09 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -1059,15 +1059,14 @@ public class SomaticIndelDetectorWalker extends ReadWalker { } GenotypeCollection genotypes = GenotypeCollection.create(); - for ( String sample : normalSamples ) { Map attrs = call.makeStatsAttributes(null); if ( call.isCall() ) // we made a call - put actual het genotype here: - genotypes.put(sample,new Genotype(sample,alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); + genotypes.add(new Genotype(sample,alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); else // no call: genotype is ref/ref (but alleles still contain the alt if we observed anything at all) - genotypes.put(sample,new Genotype(sample, homref_alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); + genotypes.add(new Genotype(sample, homref_alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); } Set filters = null; @@ -1151,11 +1150,11 @@ public class SomaticIndelDetectorWalker extends ReadWalker { GenotypeCollection genotypes = GenotypeCollection.create(); for ( String sample : normalSamples ) { - genotypes.put(sample,new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsNormal,false)); + genotypes.add(new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsNormal,false)); } for ( String sample : tumorSamples ) { - genotypes.put(sample,new Genotype(sample, homRefT ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsTumor,false) ); + genotypes.add(new Genotype(sample, homRefT ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsTumor,false) ); } Set filters = null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java index 53cfaa3a9..5ae034b0a 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java @@ -122,7 +122,7 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { if (useSingleSample != null) { // only want to output context for one sample Genotype sampGt = vc.getGenotype(useSingleSample); if (sampGt != null) // TODO: subContextFromGenotypes() does not handle any INFO fields [AB, HaplotypeScore, MQ, etc.]. Note that even SelectVariants.subsetRecord() only handles AC,AN,AF, and DP! - vc = vc.subContextFromGenotypes(sampGt); + vc = vc.subContextFromSample(sampGt.getSampleName()); else // asked for a sample that this vc does not contain, so ignore this vc: return; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index b35c54f94..2a3e353ef 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -293,7 +293,7 @@ public class PhaseByTransmission extends RodWalker { if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); - GenotypeCollection genotypeCollection = vc.getGenotypes(); + GenotypeCollection genotypeCollection = GenotypeCollection.create(vc.getGenotypes().size()); for (Trio trio : trios) { Genotype mother = vc.getGenotype(trio.getMother()); @@ -306,9 +306,7 @@ public class PhaseByTransmission extends RodWalker { Genotype phasedFather = trioGenotypes.get(1); Genotype phasedChild = trioGenotypes.get(2); - genotypeCollection.put(phasedMother.getSampleName(), phasedMother); - genotypeCollection.put(phasedFather.getSampleName(), phasedFather); - genotypeCollection.put(phasedChild.getSampleName(), phasedChild); + genotypeCollection.add(phasedMother, phasedFather, phasedChild); } VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypeCollection); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 132ed1582..82adfe96c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -122,7 +122,8 @@ public class ReadBackedPhasingWalker extends RodWalker samplesToPhase = null; + protected Set + samplesToPhase = null; private GenomeLoc mostDownstreamLocusReached = null; @@ -272,10 +273,10 @@ public class ReadBackedPhasingWalker extends RodWalker KEYS_TO_KEEP_IN_REDUCED_VCF = new HashSet(Arrays.asList(PQ_KEY)); - private VariantContext reduceVCToSamples(VariantContext vc, List samplesToPhase) { + private VariantContext reduceVCToSamples(VariantContext vc, Set samplesToPhase) { // for ( String sample : samplesToPhase ) // logger.debug(String.format(" Sample %s has genotype %s, het = %s", sample, vc.getGenotype(sample), vc.getGenotype(sample).isHet() )); - VariantContext subvc = vc.subContextFromGenotypes(vc.getGenotypes(samplesToPhase).values()); + VariantContext subvc = vc.subContextFromSamples(samplesToPhase); // logger.debug("original VC = " + vc); // logger.debug("sub VC = " + subvc); return VariantContextUtils.pruneVariantContext(subvc, KEYS_TO_KEEP_IN_REDUCED_VCF); @@ -354,9 +355,8 @@ public class ReadBackedPhasingWalker extends RodWalker samplePhaseStats = new TreeMap(); - for (Map.Entry sampGtEntry : sampGenotypes.entrySet()) { - String samp = sampGtEntry.getKey(); - Genotype gt = sampGtEntry.getValue(); + for (final Genotype gt : sampGenotypes) { + String samp = gt.getSampleName(); if (DEBUG) logger.debug("sample = " + samp); if (isUnfilteredCalledDiploidGenotype(gt)) { @@ -1134,7 +1134,7 @@ public class ReadBackedPhasingWalker extends RodWalker(vc.getAttributes()); @@ -1153,7 +1153,7 @@ public class ReadBackedPhasingWalker extends RodWalker 0 ? vc1.getAlternateAllele(0).getBaseString().toUpperCase() : null; + for (final Genotype g : vc1.getGenotypes()) { + final String altStr = vc1.getAlternateAlleles().size() > 0 ? vc1.getAlternateAllele(0).getBaseString().toUpperCase() : null; switch (g.getType()) { case NO_CALL: diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java index 8cc321ef5..417e340b8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java @@ -118,7 +118,7 @@ public class G1KPhaseITable extends VariantEvaluator { } // count variants per sample - for (final Genotype g : eval.getGenotypes().values()) { + for (final Genotype g : eval.getGenotypes()) { if ( ! g.isNoCall() && ! g.isHomRef() ) { int count = countsPerSample.get(g.getSampleName()).get(eval.getType()); countsPerSample.get(g.getSampleName()).put(eval.getType(), count + 1); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java index bbd3f5f54..70b37f500 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java @@ -209,7 +209,7 @@ public class GenotypeConcordance extends VariantEvaluator { //public GenotypeConcordance(VariantEvalWalker parent) { // super(parent); - // discordantInteresting = parent.DISCORDANT_INTERESTING; + // discordantInteresting = parent.DISCORDANT_INTERESTING; //} public String getName() { @@ -277,8 +277,9 @@ public class GenotypeConcordance extends VariantEvaluator { // determine concordance for eval data if (eval != null) { - for (final String sample : eval.getGenotypes().keySet()) { - final Genotype.Type called = eval.getGenotype(sample).getType(); + for (final Genotype g : eval.getGenotypes() ) { + final String sample = g.getSampleName(); + final Genotype.Type called = g.getType(); final Genotype.Type truth; if (!validationIsValidVC || !validation.hasGenotype(sample)) { @@ -299,9 +300,9 @@ public class GenotypeConcordance extends VariantEvaluator { else { final Genotype.Type called = Genotype.Type.NO_CALL; - for (final String sample : validation.getGenotypes().keySet()) { - final Genotype.Type truth = validation.getGenotype(sample).getType(); - detailedStats.incrValue(sample, truth, called); + for (final Genotype g : validation.getGenotypes()) { + final Genotype.Type truth = g.getType(); + detailedStats.incrValue(g.getSampleName(), truth, called); // print out interesting sites /* @@ -410,8 +411,8 @@ class SampleStats implements TableType { public SampleStats(VariantContext vc, int nGenotypeTypes) { this.nGenotypeTypes = nGenotypeTypes; - for (String sample : vc.getGenotypes().keySet()) - concordanceStats.put(sample, new long[nGenotypeTypes][nGenotypeTypes]); + for (final Genotype g : vc.getGenotypes()) + concordanceStats.put(g.getSampleName(), new long[nGenotypeTypes][nGenotypeTypes]); } public SampleStats(int genotypeTypes) { @@ -511,8 +512,8 @@ class SampleSummaryStats implements TableType { public SampleSummaryStats(final VariantContext vc) { concordanceSummary.put(ALL_SAMPLES_KEY, new double[COLUMN_KEYS.length]); - for( final String sample : vc.getGenotypes().keySet() ) { - concordanceSummary.put(sample, new double[COLUMN_KEYS.length]); + for( final Genotype g : vc.getGenotypes() ) { + concordanceSummary.put(g.getSampleName(), new double[COLUMN_KEYS.length]); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java index e51623c3c..e1069d2d2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java @@ -48,7 +48,7 @@ public class ThetaVariantEvaluator extends VariantEvaluator { float numGenosHere = 0; int numIndsHere = 0; - for (Genotype genotype : vc.getGenotypes().values()) { + for (final Genotype genotype : vc.getGenotypes()) { numIndsHere++; if (!genotype.isNoCall()) { //increment stats for heterozygosity diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index e700a733c..24caed549 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -266,7 +266,7 @@ public class VariantEvalUtils { * @return a new VariantContext with just the requested sample */ public VariantContext getSubsetOfVariantContext(VariantContext vc, String sampleName) { - return getSubsetOfVariantContext(vc, Arrays.asList(sampleName)); + return getSubsetOfVariantContext(vc, new HashSet(Arrays.asList(sampleName))); } /** @@ -276,7 +276,7 @@ public class VariantEvalUtils { * @param sampleNames the samples to pull out of the VariantContext * @return a new VariantContext with just the requested samples */ - public VariantContext getSubsetOfVariantContext(VariantContext vc, Collection sampleNames) { + public VariantContext getSubsetOfVariantContext(VariantContext vc, Set sampleNames) { VariantContext vcsub = vc.subContextFromSamples(sampleNames, vc.getAlleles()); HashMap newAts = new HashMap(vcsub.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java index f87ec5d3b..0148a71c2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java @@ -212,15 +212,15 @@ public class LeftAlignVariants extends RodWalker { // create new Genotype objects GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); - for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { + for ( final Genotype genotype : vc.getGenotypes() ) { List newAlleles = new ArrayList(); - for ( Allele allele : genotype.getValue().getAlleles() ) { + for ( Allele allele : genotype.getAlleles() ) { Allele newA = alleleMap.get(allele); if ( newA == null ) newA = Allele.NO_CALL; newAlleles.add(newA); } - newGenotypes.put(genotype.getKey(), Genotype.modifyAlleles(genotype.getValue(), newAlleles)); + newGenotypes.add(Genotype.modifyAlleles(genotype, newAlleles)); } return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), refBaseForIndel); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 3764a9998..3c92bf00f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -557,7 +557,7 @@ public class SelectVariants extends RodWalker { // check if we find it in the variant rod GenotypeCollection genotypes = vc.getGenotypes(samples); - for (Genotype g : genotypes.values()) { + for (final Genotype g : genotypes) { if (sampleHasVariant(g)) { // There is a variant called (or filtered with not exclude filtered option set) that is not HomRef for at least one of the samples. if (compVCs == null) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java index ff7fb3434..27eaa7b5d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java @@ -130,8 +130,7 @@ public class VariantsToVCF extends RodWalker { // set the appropriate sample name if necessary if ( sampleName != null && vc.hasGenotypes() && vc.hasGenotype(variants.getName()) ) { Genotype g = Genotype.modifyName(vc.getGenotype(variants.getName()), sampleName); - GenotypeCollection genotypes = GenotypeCollection.create(1); - genotypes.put(sampleName, g); + GenotypeCollection genotypes = GenotypeCollection.create(g); vc = VariantContext.modifyGenotypes(vc, genotypes); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java index 0da7a100f..d13002642 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java @@ -451,7 +451,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { boolean sawGoodGT = false; boolean sawGoodQual = false; boolean sawGenotypeFilter = false; - for ( Genotype g : vc.getGenotypes().values() ) { + for ( final Genotype g : vc.getGenotypes() ) { keys.addAll(g.getAttributes().keySet()); if ( g.isAvailable() ) sawGoodGT = true; diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index 302b93da7..4d6f26e87 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -180,7 +180,7 @@ public class VCF3Codec extends AbstractVCFCodec { // add it to the list try { - genotypes.put(sampleName, new Genotype(sampleName, + genotypes.add(new Genotype(sampleName, parseGenotypeAlleles(GTValueArray[genotypeAlleleLocation], alleles, alleleMap), GTQual, genotypeFilters, diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index 8256c9cac..696b35050 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -209,13 +209,7 @@ public class VCFCodec extends AbstractVCFCodec { // add it to the list try { - genotypes.put(sampleName, - new Genotype(sampleName, - GTalleles, - GTQual, - genotypeFilters, - gtAttributes, - phased)); + genotypes.add(new Genotype(sampleName, GTalleles, GTQual, genotypeFilters, gtAttributes, phased)); } catch (TribbleException e) { throw new TribbleException.InternalCodecException(e.getMessage() + ", at position " + chr+":"+pos); } diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index 9a900d734..a06fe906f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -155,12 +155,12 @@ public class GCF { if ( genotypes.isEmpty() ) return VariantContext.NO_GENOTYPES; else { - GenotypeCollection map = GenotypeCollection.create(); + GenotypeCollection map = GenotypeCollection.create(genotypes.size()); for ( int i = 0; i < genotypes.size(); i++ ) { final String sampleName = header.getSample(i); final Genotype g = genotypes.get(i).decode(sampleName, header, this, alleleMap); - map.put(sampleName, g); + map.add(g); } return map; @@ -173,7 +173,7 @@ public class GCF { List genotypes = new ArrayList(nGenotypes); for ( int i = 0; i < nGenotypes; i++ ) genotypes.add(null); - for ( Genotype g : vc.getGenotypes().values() ) { + for ( Genotype g : vc.getGenotypes() ) { int i = GCFHeaderBuilder.encodeSample(g.getSampleName()); genotypes.set(i, new GCFGenotype(GCFHeaderBuilder, alleleMap, g)); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java index a83356647..f12a8e531 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java @@ -67,20 +67,28 @@ public class GenotypeCollection implements List { } public static final GenotypeCollection create(final int nGenotypes) { - return new GenotypeCollection(nGenotypes, true); + return new GenotypeCollection(nGenotypes, false); } // todo -- differentiate between empty constructor and copy constructor // todo -- create constructor (Genotype ... genotypes) public static final GenotypeCollection create(final ArrayList genotypes) { - return new GenotypeCollection(genotypes, true); + return new GenotypeCollection(genotypes, false); + } + + public static final GenotypeCollection create(final Genotype... genotypes) { + return new GenotypeCollection(new ArrayList(Arrays.asList(genotypes)), false); } public static final GenotypeCollection copy(final GenotypeCollection toCopy) { return create(toCopy.genotypes); } + public static final GenotypeCollection copy(final Collection toCopy) { + return create(new ArrayList(toCopy)); + } + // public static final GenotypeMap create(final Collection genotypes) { // if ( genotypes == null ) // return null; // todo -- really should return an empty map @@ -173,6 +181,12 @@ public class GenotypeCollection implements List { return genotypes.add(genotype); } + public boolean add(final Genotype ... genotype) { + checkImmutability(); + invalidateCaches(); + return genotypes.addAll(Arrays.asList(genotype)); + } + @Override public void add(final int i, final Genotype genotype) { throw new UnsupportedOperationException(); @@ -291,7 +305,7 @@ public class GenotypeCollection implements List { return genotypes.toArray(ts); } - public Iterable iterateInOrder(final Iterable sampleNamesInOrder) { + public Iterable iterateInSampleNameOrder(final Iterable sampleNamesInOrder) { return new Iterable() { @Override public Iterator iterator() { @@ -300,6 +314,10 @@ public class GenotypeCollection implements List { }; } + public Iterable iterateInSampleNameOrder() { + return iterateInSampleNameOrder(getSampleNamesOrderedByName()); + } + private final class InOrderIterator implements Iterator { final Iterator sampleNamesInOrder; @@ -322,4 +340,41 @@ public class GenotypeCollection implements List { throw new UnsupportedOperationException(); } } + + public Set getSampleNames() { + buildCache(); + return sampleNameToOffset.keySet(); + } + + public Set getSampleNamesOrderedByName() { + return new TreeSet(getSampleNames()); + } + + public boolean containsSample(final String sample) { + buildCache(); + return sampleNameToOffset.containsKey(sample); + } + + public boolean containsSamples(final Collection samples) { + buildCache(); + return getSampleNames().containsAll(samples); + } + + public GenotypeCollection subsetToSamples( final Collection samples ) { + return subsetToSamples(new HashSet(samples)); + } + + public GenotypeCollection subsetToSamples( final Set samples ) { + if ( samples.size() == genotypes.size() ) + return this; + else if ( samples.isEmpty() ) + return NO_GENOTYPES; + else { + GenotypeCollection subset = create(samples.size()); + for ( final Genotype g : genotypes ) + if ( samples.contains(g.getSampleName()) ) + subset.add(g); + return subset; + } + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 5bd29a0ce..b394517bf 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -7,6 +7,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import java.lang.reflect.Array; import java.util.*; /** @@ -279,7 +280,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { this(source, contig, start, stop, alleles, - GenotypeCollection.create(genotypes), + GenotypeCollection.copy(genotypes), negLog10PError, filters, attributes, null, false); } @@ -423,58 +424,73 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - /** - * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotype - * genotype and alleles in genotype. This is the right way to test if a single genotype is actually - * variant or not. - * - * @param genotype genotype - * @return vc subcontext - */ - public VariantContext subContextFromGenotypes(Genotype genotype) { - return subContextFromGenotypes(Arrays.asList(genotype)); - } +// /** +// * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotype +// * genotype and alleles in genotype. This is the right way to test if a single genotype is actually +// * variant or not. +// * +// * @param genotype genotype +// * @return vc subcontext +// * @deprecated replaced by {@link #subContextFromSample(String)} +// */ +// public VariantContext subContextFromGenotypes(Genotype genotype) { +// return subContextFromGenotypes(Arrays.asList(genotype)); +// } +// +// +// /** +// * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes +// * genotypes and alleles in these genotypes. This is the right way to test if a single genotype is actually +// * variant or not. +// * +// * @param genotypes genotypes +// * @return vc subcontext +// * @deprecated replaced by {@link #subContextFromSamples(java.util.Collection)} +// */ +// public VariantContext subContextFromGenotypes(Collection genotypes) { +// return subContextFromGenotypes(genotypes, allelesOfGenotypes(genotypes)) ; +// } +// +// /** +// * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes +// * genotypes. Also, the resulting variant context will contain the alleles provided, not only those found in genotypes +// * +// * @param genotypes genotypes +// * @param alleles the set of allele segregating alleles at this site. Must include those in genotypes, but may be more +// * @return vc subcontext +// * @deprecated replaced by {@link #subContextFromSamples(java.util.Collection, java.util.Collection)} +// */ +// @Deprecated +// public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { +// return new VariantContext(getSource(), contig, start, stop, alleles, +// GenotypeCollection.create(genotypes), +// getNegLog10PError(), +// filtersWereApplied() ? getFilters() : null, +// getAttributes(), +// getReferenceBaseForIndel()); +// } - - /** - * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes - * genotypes and alleles in these genotypes. This is the right way to test if a single genotype is actually - * variant or not. - * - * @param genotypes genotypes - * @return vc subcontext - */ - public VariantContext subContextFromGenotypes(Collection genotypes) { - return subContextFromGenotypes(genotypes, allelesOfGenotypes(genotypes)) ; - } - - /** - * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes - * genotypes. Also, the resulting variant context will contain the alleles provided, not only those found in genotypes - * - * @param genotypes genotypes - * @param alleles the set of allele segregating alleles at this site. Must include those in genotypes, but may be more - * @return vc subcontext - */ - public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { + public VariantContext subContextFromSamples(Set sampleNames, Collection alleles) { return new VariantContext(getSource(), contig, start, stop, alleles, - GenotypeCollection.create(genotypes), + genotypes.subsetToSamples(sampleNames), getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), getReferenceBaseForIndel()); } - public VariantContext subContextFromSamples(Collection sampleNames, Collection alleles) { - return subContextFromGenotypes(getGenotypes(sampleNames).values(), alleles); - } - - public VariantContext subContextFromSamples(Collection sampleNames) { - return subContextFromGenotypes(getGenotypes(sampleNames).values()); + public VariantContext subContextFromSamples(Set sampleNames) { + GenotypeCollection newGenotypes = genotypes.subsetToSamples(sampleNames); + return new VariantContext(getSource(), contig, start, stop, allelesOfGenotypes(newGenotypes), + newGenotypes, + getNegLog10PError(), + filtersWereApplied() ? getFilters() : null, + getAttributes(), + getReferenceBaseForIndel()); } public VariantContext subContextFromSample(String sampleName) { - return subContextFromGenotypes(getGenotype(sampleName)); + return subContextFromSamples(new HashSet(Arrays.asList(sampleName))); } /** @@ -875,16 +891,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ public boolean hasGenotypes() { loadGenotypes(); - return genotypes.size() > 0; + return ! genotypes.isEmpty(); } public boolean hasGenotypes(Collection sampleNames) { loadGenotypes(); - for ( String name : sampleNames ) { - if ( ! genotypes.containsKey(name) ) - return false; - } - return true; + return genotypes.containsSamples(sampleNames); } /** @@ -895,10 +907,9 @@ public class VariantContext implements Feature { // to enable tribble intergrati return genotypes; } - public List getGenotypesSortedByName() { + public Iterable getGenotypesSortedByName() { loadGenotypes(); - Collection types = new TreeMap(genotypes).values(); - return new ArrayList(types); + return genotypes.iterateInSampleNameOrder(); } /** @@ -922,24 +933,23 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ public GenotypeCollection getGenotypes(Collection sampleNames) { - GenotypeCollection map = GenotypeCollection.create(sampleNames.size()); - - for ( String name : sampleNames ) { - if ( map.containsKey(name) ) throw new IllegalArgumentException("Duplicate names detected in requested samples " + sampleNames); - final Genotype g = getGenotype(name); - if ( g != null ) { - map.put(name, g); - } - } - - return map; + return getGenotypes().subsetToSamples(sampleNames); } + public GenotypeCollection getGenotypes(Set sampleNames) { + return getGenotypes().subsetToSamples(sampleNames); + } + + /** - * @return the set of all sample names in this context + * @return the set of all sample names in this context, not ordered */ public Set getSampleNames() { - return getGenotypes().keySet(); + return getGenotypes().getSampleNames(); + } + + public Set getSampleNamesOrderedByName() { + return getGenotypes().getSampleNamesOrderedByName(); } /** @@ -952,11 +962,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati } public boolean hasGenotype(String sample) { - return getGenotypes().containsKey(sample); + return getGenotypes().containsSample(sample); } public Genotype getGenotype(int ith) { - return getGenotypesSortedByName().get(ith); + return genotypes.get(ith); } @@ -968,7 +978,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati public int getChromosomeCount() { int n = 0; - for ( Genotype g : getGenotypes().values() ) { + for ( final Genotype g : getGenotypes() ) { n += g.isNoCall() ? 0 : g.getPloidy(); } @@ -984,7 +994,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati public int getChromosomeCount(Allele a) { int n = 0; - for ( Genotype g : getGenotypes().values() ) { + for ( final Genotype g : getGenotypes() ) { n += g.getAlleles(a).size(); } @@ -1015,7 +1025,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati if ( genotypeCounts == null ) { genotypeCounts = new int[Genotype.Type.values().length]; - for ( Genotype g : getGenotypes().values() ) { + for ( final Genotype g : getGenotypes() ) { if ( g.isNoCall() ) genotypeCounts[Genotype.Type.NO_CALL.ordinal()]++; else if ( g.isHomRef() ) @@ -1136,7 +1146,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati List reportedAlleles = getAlleles(); Set observedAlleles = new HashSet(); observedAlleles.add(getReference()); - for ( Genotype g : getGenotypes().values() ) { + for ( final Genotype g : getGenotypes() ) { if ( g.isCalled() ) observedAlleles.addAll(g.getAlleles()); } @@ -1285,12 +1295,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati private void validateGenotypes() { if ( this.genotypes == null ) throw new IllegalStateException("Genotypes is null"); - for ( Map.Entry elt : this.genotypes.entrySet() ) { - String name = elt.getKey(); - Genotype g = elt.getValue(); - - if ( ! name.equals(g.getSampleName()) ) throw new IllegalStateException("Bound sample name " + name + " does not equal the name of the genotype " + g.getSampleName()); - + for ( final Genotype g : this.genotypes ) { if ( g.isAvailable() ) { for ( Allele gAllele : g.getAlleles() ) { if ( ! hasAllele(gAllele) && gAllele.isCalled() ) @@ -1465,8 +1470,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati Byte refByte = inputVC.getReferenceBaseForIndel(); List alleles = new ArrayList(); - GenotypeCollection genotypes = GenotypeCollection.create(); - GenotypeCollection inputGenotypes = inputVC.getGenotypes(); for (Allele a : inputVC.getAlleles()) { // get bases for current allele and create a new one with trimmed bases @@ -1483,11 +1486,10 @@ public class VariantContext implements Feature { // to enable tribble intergrati } // now we can recreate new genotypes with trimmed alleles - for (String sample : inputVC.getSampleNames()) { - Genotype g = inputGenotypes.get(sample); - + GenotypeCollection genotypes = GenotypeCollection.create(inputVC.getNSamples()); + for (final Genotype g : inputVC.getGenotypes() ) { List inAlleles = g.getAlleles(); - List newGenotypeAlleles = new ArrayList(); + List newGenotypeAlleles = new ArrayList(g.getAlleles().size()); for (Allele a : inAlleles) { if (a.isCalled()) { if (a.isSymbolic()) { @@ -1506,8 +1508,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati newGenotypeAlleles.add(Allele.NO_CALL); } } - genotypes.put(sample, new Genotype(sample, newGenotypeAlleles, g.getNegLog10PError(), - g.getFilters(),g.getAttributes(),g.isPhased())); + genotypes.add(new Genotype(g.getSampleName(), newGenotypeAlleles, g.getNegLog10PError(), + g.getFilters(), g.getAttributes(), g.isPhased())); } @@ -1520,48 +1522,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati } - public ArrayList getTwoAllelesWithHighestAlleleCounts() { - // first idea: get two alleles with highest AC - int maxAC1 = 0, maxAC2=0,maxAC1ind =0, maxAC2ind = 0; - int i=0; - int[] alleleCounts = new int[this.getAlleles().size()]; - ArrayList alleleArray = new ArrayList(); - for (Allele a:this.getAlleles()) { - int ac = this.getChromosomeCount(a); - if (ac >=maxAC1) { - maxAC1 = ac; - maxAC1ind = i; - } - alleleArray.add(a); - alleleCounts[i++] = ac; - } - // now get second best allele - for (i=0; i < alleleCounts.length; i++) { - if (i == maxAC1ind) - continue; - if (alleleCounts[i] >= maxAC2) { - maxAC2 = alleleCounts[i]; - maxAC2ind = i; - } - } - - Allele alleleA, alleleB; - if (alleleArray.get(maxAC1ind).isReference()) { - alleleA = alleleArray.get(maxAC1ind); - alleleB = alleleArray.get(maxAC2ind); - } - else if (alleleArray.get(maxAC2ind).isReference()) { - alleleA = alleleArray.get(maxAC2ind); - alleleB = alleleArray.get(maxAC1ind); - } else { - alleleA = alleleArray.get(maxAC1ind); - alleleB = alleleArray.get(maxAC2ind); - } - ArrayList a = new ArrayList(); - a.add(alleleA); - a.add(alleleB); - return a; - } public Allele getAltAlleleWithHighestAlleleCount() { // first idea: get two alleles with highest AC Allele best = null; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 89db22bd6..afe48f4f5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -70,7 +70,7 @@ public class VariantContextUtils { * @return VariantContext object */ public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, GenotypeCollection.create(genotypes), negLog10PError, filters, attributes); + return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, GenotypeCollection.copy(genotypes), negLog10PError, filters, attributes); } /** @@ -351,10 +351,9 @@ public class VariantContextUtils { // Genotypes final GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); - for ( final Genotype g : vc.getGenotypes().values() ) { + for ( final Genotype g : vc.getGenotypes() ) { Map genotypeAttributes = subsetAttributes(g.commonInfo, keysToPreserve); - genotypes.put(g.getSampleName(), - new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.getFilters(), + genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.getFilters(), genotypeAttributes, g.isPhased())); } @@ -682,9 +681,9 @@ public class VariantContextUtils { if (!hasNullAlleles) return inputVC; // now we can recreate new genotypes with trimmed alleles - for ( Map.Entry sample : inputVC.getGenotypes().entrySet() ) { + for ( final Genotype genotype : inputVC.getGenotypes() ) { - List originalAlleles = sample.getValue().getAlleles(); + List originalAlleles = genotype.getAlleles(); List trimmedAlleles = new ArrayList(); for ( Allele a : originalAlleles ) { if ( a.isCalled() ) @@ -692,7 +691,7 @@ public class VariantContextUtils { else trimmedAlleles.add(Allele.NO_CALL); } - genotypes.put(sample.getKey(), Genotype.modifyAlleles(sample.getValue(), trimmedAlleles)); + genotypes.add(Genotype.modifyAlleles(genotype, trimmedAlleles)); } return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVC.filtersWereApplied() ? inputVC.getFilters() : null, attributes, new Byte(inputVC.getReference().getBases()[0])); @@ -705,8 +704,8 @@ public class VariantContextUtils { public static GenotypeCollection stripPLs(GenotypeCollection genotypes) { GenotypeCollection newGs = GenotypeCollection.create(genotypes.size()); - for ( Map.Entry g : genotypes.entrySet() ) { - newGs.put(g.getKey(), g.getValue().hasLikelihoods() ? removePLs(g.getValue()) : g.getValue()); + for ( final Genotype g : genotypes ) { + newGs.add(g.hasLikelihoods() ? removePLs(g) : g); } return newGs; @@ -884,9 +883,9 @@ public class VariantContextUtils { } private static void mergeGenotypes(GenotypeCollection mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { - for ( Genotype g : oneVC.getGenotypes().values() ) { + for ( Genotype g : oneVC.getGenotypes() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); - if ( ! mergedGenotypes.containsKey(name) ) { + if ( ! mergedGenotypes.containsSample(name) ) { // only add if the name is new Genotype newG = g; @@ -895,7 +894,7 @@ public class VariantContextUtils { newG = new Genotype(name, alleles, g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased()); } - mergedGenotypes.put(name, newG); + mergedGenotypes.add(newG); } } } @@ -924,15 +923,15 @@ public class VariantContextUtils { // create new Genotype objects GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); - for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { + for ( final Genotype genotype : vc.getGenotypes() ) { List newAlleles = new ArrayList(); - for ( Allele allele : genotype.getValue().getAlleles() ) { + for ( Allele allele : genotype.getAlleles() ) { Allele newAllele = alleleMap.get(allele); if ( newAllele == null ) newAllele = Allele.NO_CALL; newAlleles.add(newAllele); } - newGenotypes.put(genotype.getKey(), Genotype.modifyAlleles(genotype.getValue(), newAlleles)); + newGenotypes.add(Genotype.modifyAlleles(genotype, newAlleles)); } return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes()); @@ -944,13 +943,13 @@ public class VariantContextUtils { return vc; GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); - for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { + for ( final Genotype genotype : vc.getGenotypes() ) { Map attrs = new HashMap(); - for ( Map.Entry attr : genotype.getValue().getAttributes().entrySet() ) { + for ( Map.Entry attr : genotype.getAttributes().entrySet() ) { if ( allowedAttributes.contains(attr.getKey()) ) attrs.put(attr.getKey(), attr.getValue()); } - newGenotypes.put(genotype.getKey(), Genotype.modifyAttributes(genotype.getValue(), attrs)); + newGenotypes.add(Genotype.modifyAttributes(genotype, attrs)); } return VariantContext.modifyGenotypes(vc, newGenotypes); @@ -1023,10 +1022,8 @@ public class VariantContextUtils { MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added GenotypeCollection mergedGenotypes = GenotypeCollection.create(); - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); List site1Alleles = gt1.getAlleles(); List site2Alleles = gt2.getAlleles(); @@ -1052,8 +1049,8 @@ public class VariantContextUtils { if (phaseQual.PQ != null) mergedGtAttribs.put(ReadBackedPhasingWalker.PQ_KEY, phaseQual.PQ); - Genotype mergedGt = new Genotype(sample, mergedAllelesForSample, mergedGQ, mergedGtFilters, mergedGtAttribs, phaseQual.isPhased); - mergedGenotypes.put(sample, mergedGt); + Genotype mergedGt = new Genotype(gt1.getSampleName(), mergedAllelesForSample, mergedGQ, mergedGtFilters, mergedGtAttribs, phaseQual.isPhased); + mergedGenotypes.add(mergedGt); } String mergedName = VariantContextUtils.mergeVariantContextNames(vc1.getSource(), vc2.getSource()); @@ -1197,8 +1194,7 @@ public class VariantContextUtils { } private static boolean allGenotypesAreUnfilteredAndCalled(VariantContext vc) { - for (Map.Entry gtEntry : vc.getGenotypes().entrySet()) { - Genotype gt = gtEntry.getValue(); + for (final Genotype gt : vc.getGenotypes()) { if (gt.isNoCall() || gt.isFiltered()) return false; } @@ -1210,10 +1206,8 @@ public class VariantContextUtils { private static boolean allSamplesAreMergeable(VariantContext vc1, VariantContext vc2) { // Check that each sample's genotype in vc2 is uniquely appendable onto its genotype in vc1: - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); if (!alleleSegregationIsKnown(gt1, gt2)) // can merge if: phased, or if either is a hom return false; @@ -1275,10 +1269,8 @@ public class VariantContextUtils { */ public static boolean someSampleHasDoubleNonReferenceAllele(VariantContext vc1, VariantContext vc2) { - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); List site1Alleles = gt1.getAlleles(); List site2Alleles = gt2.getAlleles(); @@ -1309,10 +1301,8 @@ public class VariantContextUtils { allele2ToAllele1.put(vc2.getReference(), vc1.getReference()); // Note the segregation of the alleles for each sample (and check that it is consistent with the reference and all previous samples). - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); List site1Alleles = gt1.getAlleles(); List site2Alleles = gt2.getAlleles(); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java index 7607049db..181516f33 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java @@ -37,13 +37,14 @@ import org.broadinstitute.sting.gatk.refdata.VariantContextAdaptors; import org.broadinstitute.sting.gatk.walkers.Reference; import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.gatk.walkers.Window; +import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; +import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; +import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.List; +import java.util.*; /** * Test routine for new VariantContext object @@ -93,7 +94,7 @@ public class TestVariantContextWalker extends RodWalker { if ( writer != null && n == 0 ) { if ( ! wroteHeader ) { - writer.writeHeader(VariantContextAdaptors.createVCFHeader(null, vc)); + writer.writeHeader(createVCFHeader(vc)); wroteHeader = true; } @@ -115,6 +116,10 @@ public class TestVariantContextWalker extends RodWalker { } } + private static VCFHeader createVCFHeader(VariantContext vc) { + return new VCFHeader(new HashSet(), vc.getGenotypes().getSampleNamesSorted()); + } + public Integer reduceInit() { return 0; } diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index d698d12a3..f60418155 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -122,7 +122,7 @@ public class VCFWriterUnitTest extends BaseTest { List alleles = new ArrayList(); Set filters = null; Map attributes = new HashMap(); - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypeCollection genotypes = GenotypeCollection.create(header.getGenotypeSamples().size()); alleles.add(Allele.create("-",true)); alleles.add(Allele.create("CC",false)); @@ -133,7 +133,7 @@ public class VCFWriterUnitTest extends BaseTest { gtattributes.put("BB","1"); Genotype gt = new Genotype(name,alleles.subList(1,2),0,null,gtattributes,true); - genotypes.put(name,gt); + genotypes.add(gt); } return new VariantContext("RANDOM",loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, 0, filters, attributes, (byte)'A'); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index f5e485587..b21a32174 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -99,7 +99,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { int start = 10; int stop = start; // alleles.contains(ATC) ? start + 3 : start; return new VariantContext(source, "1", start, stop, alleles, - GenotypeCollection.create(genotypes), 1.0, filters, null, Cref.getBases()[0]); + GenotypeCollection.copy(genotypes), 1.0, filters, null, Cref.getBases()[0]); } // -------------------------------------------------------------------------------- @@ -509,7 +509,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { } // necessary to not overload equals for genotypes - private void assertGenotypesAreMostlyEqual(Map actual, Map expected) { + private void assertGenotypesAreMostlyEqual(GenotypeCollection actual, GenotypeCollection expected) { if (actual == expected) { return; } @@ -522,10 +522,8 @@ public class VariantContextUtilsUnitTest extends BaseTest { Assert.fail("Maps do not have the same size:" + actual.size() + " != " + expected.size()); } - for (Map.Entry entry : actual.entrySet()) { - String key = entry.getKey(); - Genotype value = entry.getValue(); - Genotype expectedValue = expected.get(key); + for (Genotype value : actual) { + Genotype expectedValue = expected.get(value.getSampleName()); Assert.assertEquals(value.alleles, expectedValue.alleles, "Alleles in Genotype aren't equal"); Assert.assertEquals(value.getNegLog10PError(), expectedValue.getNegLog10PError(), "GQ values aren't equal"); @@ -545,7 +543,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { VariantContextUtils.GenotypeMergeType.UNIQUIFY, false, false, "set", false, false); // test genotypes - Assert.assertEquals(merged.getGenotypes().keySet(), new HashSet(Arrays.asList("s1.1", "s1.2"))); + Assert.assertEquals(merged.getSampleNames(), new HashSet(Arrays.asList("s1.1", "s1.2"))); } @Test(expectedExceptions = UserException.class) From 4ff8225d787bbb96cfd472a186068a4d01e66802 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 14 Nov 2011 17:51:41 -0500 Subject: [PATCH 020/113] GenotypeMap -> GenotypeCollection part 3 -- Test code actually builds --- .../gatk/walkers/qc/TestVariantContextWalker.java | 2 +- .../variantcontext/VariantContextBenchmark.java | 12 ++++-------- .../utils/variantcontext/VariantContextUnitTest.java | 12 ++++++------ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java index 181516f33..6bb764f44 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java @@ -117,7 +117,7 @@ public class TestVariantContextWalker extends RodWalker { } private static VCFHeader createVCFHeader(VariantContext vc) { - return new VCFHeader(new HashSet(), vc.getGenotypes().getSampleNamesSorted()); + return new VCFHeader(new HashSet(), vc.getGenotypes().getSampleNamesOrderedByName()); } public Integer reduceInit() { diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index 7ad2c5c1b..06ce61627 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -49,7 +49,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { @Param({"READ", "READ_SUBSET"}) Operation operation; // set automatically by framework - @Param({"OF_GENOTYPES", "OF_SAMPLES"}) + @Param({"OF_SAMPLES"}) SubContextOp subContextOp; // set automatically by framework private String INPUT_STRING; @@ -60,7 +60,6 @@ public class VariantContextBenchmark extends SimpleBenchmark { } public enum SubContextOp { - OF_GENOTYPES, OF_SAMPLES } @@ -91,7 +90,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { codec.readHeader(lineReader); int counter = 0; - List samples = null; + Set samples = null; while (counter++ < linesToRead ) { String line = lineReader.readLine(); if ( line == null ) @@ -99,7 +98,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { VariantContext vc = (VariantContext)codec.decode(line); if ( samples == null ) { - samples = new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake); + samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); } if ( op == Operation.READ_SUBSET) @@ -119,13 +118,10 @@ public class VariantContextBenchmark extends SimpleBenchmark { CaliperMain.main(VariantContextBenchmark.class, args); } - private static final void processOneVC(VariantContext vc, List samples, SubContextOp subop) { + private static final void processOneVC(VariantContext vc, Set samples, SubContextOp subop) { VariantContext sub; switch ( subop ) { - case OF_GENOTYPES: - sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values(), vc.getAlleles()); - break; case OF_SAMPLES: sub = vc.subContextFromSamples(samples, vc.getAlleles()); break; diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 77980267c..8d5505c0e 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -431,12 +431,12 @@ public class VariantContextUnitTest extends BaseTest { Genotype g5 = new Genotype("--", Arrays.asList(del, del), 10); VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop , alleles, Arrays.asList(g1,g2,g3,g4,g5)); - VariantContext vc12 = vc.subContextFromGenotypes(Arrays.asList(g1,g2)); - VariantContext vc1 = vc.subContextFromGenotypes(Arrays.asList(g1)); - VariantContext vc23 = vc.subContextFromGenotypes(Arrays.asList(g2, g3)); - VariantContext vc4 = vc.subContextFromGenotypes(Arrays.asList(g4)); - VariantContext vc14 = vc.subContextFromGenotypes(Arrays.asList(g1, g4)); - VariantContext vc5 = vc.subContextFromGenotypes(Arrays.asList(g5)); + VariantContext vc12 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(),g2.getSampleName()))); + VariantContext vc1 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName()))); + VariantContext vc23 = vc.subContextFromSamples(new HashSet(Arrays.asList(g2.getSampleName(), g3.getSampleName()))); + VariantContext vc4 = vc.subContextFromSamples(new HashSet(Arrays.asList(g4.getSampleName()))); + VariantContext vc14 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g4.getSampleName()))); + VariantContext vc5 = vc.subContextFromSamples(new HashSet(Arrays.asList(g5.getSampleName()))); Assert.assertTrue(vc12.isPolymorphic()); Assert.assertTrue(vc23.isPolymorphic()); From cde829899dd8c2d65edf0efaa9a9757134925c1a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 14 Nov 2011 18:07:41 -0500 Subject: [PATCH 022/113] compress Reduce Read counts bytes by offset compressed the representation of the reduce reads counts by offset results in 17% average compression in final BAM file size. Example compression --> from : 10, 10, 11, 11, 12, 12, 12, 11, 10 to: 10, 0, 1, 1,2, 2, 2, 1, 0 --- .../org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 4 +++- .../org/broadinstitute/sting/utils/ReadUtilsUnitTest.java | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java index 3fe1060dd..6d7c8dad9 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -188,7 +188,9 @@ public class GATKSAMRecord extends BAMRecord { } public final byte getReducedCount(final int i) { - return getReducedReadCounts()[i]; + byte firstCount = getReducedReadCounts()[0]; + byte offsetCount = getReducedReadCounts()[i]; + return (i==0) ? firstCount : (byte) Math.min(firstCount + offsetCount, Byte.MAX_VALUE); } diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java index 46134cd24..53368c339 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java @@ -16,7 +16,8 @@ public class ReadUtilsUnitTest extends BaseTest { GATKSAMRecord read, reducedRead; final static String BASES = "ACTG"; final static String QUALS = "!+5?"; - final private static byte[] REDUCED_READ_COUNTS = new byte[]{10, 20, 30, 40}; + final private static byte[] REDUCED_READ_COUNTS = new byte[]{10, 20, 30, 40}; + final private static byte[] REDUCED_READ_COUNTS_TAG = new byte[]{10, 10, 20, 30}; // just the offsets @BeforeTest public void init() { @@ -29,7 +30,7 @@ public class ReadUtilsUnitTest extends BaseTest { reducedRead = ArtificialSAMUtils.createArtificialRead(header, "reducedRead", 0, 1, BASES.length()); reducedRead.setReadBases(BASES.getBytes()); reducedRead.setBaseQualityString(QUALS); - reducedRead.setAttribute(GATKSAMRecord.REDUCED_READ_CONSENSUS_TAG, REDUCED_READ_COUNTS); + reducedRead.setAttribute(GATKSAMRecord.REDUCED_READ_CONSENSUS_TAG, REDUCED_READ_COUNTS_TAG); } private void testReadBasesAndQuals(GATKSAMRecord read, int expectedStart, int expectedStop) { From 284430d61dafcdf47177040bbf416b7501e47168 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Tue, 15 Nov 2011 00:13:52 -0500 Subject: [PATCH 023/113] Added more basic UnitTests for ReadClipper hardClipByReadCoordinatesWorks hardClipLowQualTailsWorks --- .../utils/clipreads/ReadClipperUnitTest.java | 206 +++++++++--------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java index 0c71a845e..ecb5a6d33 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -30,8 +30,7 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; +import org.testng.annotations.*; import java.util.LinkedList; import java.util.List; @@ -47,7 +46,6 @@ public class ReadClipperUnitTest extends BaseTest { // TODO: Add error messages on failed tests - //int debug = 0; GATKSAMRecord read, expected; @@ -55,73 +53,6 @@ public class ReadClipperUnitTest extends BaseTest { final static String BASES = "ACTG"; final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 - // What the test read looks like - // Ref: 1 2 3 4 5 6 7 8 - // Read: 0 1 2 3 - - - - - // ----------------------------- - // Bases: A C T G - - - - - // Quals: ! + 5 ? - - - - - - @BeforeTest - public void init() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); - read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, BASES.length()); - read.setReadBases(new String(BASES).getBytes()); - read.setBaseQualityString(new String(QUALS)); - - readClipper = new ReadClipper(read); - //logger.warn(read.getCigarString()); - } - - @Test ( enabled = true ) - public void testHardClipBothEndsByReferenceCoordinates() { - init(); - logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); - //int debug = 1; - //Clip whole read - Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(1,1), new GATKSAMRecord(read.getHeader())); - //clip 1 base - expected = readClipper.hardClipBothEndsByReferenceCoordinates(1,4); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); - Assert.assertEquals(expected.getCigarString(), "1H2M1H"); - - } - - @Test ( enabled = false ) // TODO This fails at hardClipCigar and returns a NullPointerException - public void testHardClipByReadCoordinates() { - init(); - logger.warn("Executing testHardClipByReadCoordinates"); - - //Clip whole read - Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); - - //clip 1 base at start - System.out.println(readClipper.read.getCigarString()); - expected = readClipper.hardClipByReadCoordinates(0,0); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); - - //clip 1 base at end - expected = readClipper.hardClipByReadCoordinates(3,3); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); - - //clip 2 bases at start - expected = readClipper.hardClipByReadCoordinates(0,1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); - - //clip 2 bases at end - expected = readClipper.hardClipByReadCoordinates(2,3); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); - - } public void testIfEqual( GATKSAMRecord read, byte[] readBases, String baseQuals, String cigar) { Assert.assertEquals(read.getReadBases(), readBases); @@ -145,6 +76,65 @@ public class ReadClipperUnitTest extends BaseTest { } } + // What the test read looks like + // Ref: 1 2 3 4 5 6 7 8 + // Read: 0 1 2 3 - - - - + // ----------------------------- + // Bases: A C T G - - - - + // Quals: ! + 5 ? - - - - + + @BeforeMethod + public void init() { + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); + read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, BASES.length()); + read.setReadBases(new String(BASES).getBytes()); + read.setBaseQualityString(new String(QUALS)); + + readClipper = new ReadClipper(read); + //logger.warn(read.getCigarString()); + } + + @Test ( enabled = true ) + public void testHardClipBothEndsByReferenceCoordinates() { + + logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); + //int debug = 1; + //Clip whole read + Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(1,1), new GATKSAMRecord(read.getHeader())); + + //clip 1 base + expected = readClipper.hardClipBothEndsByReferenceCoordinates(1,4); + Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); + Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); + Assert.assertEquals(expected.getCigarString(), "1H2M1H"); + + } + + @Test ( enabled = true ) + public void testHardClipByReadCoordinates() { + + logger.warn("Executing testHardClipByReadCoordinates"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); + + List testList = new LinkedList(); + testList.add(new testParameter(0,0,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(3,3,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(0,1,2,4,"2H2M"));//clip 2 bases at start + testList.add(new testParameter(2,3,0,2,"2M2H"));//clip 2 bases at end + + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReadCoordinates(p.inputStart, p.inputStop), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } + + } + @Test ( enabled = true ) public void testHardClipByReferenceCoordinates() { logger.warn("Executing testHardClipByReferenceCoordinates"); @@ -178,8 +168,8 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(4), new GATKSAMRecord(read.getHeader())); List testList = new LinkedList(); - testList.add(new testParameter(1,-1,1,4,"1H3M"));//clip 1 base at start - testList.add(new testParameter(2,-1,2,4,"2H2M"));//clip 2 bases at start + testList.add(new testParameter(1, -1, 1, 4, "1H3M"));//clip 1 base at start + testList.add(new testParameter(2, -1, 2, 4, "2H2M"));//clip 2 bases at start for ( testParameter p : testList ) { init(); @@ -201,8 +191,8 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(1), new GATKSAMRecord(read.getHeader())); List testList = new LinkedList(); - testList.add(new testParameter(-1,4,0,3,"3M1H"));//clip 1 base at end - testList.add(new testParameter(-1,3,0,2,"2M2H"));//clip 2 bases at end + testList.add(new testParameter(-1, 4, 0, 3, "3M1H"));//clip 1 base at end + testList.add(new testParameter(-1, 3, 0, 2, "2M2H"));//clip 2 bases at end for ( testParameter p : testList ) { init(); @@ -215,45 +205,55 @@ public class ReadClipperUnitTest extends BaseTest { } - @Test ( enabled = false ) // TODO This function is returning null reads + @Test ( enabled = true ) // TODO This function is returning null reads public void testHardClipLowQualEnds() { - init(); - logger.warn("Executing testHardClipByReferenceCoordinates"); + logger.warn("Executing testHardClipByReferenceCoordinates"); //Clip whole read Assert.assertEquals(readClipper.hardClipLowQualEnds((byte)64), new GATKSAMRecord(read.getHeader())); - //clip 1 base at start - expected = readClipper.hardClipLowQualEnds((byte)34); - logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); - - //clip 2 bases at start - expected = readClipper.hardClipLowQualEnds((byte)44); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); + List testList = new LinkedList(); + testList.add(new testParameter(1,-1,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(11,-1,2,4,"2H2M"));//clip 2 bases at start + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipLowQualEnds( (byte)p.inputStart ), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } + /* todo find a better way to test lowqual tail clipping on both sides // Reverse Quals sequence - //readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 - //clip 1 base at end - expected = readClipper.hardClipLowQualEnds((byte)'!'); - logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); + testList = new LinkedList(); + testList.add(new testParameter(1,-1,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(11,-1,0,2,"2M2H"));//clip 2 bases at end - //clip 2 bases at end - expected = readClipper.hardClipLowQualEnds((byte)44); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); + for ( testParameter p : testList ) { + init(); + readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipLowQualEnds( (byte)p.inputStart ), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } + */ + } - // revert Qual sequence - readClipper.getRead().setBaseQualityString(QUALS); + public class CigarReadMaker { + + } + + @Test ( enabled = false ) + public void testHardClipSoftClippedBases() { + + // Generate a list of cigars to test + // We will use testParameter in the following way + // Right tail, left tail, } } \ No newline at end of file From 6e1a86bc3eb2a6031a0c40d83a9979c2f9fde97b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 15 Nov 2011 09:21:30 -0500 Subject: [PATCH 024/113] Bug fixes to VariantContext and GenotypeCollection --- .../sting/utils/variantcontext/GenotypeCollection.java | 4 ++-- .../sting/utils/variantcontext/VariantContext.java | 7 +++++-- .../utils/variantcontext/VariantContextUtilsUnitTest.java | 6 ++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java index f12a8e531..260543031 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java @@ -74,7 +74,7 @@ public class GenotypeCollection implements List { // todo -- create constructor (Genotype ... genotypes) public static final GenotypeCollection create(final ArrayList genotypes) { - return new GenotypeCollection(genotypes, false); + return genotypes == null ? NO_GENOTYPES : new GenotypeCollection(genotypes, false); } public static final GenotypeCollection create(final Genotype... genotypes) { @@ -86,7 +86,7 @@ public class GenotypeCollection implements List { } public static final GenotypeCollection copy(final Collection toCopy) { - return create(new ArrayList(toCopy)); + return toCopy == null ? NO_GENOTYPES : create(new ArrayList(toCopy)); } // public static final GenotypeMap create(final Collection genotypes) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index b394517bf..06efd22d3 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -360,8 +360,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati // we need to make this a LinkedHashSet in case the user prefers a given ordering of alleles this.alleles = makeAlleles(alleles); - if ( genotypes == null ) { genotypes = NO_GENOTYPES; } - this.genotypes = genotypes; + if ( genotypes == null ) { + genotypes = NO_GENOTYPES; + } else { + this.genotypes = genotypes.immutable(); + } // cache the REF and ALT alleles int nAlleles = alleles.size(); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index b21a32174..8aded831a 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -249,16 +249,14 @@ public class VariantContextUtilsUnitTest extends BaseTest { final List inputs = new ArrayList(); for ( final String id : cfg.inputs ) { - if ( id.equals(".") ) - snpVC1 = VariantContext.modifyID(snpVC1, id); - inputs.add(snpVC1); + inputs.add(VariantContext.modifyID(snpVC1, id)); } final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, inputs, null, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, VariantContextUtils.GenotypeMergeType.UNSORTED, false, false, "set", false, false); - Assert.assertEquals(merged.getID(), cfg.expected.equals(".") ? null : cfg.expected); + Assert.assertEquals(merged.getID(), cfg.expected); } // -------------------------------------------------------------------------------- From b66556f4a0faf3036275741b07e0bace4df34943 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 15 Nov 2011 09:22:57 -0500 Subject: [PATCH 025/113] Update error message so that it's clear ReadPair Walkers are exceptions --- .../src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index f8e87aa58..2ceb4ab46 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -480,7 +480,7 @@ public class GenomeAnalysisEngine { } } else if (walker instanceof ReadPairWalker) { if(readsDataSource != null && readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.queryname) - throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers can only walk over query name-sorted data. Please resort your input BAM file."); + throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers are exceptions in that they cannot be run on coordinate-sorted BAMs but instead require query name-sorted files. You will need to resort your input BAM file in query name order to use this walker."); if(intervals != null && !intervals.isEmpty()) throw new UserException.CommandLineException("Pairs traversal cannot be used in conjunction with intervals."); From b45d10e6f1fc3443b9cfada54f4554f5f2e6d34f Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 15 Nov 2011 10:23:59 -0500 Subject: [PATCH 026/113] The DP in the FORMAT field (per sample) must also use the representative count or else it's always 1 for reduced reads. --- .../genotyper/GenotypeLikelihoodsCalculationModel.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java index 489e963e8..74c55dbfe 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java @@ -26,7 +26,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; -import org.broadinstitute.sting.commandline.RodBinding; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.AlignmentContextUtils; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; @@ -36,7 +35,6 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Map; @@ -83,8 +81,7 @@ public abstract class GenotypeLikelihoodsCalculationModel implements Cloneable { * @param priors priors to use for GLs * @param GLs hash of sample->GL to fill in * @param alternateAlleleToUse the alternate allele to use, null if not set - * - * @param useBAQedPileup + * @param useBAQedPileup should we use the BAQed pileup or the raw one? * @return genotype likelihoods per sample for AA, AB, BB */ public abstract Allele getLikelihoods(RefMetaDataTracker tracker, @@ -93,13 +90,14 @@ public abstract class GenotypeLikelihoodsCalculationModel implements Cloneable { AlignmentContextUtils.ReadOrientation contextType, GenotypePriors priors, Map GLs, - Allele alternateAlleleToUse, boolean useBAQedPileup); + Allele alternateAlleleToUse, + boolean useBAQedPileup); protected int getFilteredDepth(ReadBackedPileup pileup) { int count = 0; for ( PileupElement p : pileup ) { if ( BaseUtils.isRegularBase( p.getBase() ) ) - count++; + count += p.getRepresentativeCount(); } return count; From 7fada320a9dc36b8ff335dcfbc334b20dd81475b Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 15 Nov 2011 14:53:27 -0500 Subject: [PATCH 028/113] The right fix for this test is just to delete it. --- .../org/broadinstitute/sting/utils/SimpleTimerUnitTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java index 3f5d05e66..7a2696b7b 100755 --- a/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java @@ -41,11 +41,6 @@ public class SimpleTimerUnitTest extends BaseTest { double t6 = t.getElapsedTime(); Assert.assertTrue(t5 >= t4, "Restarted timer elapsed time should be after elapsed time preceding the restart"); Assert.assertTrue(t6 >= t5, "Second elapsed time not after the first in restarted timer"); - - t.stop().start(); - Assert.assertTrue(t.isRunning(), "second started timer isn't running"); - Assert.assertTrue(t.getElapsedTime() >= 0.0, "elapsed time should have been reset"); - Assert.assertTrue(t.getElapsedTime() < t6, "elapsed time isn't less than time before start call"); // we should have effective no elapsed time } private final static void idleLoop() { From 460a51f473e549e4c88eb7db6e6ac56294aaba3b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 15 Nov 2011 14:56:33 -0500 Subject: [PATCH 029/113] ID field now stored in the VariantContext itself, not the attributes --- .../gatk/refdata/VariantContextAdaptors.java | 14 +-- .../annotator/VariantAnnotatorEngine.java | 11 +- .../beagle/BeagleOutputToVCFWalker.java | 4 +- .../walkers/diffengine/VCFDiffableReader.java | 4 +- .../filters/VariantFiltrationWalker.java | 2 +- .../walkers/genotyper/UGCallVariants.java | 2 +- .../genotyper/UnifiedGenotyperEngine.java | 10 +- .../indels/SomaticIndelDetectorWalker.java | 4 +- .../phasing/ReadBackedPhasingWalker.java | 2 +- .../variantutils/LeftAlignVariants.java | 2 +- .../walkers/variantutils/VariantsToTable.java | 2 +- .../walkers/variantutils/VariantsToVCF.java | 8 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 11 +- .../utils/codecs/vcf/StandardVCFWriter.java | 4 +- .../broadinstitute/sting/utils/gcf/GCF.java | 5 +- .../utils/variantcontext/VariantContext.java | 109 +++++++++++++----- .../variantcontext/VariantContextUtils.java | 92 +++------------ .../refdata/RefMetaDataTrackerUnitTest.java | 7 +- .../utils/genotype/vcf/VCFWriterUnitTest.java | 2 +- .../VariantContextUnitTest.java | 77 +++++++------ .../VariantContextUtilsUnitTest.java | 3 +- .../VariantJEXLContextUnitTest.java | 3 +- 22 files changed, 182 insertions(+), 196 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index 164e1f1cb..c5b8b628a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -6,8 +6,10 @@ import org.broad.tribble.annotation.Strand; import org.broad.tribble.dbsnp.OldDbSNPFeature; import org.broad.tribble.gelitext.GeliTextFeature; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; +import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.classloader.PluginManager; import org.broadinstitute.sting.utils.codecs.hapmap.RawHapMapFeature; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; import org.broadinstitute.sting.utils.variantcontext.*; @@ -187,7 +189,6 @@ public class VariantContextAdaptors { } Map attributes = new HashMap(); - attributes.put(VariantContext.ID_KEY, dbsnp.getRsID()); int index = dbsnp.getStart() - ref.getWindow().getStart() - 1; if ( index < 0 ) @@ -195,7 +196,7 @@ public class VariantContextAdaptors { Byte refBaseForIndel = new Byte(ref.getBases()[index]); GenotypeCollection genotypes = null; - VariantContext vc = new VariantContext(name, dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0), alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attributes, refBaseForIndel); + VariantContext vc = new VariantContext(name, dbsnp.getRsID(), dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0), alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attributes, refBaseForIndel); return vc; } else return null; // can't handle anything else @@ -255,8 +256,8 @@ public class VariantContextAdaptors { // add the call to the genotype list, and then use this list to create a VariantContext genotypes.add(call); alleles.add(refAllele); - VariantContext vc = VariantContextUtils.toVC(name, ref.getGenomeLocParser().createGenomeLoc(geli.getChr(),geli.getStart()), alleles, genotypes, geli.getLODBestToReference(), null, attributes); - return vc; + GenomeLoc loc = ref.getGenomeLocParser().createGenomeLoc(geli.getChr(),geli.getStart()); + return new VariantContext(name, VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, geli.getLODBestToReference(), null, attributes); } else return null; // can't handle anything else } @@ -347,13 +348,10 @@ public class VariantContextAdaptors { genotypes.add(g); } - HashMap attrs = new HashMap(1); - attrs.put(VariantContext.ID_KEY, hapmap.getName()); - long end = hapmap.getEnd(); if ( deletionLength > 0 ) end += deletionLength; - VariantContext vc = new VariantContext(name, hapmap.getChr(), hapmap.getStart(), end, alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attrs, refBaseForIndel); + VariantContext vc = new VariantContext(name, hapmap.getName(), hapmap.getChr(), hapmap.getStart(), end, alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null, refBaseForIndel); return vc; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index a0bd69be7..48fcdec10 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -163,11 +163,10 @@ public class VariantAnnotatorEngine { } public VariantContext annotateContext(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - Map infoAnnotations = new LinkedHashMap(vc.getAttributes()); // annotate db occurrences - annotateDBs(tracker, ref, vc, infoAnnotations); + vc = annotateDBs(tracker, ref, vc, infoAnnotations); // annotate expressions where available annotateExpressions(tracker, ref, infoAnnotations); @@ -186,14 +185,14 @@ public class VariantAnnotatorEngine { return VariantContext.modifyGenotypes(annotatedVC, annotateGenotypes(tracker, ref, stratifiedContexts, vc)); } - private void annotateDBs(RefMetaDataTracker tracker, ReferenceContext ref, VariantContext vc, Map infoAnnotations) { + private VariantContext annotateDBs(RefMetaDataTracker tracker, ReferenceContext ref, VariantContext vc, Map infoAnnotations) { for ( Map.Entry, String> dbSet : dbAnnotations.entrySet() ) { if ( dbSet.getValue().equals(VCFConstants.DBSNP_KEY) ) { String rsID = VCFUtils.rsIDOfFirstRealVariant(tracker.getValues(dbSet.getKey(), ref.getLocus()), vc.getType()); infoAnnotations.put(VCFConstants.DBSNP_KEY, rsID != null); // annotate dbsnp id if available and not already there - if ( rsID != null && (!vc.hasID() || vc.getID().equals(VCFConstants.EMPTY_ID_FIELD)) ) - infoAnnotations.put(VariantContext.ID_KEY, rsID); + if ( rsID != null && vc.emptyID() ) + vc = VariantContext.modifyID(vc, rsID); } else { boolean overlapsComp = false; for ( VariantContext comp : tracker.getValues(dbSet.getKey(), ref.getLocus()) ) { @@ -205,6 +204,8 @@ public class VariantAnnotatorEngine { infoAnnotations.put(dbSet.getValue(), overlapsComp); } } + + return vc; } private void annotateExpressions(RefMetaDataTracker tracker, ReferenceContext ref, Map infoAnnotations) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index c03621280..549c26575 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -333,11 +333,11 @@ public class BeagleOutputToVCFWalker extends RodWalker { VariantContext filteredVC; if ( beagleVarCounts > 0 || DONT_FILTER_MONOMORPHIC_SITES ) - filteredVC = new VariantContext("outputvcf", vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), vc_input.getAlleles(), genotypes, vc_input.getNegLog10PError(), vc_input.filtersWereApplied() ? vc_input.getFilters() : null, vc_input.getAttributes()); + filteredVC = new VariantContext("outputvcf", VCFConstants.EMPTY_ID_FIELD, vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), vc_input.getAlleles(), genotypes, vc_input.getNegLog10PError(), vc_input.filtersWereApplied() ? vc_input.getFilters() : null, vc_input.getAttributes()); else { Set removedFilters = vc_input.filtersWereApplied() ? new HashSet(vc_input.getFilters()) : new HashSet(1); removedFilters.add(String.format("BGL_RM_WAS_%s",vc_input.getAlternateAllele(0))); - filteredVC = new VariantContext("outputvcf", vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), new HashSet(Arrays.asList(vc_input.getReference())), genotypes, vc_input.getNegLog10PError(), removedFilters, vc_input.getAttributes()); + filteredVC = new VariantContext("outputvcf", VCFConstants.EMPTY_ID_FIELD, vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), new HashSet(Arrays.asList(vc_input.getReference())), genotypes, vc_input.getNegLog10PError(), removedFilters, vc_input.getAttributes()); } HashMap attributes = new HashMap(filteredVC.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java index 2587b30ef..4b8a703a5 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java @@ -90,7 +90,7 @@ public class VCFDiffableReader implements DiffableReader { // add fields vcRoot.add("CHROM", vc.getChr()); vcRoot.add("POS", vc.getStart()); - vcRoot.add("ID", vc.hasID() ? vc.getID() : VCFConstants.MISSING_VALUE_v4); + vcRoot.add("ID", vc.getID()); vcRoot.add("REF", vc.getReference()); vcRoot.add("ALT", vc.getAlternateAlleles()); vcRoot.add("QUAL", vc.hasNegLog10PError() ? vc.getNegLog10PError() * 10 : VCFConstants.MISSING_VALUE_v4); @@ -98,7 +98,7 @@ public class VCFDiffableReader implements DiffableReader { // add info fields for (Map.Entry attribute : vc.getAttributes().entrySet()) { - if ( ! attribute.getKey().startsWith("_") && ! attribute.getKey().equals(VariantContext.ID_KEY)) + if ( ! attribute.getKey().startsWith("_") ) vcRoot.add(attribute.getKey(), attribute.getValue()); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index 81bff7aaa..6f482b6f2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -327,7 +327,7 @@ public class VariantFiltrationWalker extends RodWalker { if ( genotypes == null ) filteredVC = VariantContext.modifyFilters(vc, filters); else - filteredVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), filters, vc.getAttributes()); + filteredVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), filters, vc.getAttributes()); writer.add(filteredVC); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index 71ad0d75a..00317eec6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -140,7 +140,7 @@ public class UGCallVariants extends RodWalker { VariantContext vc = VCs.get(0); throw new UserException("There is no ALT allele in any of the VCF records passed in at " + vc.getChr() + ":" + vc.getStart()); } - return new VariantContext("VCwithGLs", variantVC.getChr(), variantVC.getStart(), variantVC.getEnd(), variantVC.getAlleles(), genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null); + return new VariantContext("VCwithGLs", VCFConstants.EMPTY_ID_FIELD, variantVC.getChr(), variantVC.getStart(), variantVC.getEnd(), variantVC.getAlleles(), genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null); } private static GenotypeCollection getGenotypesWithGLs(GenotypeCollection genotypes) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index a89545a66..4f87f5eb0 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -229,7 +229,7 @@ public class UnifiedGenotyperEngine { VariantContext vcInput = UnifiedGenotyperEngine.getVCFromAllelesRod(tracker, ref, rawContext.getLocation(), false, logger, UAC.alleles); if ( vcInput == null ) return null; - vc = new VariantContext("UG_call", vcInput.getChr(), vcInput.getStart(), vcInput.getEnd(), vcInput.getAlleles(), VariantContext.NO_NEG_LOG_10PERROR, null, null, ref.getBase()); + vc = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, vcInput.getChr(), vcInput.getStart(), vcInput.getEnd(), vcInput.getAlleles(), VariantContext.NO_NEG_LOG_10PERROR, null, null, ref.getBase()); } else { // deal with bad/non-standard reference bases @@ -238,7 +238,7 @@ public class UnifiedGenotyperEngine { Set alleles = new HashSet(); alleles.add(Allele.create(ref.getBase(), true)); - vc = new VariantContext("UG_call", ref.getLocus().getContig(), ref.getLocus().getStart(), ref.getLocus().getStart(), alleles); + vc = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, ref.getLocus().getContig(), ref.getLocus().getStart(), ref.getLocus().getStart(), alleles); } if ( annotationEngine != null ) { @@ -288,7 +288,7 @@ public class UnifiedGenotyperEngine { int endLoc = calculateEndPos(alleles, refAllele, loc); return new VariantContext("UG_call", - loc.getContig(), + VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), endLoc, alleles, @@ -420,7 +420,7 @@ public class UnifiedGenotyperEngine { myAlleles = new HashSet(1); myAlleles.add(vc.getReference()); } - VariantContext vcCall = new VariantContext("UG_call", loc.getContig(), loc.getStart(), endLoc, + VariantContext vcCall = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), endLoc, myAlleles, genotypes, phredScaledConfidence/10.0, passesCallThreshold(phredScaledConfidence) ? null : filter, attributes, refContext.getBase()); if ( annotationEngine != null ) { @@ -504,7 +504,7 @@ public class UnifiedGenotyperEngine { myAlleles = new HashSet(1); myAlleles.add(vc.getReference()); } - VariantContext vcCall = new VariantContext("UG_call", loc.getContig(), loc.getStart(), endLoc, + VariantContext vcCall = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), endLoc, myAlleles, genotypes, phredScaledConfidence/10.0, passesCallThreshold(phredScaledConfidence) ? null : filter, attributes, vc.getReferenceBaseForIndel()); return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index dda4a7a09..df1d081c6 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -1074,7 +1074,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { filters = new HashSet(); filters.add("NoCall"); } - VariantContext vc = new VariantContext("IGv2_Indel_call", refName, start, stop, alleles, genotypes, + VariantContext vc = new VariantContext("IGv2_Indel_call", VCFConstants.EMPTY_ID_FIELD, refName, start, stop, alleles, genotypes, -1.0 /* log error */, filters, null, refBases[(int)start-1]); vcf.add(vc); } @@ -1171,7 +1171,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { filters.add("TCov"); } - VariantContext vc = new VariantContext("IGv2_Indel_call", refName, start, stop, alleles, genotypes, + VariantContext vc = new VariantContext("IGv2_Indel_call", VCFConstants.EMPTY_ID_FIELD, refName, start, stop, alleles, genotypes, -1.0 /* log error */, filters, attrs, refBases[(int)start-1]); vcf.add(vc); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 82adfe96c..94127438d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -1141,7 +1141,7 @@ public class ReadBackedPhasingWalker extends RodWalker { newGenotypes.add(Genotype.modifyAlleles(genotype, newAlleles)); } - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), refBaseForIndel); + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), refBaseForIndel); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java index 454909634..2d5f0c14f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java @@ -315,7 +315,7 @@ public class VariantsToTable extends RodWalker { getters.put("FILTER", new Getter() { public String get(VariantContext vc) { return vc.isNotFiltered() ? "PASS" : Utils.join(",", vc.getFilters()); } }); - getters.put("ID", new Getter() { public String get(VariantContext vc) { return vc.hasID() ? vc.getID() : "."; } }); + getters.put("ID", new Getter() { public String get(VariantContext vc) { return vc.getID(); } }); getters.put("HET", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getHetCount()); } }); getters.put("HOM-REF", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getHomRefCount()); } }); getters.put("HOM-VAR", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getHomVarCount()); } }); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java index 27eaa7b5d..89322e9f9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java @@ -121,10 +121,8 @@ public class VariantsToVCF extends RodWalker { Collection contexts = getVariantContexts(tracker, ref); for ( VariantContext vc : contexts ) { - Map attrs = new HashMap(vc.getAttributes()); - if ( rsID != null && !vc.hasID() ) { - attrs.put(VariantContext.ID_KEY, rsID); - vc = VariantContext.modifyAttributes(vc, attrs); + if ( rsID != null && vc.emptyID() ) { + vc = VariantContext.modifyID(vc, rsID); } // set the appropriate sample name if necessary @@ -203,7 +201,7 @@ public class VariantsToVCF extends RodWalker { while ( dbsnpIterator.hasNext() ) { GATKFeature feature = dbsnpIterator.next(); VariantContext vc = (VariantContext)feature.getUnderlyingObject(); - if ( vc.hasID() && vc.getID().equals(rsID) ) + if ( vc.getID().equals(rsID) ) return vc; } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index ad14e059b..816863b5e 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -264,7 +264,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, else if ( parts[2].equals(VCFConstants.EMPTY_ID_FIELD) ) id = VCFConstants.EMPTY_ID_FIELD; else - id = new String(parts[2]); + id = parts[2]; String ref = getCachedString(parts[3].toUpperCase()); String alts = getCachedString(parts[4].toUpperCase()); Double qual = parseQual(parts[5]); @@ -274,7 +274,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, // get our alleles, filters, and setup an attribute map List alleles = parseAlleles(ref, alts, lineNo); Set filters = parseFilters(filter); - Map attributes = parseInfo(info, id); + Map attributes = parseInfo(info); // find out our current location, and clip the alleles down to their minimum length int loc = pos; @@ -295,7 +295,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, VariantContext vc = null; try { - vc = new VariantContext(name, contig, pos, loc, alleles, qual, filters, attributes, ref.getBytes()[0]); + vc = new VariantContext(name, id, contig, pos, loc, alleles, qual, filters, attributes, ref.getBytes()[0]); } catch (Exception e) { generateException(e.getMessage()); } @@ -349,10 +349,9 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, /** * parse out the info fields * @param infoField the fields - * @param id the indentifier * @return a mapping of keys to objects */ - private Map parseInfo(String infoField, String id) { + private Map parseInfo(String infoField) { Map attributes = new HashMap(); if ( infoField.length() == 0 ) @@ -391,8 +390,6 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, } } - if ( ! id.equals(VCFConstants.EMPTY_ID_FIELD) ) - attributes.put(VariantContext.ID_KEY, id); return attributes; } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java index d13002642..63c61cfaa 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java @@ -182,7 +182,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { mWriter.write(VCFConstants.FIELD_SEPARATOR); // ID - String ID = vc.hasID() ? vc.getID() : VCFConstants.EMPTY_ID_FIELD; + String ID = vc.getID(); mWriter.write(ID); mWriter.write(VCFConstants.FIELD_SEPARATOR); @@ -227,7 +227,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { Map infoFields = new TreeMap(); for ( Map.Entry field : vc.getAttributes().entrySet() ) { String key = field.getKey(); - if ( key.equals(VariantContext.ID_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) + if ( key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) continue; String outputValue = formatVCFField(field.getValue()); diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index a06fe906f..20cadc469 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -25,6 +25,7 @@ package org.broadinstitute.sting.utils.gcf; import org.broadinstitute.sting.utils.codecs.vcf.StandardVCFWriter; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; @@ -148,7 +149,7 @@ public class GCF { Byte refPadByte = refPad == 0 ? null : refPad; GenotypeCollection genotypes = decodeGenotypes(header); - return new VariantContext(source, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); + return new VariantContext(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); } private GenotypeCollection decodeGenotypes(final GCFHeader header) { @@ -193,7 +194,7 @@ public class GCF { boolean first = true; for ( Map.Entry field : vc.getAttributes().entrySet() ) { String key = field.getKey(); - if ( key.equals(VariantContext.ID_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) + if ( key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) continue; int stringIndex = GCFHeaderBuilder.encodeString(key); String outputValue = StandardVCFWriter.formatVCFField(field.getValue()); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index ed5ceaa69..661ed2bf4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -167,16 +167,19 @@ public class VariantContext implements Feature { // to enable tribble intergrati public final static double NO_NEG_LOG_10PERROR = CommonInfo.NO_NEG_LOG_10PERROR; public final static String UNPARSED_GENOTYPE_MAP_KEY = "_UNPARSED_GENOTYPE_MAP_"; public final static String UNPARSED_GENOTYPE_PARSER_KEY = "_UNPARSED_GENOTYPE_PARSER_"; - public final static String ID_KEY = "ID"; + + @Deprecated // ID is no longer stored in the attributes map + private final static String ID_KEY = "ID"; private final Byte REFERENCE_BASE_FOR_INDEL; public final static Set PASSES_FILTERS = Collections.unmodifiableSet(new LinkedHashSet()); /** The location of this VariantContext */ - protected String contig; - protected long start; - protected long stop; + final protected String contig; + final protected long start; + final protected long stop; + private final String ID; /** The type (cached for performance reasons) of this context */ protected Type type = null; @@ -199,7 +202,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati private Allele ALT = null; // were filters applied? - private boolean filtersWereAppliedToContext; + final private boolean filtersWereAppliedToContext; // --------------------------------------------------------------------------------------------------------- // @@ -223,10 +226,16 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); } + @Deprecated + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel); + } + + /** * the complete constructor. Makes a complete VariantContext from its arguments * @@ -240,8 +249,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes) { + this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); + } + + @Deprecated public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); + this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); } /** @@ -261,8 +275,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true, true); + } + + @Deprecated public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true, true); + this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, negLog10PError, filters, attributes, referenceBaseForIndel); } /** @@ -278,12 +297,19 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + this(source, ID, contig, start, stop, alleles, GenotypeCollection.copy(genotypes), negLog10PError, filters, attributes, null, false, true); } + @Deprecated + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, + GenotypeCollection.copy(genotypes), + negLog10PError, filters, attributes); + } + /** * Create a new variant context without genotypes and no Perror, no filters, and no attributes * @@ -293,8 +319,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param stop the stop reference base (one based) * @param alleles alleles */ + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles) { + this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null, null, false, true); + } + + @Deprecated public VariantContext(String source, String contig, long start, long stop, Collection alleles) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null, null, false, true); + this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles); } /** @@ -307,8 +338,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param alleles alleles * @param genotypes genotypes */ + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, Collection genotypes) { + this(source, ID, contig, start, stop, alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); + } + + @Deprecated public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { - this(source, contig, start, stop, alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); + this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); } /** @@ -317,7 +353,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param other the VariantContext to copy */ public VariantContext(VariantContext other) { - this(other.getSource(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, true); + this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, true); } /** @@ -336,7 +372,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param genotypesAreUnparsed true if the genotypes have not yet been parsed * @param performValidation if true, call validate() as the final step in construction */ - private VariantContext(String source, String contig, long start, long stop, + private VariantContext(String source, String ID, + String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed, @@ -346,6 +383,10 @@ public class VariantContext implements Feature { // to enable tribble intergrati this.start = start; this.stop = stop; + // intern for efficiency. equals calls will generate NPE if ID is inappropriately passed in as null + this.ID = ID.equals(VCFConstants.EMPTY_ID_FIELD) ? VCFConstants.EMPTY_ID_FIELD : ID; + if ( this.ID.equals("") ) throw new IllegalArgumentException("ID field cannot be the empty string"); + if ( !genotypesAreUnparsed && attributes != null ) { if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) attributes.remove(UNPARSED_GENOTYPE_MAP_KEY); @@ -357,13 +398,17 @@ public class VariantContext implements Feature { // to enable tribble intergrati filtersWereAppliedToContext = filters != null; REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; + // todo -- remove me when this check is no longer necessary + if ( this.commonInfo.hasAttribute(ID_KEY) ) + throw new IllegalArgumentException("Trying to create a VariantContext with a ID key. Please use provided constructor argument ID"); + if ( alleles == null ) { throw new IllegalArgumentException("Alleles cannot be null"); } // we need to make this a LinkedHashSet in case the user prefers a given ordering of alleles this.alleles = makeAlleles(alleles); if ( genotypes == null ) { - genotypes = NO_GENOTYPES; + this.genotypes = NO_GENOTYPES; } else { this.genotypes = genotypes.immutable(); } @@ -397,13 +442,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati // --------------------------------------------------------------------------------------------------------- public static VariantContext modifyGenotypes(VariantContext vc, GenotypeCollection genotypes) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), false, false); + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), false, false); modifiedVC.validateGenotypes(); return modifiedVC; } public static VariantContext modifyLocation(VariantContext vc, String chr, int start, int end) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), true, false); + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), true, false); // Since start and end have changed, we need to call both validateAlleles() and validateReferencePadding(), // since those validation routines rely on the values of start and end: @@ -414,31 +459,31 @@ public class VariantContext implements Feature { // to enable tribble intergrati } public static VariantContext modifyFilters(VariantContext vc, Set filters) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); } public static VariantContext modifyAttributes(VariantContext vc, Map attributes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); } public static VariantContext modifyAttribute(VariantContext vc, final String key, final Object value) { Map attributes = new HashMap(vc.getAttributes()); attributes.put(key, value); - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); } public static VariantContext modifyReferencePadding(VariantContext vc, Byte b) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true, false); + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true, false); modifiedVC.validateReferencePadding(); return modifiedVC; } public static VariantContext modifyPErrorFiltersAndAttributes(VariantContext vc, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true, false); + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true, false); } public static VariantContext modifyID(final VariantContext vc, final String id) { - return modifyAttribute(vc, ID_KEY, id); + return new VariantContext(vc.getSource(), id, vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); } // --------------------------------------------------------------------------------------------------------- @@ -494,7 +539,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati // } public VariantContext subContextFromSamples(Set sampleNames, Collection alleles) { - return new VariantContext(getSource(), contig, start, stop, alleles, + return new VariantContext(getSource(), getID(), contig, start, stop, alleles, genotypes.subsetToSamples(sampleNames), getNegLog10PError(), filtersWereApplied() ? getFilters() : null, @@ -504,7 +549,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati public VariantContext subContextFromSamples(Set sampleNames) { GenotypeCollection newGenotypes = genotypes.subsetToSamples(sampleNames); - return new VariantContext(getSource(), contig, start, stop, allelesOfGenotypes(newGenotypes), + return new VariantContext(getSource(), getID(), contig, start, stop, allelesOfGenotypes(newGenotypes), newGenotypes, getNegLog10PError(), filtersWereApplied() ? getFilters() : null, @@ -694,11 +739,15 @@ public class VariantContext implements Feature { // to enable tribble intergrati // --------------------------------------------------------------------------------------------------------- public boolean hasID() { - return commonInfo.hasAttribute(ID_KEY); + return getID() != VCFConstants.EMPTY_ID_FIELD; + } + + public boolean emptyID() { + return ! hasID(); } public String getID() { - return (String)commonInfo.getAttribute(ID_KEY); + return ID; } public boolean hasReferenceBaseForIndel() { @@ -1154,7 +1203,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati } public void validateRSIDs(Set rsIDs) { - if ( rsIDs != null && hasAttribute(VariantContext.ID_KEY) ) { + if ( rsIDs != null && hasID() ) { for ( String id : getID().split(VCFConstants.ID_FIELD_SEPARATOR) ) { if ( id.startsWith("rs") && !rsIDs.contains(id) ) throw new TribbleException.InternalCodecException(String.format("the rsID %s for the record at position %s:%d is not in dbSNP", id, getChr(), getStart())); @@ -1538,7 +1587,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati // Do not change the filter state if filters were not applied to this context Set inputVCFilters = inputVC.filtersWereAppliedToContext ? inputVC.getFilters() : null; - return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); + return new VariantContext(inputVC.getSource(), inputVC.getID(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); } else return inputVC; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index afe48f4f5..acd3ac0d2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -57,55 +57,6 @@ public class VariantContextUtils { engine.setLenient(false); } - /** - * Create a new VariantContext - * - * @param name name - * @param loc location - * @param alleles alleles - * @param genotypes genotypes set - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @return VariantContext object - */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, GenotypeCollection.copy(genotypes), negLog10PError, filters, attributes); - } - - /** - * Create a new variant context without genotypes and no Perror, no filters, and no attributes - * @param name name - * @param loc location - * @param alleles alleles - * @return VariantContext object - */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles) { - return new VariantContext (name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, VariantContext.NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null); - } - - /** - * Create a new variant context without genotypes and no Perror, no filters, and no attributes - * @param name name - * @param loc location - * @param alleles alleles - * @param genotypes genotypes - * @return VariantContext object - */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); - } - - /** - * Copy constructor - * - * @param other the VariantContext to copy - * @return VariantContext object - */ - public static VariantContext toVC(VariantContext other) { - return new VariantContext(other.getSource(), other.getChr(), other.getStart(), other.getEnd(), other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.getFilters(), other.getAttributes()); - } - /** * Update the attributes of the attributes map given the VariantContext to reflect the proper chromosome-based VCF tags * @@ -345,19 +296,15 @@ public class VariantContextUtils { // VC info final Map attributes = subsetAttributes(vc.commonInfo, keysToPreserve); - // this must be done as the ID is stored in the attributes field - // todo -- remove me when ID becomes a first class field in VC - if ( vc.hasID() ) attributes.put(VariantContext.ID_KEY, vc.getID()); - // Genotypes final GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); for ( final Genotype g : vc.getGenotypes() ) { Map genotypeAttributes = subsetAttributes(g.commonInfo, keysToPreserve); genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.getFilters(), - genotypeAttributes, g.isPhased())); + genotypeAttributes, g.isPhased())); } - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.getFilters(), attributes); } @@ -494,7 +441,7 @@ public class VariantContextUtils { // if (vc.hasAttribute(VCFConstants.DEPTH_KEY)) depth += vc.getAttributeAsInt(VCFConstants.DEPTH_KEY, 0); - if ( vc.hasID() && ! vc.getID().equals(VCFConstants.EMPTY_ID_FIELD) ) rsIDs.add(vc.getID()); + if ( vc.hasID() ) rsIDs.add(vc.getID()); if (mergeInfoWithMaxAC && vc.hasAttribute(VCFConstants.ALLELE_COUNT_KEY)) { String rawAlleleCounts = vc.getAttributeAsString(VCFConstants.ALLELE_COUNT_KEY, null); // lets see if the string contains a , separator @@ -587,11 +534,9 @@ public class VariantContextUtils { if ( depth > 0 ) attributes.put(VCFConstants.DEPTH_KEY, String.valueOf(depth)); - if ( ! rsIDs.isEmpty() ) { - attributes.put(VariantContext.ID_KEY, Utils.join(",", rsIDs)); - } + final String ID = rsIDs.isEmpty() ? VCFConstants.EMPTY_ID_FIELD : Utils.join(",", rsIDs); - VariantContext merged = new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, negLog10PError, filters, (mergeInfoWithMaxAC ? attributesWithMaxAC : attributes) ); + VariantContext merged = new VariantContext(name, ID, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, negLog10PError, filters, (mergeInfoWithMaxAC ? attributesWithMaxAC : attributes) ); // Trim the padded bases of all alleles if necessary merged = createVariantContextWithTrimmedAlleles(merged); @@ -694,7 +639,7 @@ public class VariantContextUtils { genotypes.add(Genotype.modifyAlleles(genotype, trimmedAlleles)); } - return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVC.filtersWereApplied() ? inputVC.getFilters() : null, attributes, new Byte(inputVC.getReference().getBases()[0])); + return new VariantContext(inputVC.getSource(), inputVC.getID(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVC.filtersWereApplied() ? inputVC.getFilters() : null, attributes, new Byte(inputVC.getReference().getBases()[0])); } @@ -934,7 +879,7 @@ public class VariantContextUtils { newGenotypes.add(Genotype.modifyAlleles(genotype, newAlleles)); } - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes()); + return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes()); } @@ -1058,7 +1003,14 @@ public class VariantContextUtils { Set mergedFilters = new HashSet(); // Since vc1 and vc2 were unfiltered, the merged record remains unfiltered Map mergedAttribs = VariantContextUtils.mergeVariantContextAttributes(vc1, vc2); - VariantContext mergedVc = new VariantContext(mergedName, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); + // ids + List mergedIDs = new ArrayList(); + if ( vc1.hasID() ) mergedIDs.add(vc1.getID()); + if ( vc2.hasID() ) mergedIDs.add(vc2.getID()); + String mergedID = Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); + + // TODO -- FIX ID + VariantContext mergedVc = new VariantContext(mergedName, mergedID, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); mergedAttribs = new HashMap(mergedVc.getAttributes()); VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); @@ -1154,20 +1106,6 @@ public class VariantContextUtils { mergedAttribs.put(orAttrib, attribVal); } - // Merge ID fields: - String iDVal = null; - for (VariantContext vc : vcList) { - String val = vc.getAttributeAsString(VariantContext.ID_KEY, null); - if (val != null && !val.equals(VCFConstants.EMPTY_ID_FIELD)) { - if (iDVal == null) - iDVal = val; - else - iDVal += VCFConstants.ID_FIELD_SEPARATOR + val; - } - } - if (iDVal != null) - mergedAttribs.put(VariantContext.ID_KEY, iDVal); - return mergedAttribs; } diff --git a/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java index 1e39fd26f..43e93de1a 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java @@ -36,6 +36,7 @@ import org.broadinstitute.sting.gatk.refdata.utils.GATKFeature; import org.broadinstitute.sting.gatk.refdata.utils.RODRecordList; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; @@ -65,9 +66,9 @@ public class RefMetaDataTrackerUnitTest { C = Allele.create("C"); G = Allele.create("G"); T = Allele.create("T"); - AC_SNP = new VariantContext("x", "chr1", START_POS, START_POS, Arrays.asList(A, C)); - AG_SNP = new VariantContext("x", "chr1", START_POS, START_POS, Arrays.asList(A, G)); - AT_SNP = new VariantContext("x", "chr1", START_POS, START_POS, Arrays.asList(A, T)); + AC_SNP = new VariantContext("x", VCFConstants.EMPTY_ID_FIELD, "chr1", START_POS, START_POS, Arrays.asList(A, C)); + AG_SNP = new VariantContext("x", VCFConstants.EMPTY_ID_FIELD, "chr1", START_POS, START_POS, Arrays.asList(A, G)); + AT_SNP = new VariantContext("x", VCFConstants.EMPTY_ID_FIELD, "chr1", START_POS, START_POS, Arrays.asList(A, T)); span10_10 = makeSpan(10, 10); span1_20 = makeSpan(1, 20); span10_20 = makeSpan(10, 20); diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index f60418155..ad38b46e3 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -136,7 +136,7 @@ public class VCFWriterUnitTest extends BaseTest { genotypes.add(gt); } - return new VariantContext("RANDOM",loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, 0, filters, attributes, (byte)'A'); + return new VariantContext("RANDOM", VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, 0, filters, attributes, (byte)'A'); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 8d5505c0e..f63209dc1 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -6,6 +6,7 @@ package org.broadinstitute.sting.utils.variantcontext; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.testng.annotations.BeforeSuite; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -67,68 +68,68 @@ public class VariantContextUnitTest extends BaseTest { // test REF List alleles = Arrays.asList(Tref); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); Assert.assertEquals(vc.getType(), VariantContext.Type.NO_VARIATION); // test SNPs alleles = Arrays.asList(Tref, A); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); Assert.assertEquals(vc.getType(), VariantContext.Type.SNP); alleles = Arrays.asList(Tref, A, C); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); Assert.assertEquals(vc.getType(), VariantContext.Type.SNP); // test MNPs alleles = Arrays.asList(ACref, TA); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles); Assert.assertEquals(vc.getType(), VariantContext.Type.MNP); alleles = Arrays.asList(ATCref, CAT, Allele.create("GGG")); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles); Assert.assertEquals(vc.getType(), VariantContext.Type.MNP); // test INDELs alleles = Arrays.asList(Aref, ATC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(Tref, TA, TC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, AC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, Allele.create("ATCTC")); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); // test MIXED alleles = Arrays.asList(TAref, T, TC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(TAref, T, AC); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(ACref, ATC, AT); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(Aref, T, symbolic); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); // test SYMBOLIC alleles = Arrays.asList(Tref, symbolic); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.SYMBOLIC); } @@ -136,8 +137,8 @@ public class VariantContextUnitTest extends BaseTest { public void testMultipleSNPAlleleOrdering() { final List allelesNaturalOrder = Arrays.asList(Aref, C, T); final List allelesUnnaturalOrder = Arrays.asList(Aref, T, C); - VariantContext naturalVC = new VariantContext("natural", snpLoc, snpLocStart, snpLocStop, allelesNaturalOrder); - VariantContext unnaturalVC = new VariantContext("unnatural", snpLoc, snpLocStart, snpLocStop, allelesUnnaturalOrder); + VariantContext naturalVC = new VariantContext("natural", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, allelesNaturalOrder); + VariantContext unnaturalVC = new VariantContext("unnatural", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, allelesUnnaturalOrder); Assert.assertEquals(new ArrayList(naturalVC.getAlleles()), allelesNaturalOrder); Assert.assertEquals(new ArrayList(unnaturalVC.getAlleles()), allelesUnnaturalOrder); } @@ -146,7 +147,7 @@ public class VariantContextUnitTest extends BaseTest { public void testCreatingSNPVariantContext() { List alleles = Arrays.asList(Aref, T); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); Assert.assertEquals(vc.getChr(), snpLoc); Assert.assertEquals(vc.getStart(), snpLocStart); @@ -173,7 +174,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingRefVariantContext() { List alleles = Arrays.asList(Aref); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); Assert.assertEquals(vc.getChr(), snpLoc); Assert.assertEquals(vc.getStart(), snpLocStart); @@ -199,7 +200,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingDeletionVariantContext() { List alleles = Arrays.asList(ATCref, del); - VariantContext vc = new VariantContext("test", delLoc, delLocStart, delLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getChr(), delLoc); Assert.assertEquals(vc.getStart(), delLocStart); @@ -226,7 +227,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingInsertionVariantContext() { List alleles = Arrays.asList(delRef, ATC); - VariantContext vc = new VariantContext("test", insLoc, insLocStart, insLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getChr(), insLoc); Assert.assertEquals(vc.getStart(), insLocStart); @@ -253,7 +254,7 @@ public class VariantContextUnitTest extends BaseTest { public void testCreatingPartiallyCalledGenotype() { List alleles = Arrays.asList(Aref, C); Genotype g = new Genotype("foo", Arrays.asList(C, Allele.NO_CALL), 10); - VariantContext vc = new VariantContext("test", snpLoc, snpLocStart, snpLocStop, alleles, Arrays.asList(g)); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, alleles, Arrays.asList(g)); Assert.assertTrue(vc.isSNP()); Assert.assertEquals(vc.getNAlleles(), 2); @@ -274,38 +275,38 @@ public class VariantContextUnitTest extends BaseTest { @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs1() { - new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATCref)); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATCref)); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs2() { - new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, del)); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(delRef, del)); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs3() { - new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(del)); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(del)); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs4() { - new VariantContext("test", insLoc, insLocStart, insLocStop, Collections.emptyList()); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Collections.emptyList()); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgsDuplicateAlleles1() { - new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(Aref, T, T)); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(Aref, T, T)); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgsDuplicateAlleles2() { - new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(Aref, A)); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(Aref, A)); } @Test (expectedExceptions = IllegalStateException.class) public void testBadLoc1() { List alleles = Arrays.asList(Aref, T, del); - new VariantContext("test", delLoc, delLocStart, delLocStop, alleles); + new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles); } @Test @@ -316,7 +317,7 @@ public class VariantContextUnitTest extends BaseTest { Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3)); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3)); Assert.assertTrue(vc.hasGenotypes()); Assert.assertFalse(vc.isMonomorphic()); @@ -355,7 +356,7 @@ public class VariantContextUnitTest extends BaseTest { Genotype g5 = new Genotype("dd", Arrays.asList(del, del), 10); Genotype g6 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3, g4, g5, g6)); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3, g4, g5, g6)); Assert.assertTrue(vc.hasGenotypes()); Assert.assertFalse(vc.isMonomorphic()); @@ -380,7 +381,7 @@ public class VariantContextUnitTest extends BaseTest { Genotype g1 = new Genotype("AA1", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AA2", Arrays.asList(Aref, Aref), 10); Genotype g3 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3)); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3)); Assert.assertTrue(vc.hasGenotypes()); Assert.assertTrue(vc.isMonomorphic()); @@ -400,21 +401,21 @@ public class VariantContextUnitTest extends BaseTest { Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2)); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2)); Assert.assertTrue(vc.isNotFiltered()); Assert.assertFalse(vc.isFiltered()); Assert.assertEquals(0, vc.getFilters().size()); Set filters = new HashSet(Arrays.asList("BAD_SNP_BAD!")); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); Assert.assertEquals(1, vc.getFilters().size()); filters = new HashSet(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); - vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); @@ -429,9 +430,9 @@ public class VariantContextUnitTest extends BaseTest { Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); Genotype g4 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); Genotype g5 = new Genotype("--", Arrays.asList(del, del), 10); - VariantContext vc = new VariantContext("test", snpLoc,snpLocStart, snpLocStop , alleles, Arrays.asList(g1,g2,g3,g4,g5)); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop , alleles, Arrays.asList(g1,g2,g3,g4,g5)); - VariantContext vc12 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(),g2.getSampleName()))); + VariantContext vc12 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g2.getSampleName()))); VariantContext vc1 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName()))); VariantContext vc23 = vc.subContextFromSamples(new HashSet(Arrays.asList(g2.getSampleName(), g3.getSampleName()))); VariantContext vc4 = vc.subContextFromSamples(new HashSet(Arrays.asList(g4.getSampleName()))); @@ -514,7 +515,7 @@ public class VariantContextUnitTest extends BaseTest { @Test(dataProvider = "getAlleles") public void testMergeAlleles(GetAllelesTest cfg) { final List altAlleles = cfg.alleles.subList(1, cfg.alleles.size()); - final VariantContext vc = new VariantContext("test", snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + final VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getAlleles(), cfg.alleles, "VC alleles not the same as input alleles"); Assert.assertEquals(vc.getNAlleles(), cfg.alleles.size(), "VC getNAlleles not the same as input alleles size"); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index 8aded831a..dbe131a14 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -26,6 +26,7 @@ package org.broadinstitute.sting.utils.variantcontext; import net.sf.picard.reference.IndexedFastaSequenceFile; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; import org.testng.Assert; @@ -98,7 +99,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { private VariantContext makeVC(String source, List alleles, Collection genotypes, Set filters) { int start = 10; int stop = start; // alleles.contains(ATC) ? start + 3 : start; - return new VariantContext(source, "1", start, stop, alleles, + return new VariantContext(source, VCFConstants.EMPTY_ID_FIELD, "1", start, stop, alleles, GenotypeCollection.copy(genotypes), 1.0, filters, null, Cref.getBases()[0]); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java index b5f6b1b1a..85a2532ef 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java @@ -24,6 +24,7 @@ package org.broadinstitute.sting.utils.variantcontext; import net.sf.samtools.SAMFileHeader; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.testng.Assert; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.GenomeLoc; @@ -143,7 +144,7 @@ public class VariantJEXLContextUnitTest extends BaseTest { private JEXLMap getVarContext() { List alleles = Arrays.asList(Aref, T); - VariantContext vc = new VariantContext("test", snpLoc.getContig(), snpLoc.getStart(), snpLoc.getStop(), alleles); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc.getContig(), snpLoc.getStart(), snpLoc.getStop(), alleles); return new JEXLMap(Arrays.asList(exp),vc); } From 2b2514dad282bd60d44aec107334776fe0fe9063 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 15 Nov 2011 16:14:50 -0500 Subject: [PATCH 030/113] Moved many unused phasing walkers and utilities to archive --- .../sting/gatk/walkers/phasing/BaseArray.java | 2 +- .../walkers/phasing/CardinalityCounter.java | 2 +- .../phasing/CloneableIteratorLinkedList.java | 2 +- .../walkers/phasing}/DisjointSet.java | 4 +- .../sting/gatk/walkers/phasing/Haplotype.java | 2 +- .../gatk/walkers/phasing/MergeMNPsWalker.java | 133 ------ ...eSegregatingAlternateAllelesVCFWriter.java | 45 +- ...ergeSegregatingAlternateAllelesWalker.java | 236 ----------- .../gatk/walkers/phasing/PhasingGraph.java | 4 +- .../walkers/phasing/PhasingGraphEdge.java | 2 +- .../gatk/walkers/phasing/PhasingRead.java | 2 +- .../gatk/walkers/phasing/PhasingUtils.java | 387 ++++++++++++++++++ .../phasing/PreciseNonNegativeDouble.java | 2 +- .../phasing/ReadBackedPhasingWalker.java | 3 +- .../sting/gatk/walkers/phasing/ReadBase.java | 2 +- .../walkers/phasing/ReadBasesAtPosition.java | 2 +- .../walkers/phasing/RefSeqDataParser.java | 189 --------- .../gatk/walkers/phasing/SNPallelePair.java | 2 +- .../sting/gatk/walkers/phasing/WriteVCF.java | 34 -- .../variantcontext/VariantContextUtils.java | 341 +-------------- 20 files changed, 411 insertions(+), 985 deletions(-) rename public/java/src/org/broadinstitute/sting/{utils => gatk/walkers/phasing}/DisjointSet.java (97%) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsWalker.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesWalker.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/RefSeqDataParser.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/WriteVCF.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/BaseArray.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/BaseArray.java index 5a32479ab..54838b55e 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/BaseArray.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/BaseArray.java @@ -29,7 +29,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -public abstract class BaseArray implements Comparable { +abstract class BaseArray implements Comparable { protected Byte[] bases; public BaseArray(byte[] bases) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CardinalityCounter.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CardinalityCounter.java index 06f4d3ab2..45a1ab04c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CardinalityCounter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CardinalityCounter.java @@ -30,7 +30,7 @@ import java.util.Iterator; /* * CardinalityCounter object allows user to iterate over all assignment of arbitrary-cardinality variables. */ -public class CardinalityCounter implements Iterator, Iterable { +class CardinalityCounter implements Iterator, Iterable { private int[] cards; private int[] valList; private boolean hasNext; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CloneableIteratorLinkedList.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CloneableIteratorLinkedList.java index 4ec940f4f..e88a7104d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CloneableIteratorLinkedList.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/CloneableIteratorLinkedList.java @@ -30,7 +30,7 @@ import java.util.NoSuchElementException; It is UNIQUE in the fact that its iterator (BidirectionalIterator) can be cloned to save the current pointer for a later time (while the original iterator can continue to iterate). */ -public class CloneableIteratorLinkedList { +class CloneableIteratorLinkedList { private CloneableIteratorDoublyLinkedNode first; private CloneableIteratorDoublyLinkedNode last; private int size; diff --git a/public/java/src/org/broadinstitute/sting/utils/DisjointSet.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/DisjointSet.java similarity index 97% rename from public/java/src/org/broadinstitute/sting/utils/DisjointSet.java rename to public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/DisjointSet.java index 52c18e6d6..c054af5d6 100644 --- a/public/java/src/org/broadinstitute/sting/utils/DisjointSet.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/DisjointSet.java @@ -21,13 +21,13 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils; +package org.broadinstitute.sting.gatk.walkers.phasing; import java.util.Collection; import java.util.Set; import java.util.TreeSet; -public class DisjointSet { +class DisjointSet { private ItemNode[] nodes; public DisjointSet(int numItems) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/Haplotype.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/Haplotype.java index 3c20a311e..61d5a725e 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/Haplotype.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/Haplotype.java @@ -27,7 +27,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import java.util.Arrays; -public class Haplotype extends BaseArray implements Cloneable { +class Haplotype extends BaseArray implements Cloneable { public Haplotype(byte[] bases) { super(bases); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsWalker.java deleted file mode 100644 index 809772c05..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsWalker.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.phasing; - -import org.broadinstitute.sting.commandline.Argument; -import org.broadinstitute.sting.commandline.Input; -import org.broadinstitute.sting.commandline.Output; -import org.broadinstitute.sting.commandline.RodBinding; -import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; -import org.broadinstitute.sting.gatk.walkers.*; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; -import org.broadinstitute.sting.utils.codecs.vcf.VCFUtils; -import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; - -import java.util.*; - -import static org.broadinstitute.sting.utils.codecs.vcf.VCFUtils.getVCFHeadersFromRods; - - -/** - * Walks along all variant ROD loci, and merges consecutive sites if they segregate in all samples in the ROD. - */ -@Allows(value = {DataSource.REFERENCE}) -@Requires(value = {DataSource.REFERENCE}) -@By(DataSource.REFERENCE_ORDERED_DATA) - -public class MergeMNPsWalker extends RodWalker { - - @Output(doc = "File to which variants should be written", required = true) - protected VCFWriter writer = null; - private MergeSegregatingAlternateAllelesVCFWriter vcMergerWriter = null; - - @Argument(fullName = "maxGenomicDistanceForMNP", shortName = "maxDistMNP", doc = "The maximum reference-genome distance between consecutive heterozygous sites to permit merging phased VCF records into a MNP record; [default:1]", required = false) - protected int maxGenomicDistanceForMNP = 1; - - @Input(fullName="variant", shortName = "V", doc="Select variants from this VCF file", required=true) - public RodBinding variants; - - public void initialize() { - initializeVcfWriter(); - } - - private void initializeVcfWriter() { - // false <-> don't take control of writer, since didn't create it: - vcMergerWriter = new MergeSegregatingAlternateAllelesVCFWriter(writer, getToolkit().getGenomeLocParser(), getToolkit().getArguments().referenceFile, maxGenomicDistanceForMNP, logger, false); - writer = null; // so it can't be accessed directly [i.e., not through vcMergerWriter] - - // setup the header fields: - Set hInfo = new HashSet(); - hInfo.addAll(VCFUtils.getHeaderFields(getToolkit())); - hInfo.add(new VCFHeaderLine("reference", getToolkit().getArguments().referenceFile.getName())); - - Map rodNameToHeader = getVCFHeadersFromRods(getToolkit(), Arrays.asList(variants.getName())); - vcMergerWriter.writeHeader(new VCFHeader(hInfo, new TreeSet(rodNameToHeader.get(variants.getName()).getGenotypeSamples()))); - } - - public boolean generateExtendedEvents() { - return false; - } - - public Integer reduceInit() { - return 0; - } - - /** - * For each site, send it to be (possibly) merged with previously observed sites. - * - * @param tracker the meta-data tracker - * @param ref the reference base - * @param context the context for the given locus - * @return dummy Integer - */ - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if (tracker == null) - return null; - - for (VariantContext vc : tracker.getValues(variants, context.getLocation())) - writeVCF(vc); - - return 0; - } - - private void writeVCF(VariantContext vc) { - WriteVCF.writeVCF(vc, vcMergerWriter, logger); - } - - public Integer reduce(Integer result, Integer total) { - if (result == null) - return total; - - return total + result; - } - - /** - * Release any VariantContexts not yet processed. - * - * @param result Empty for now... - */ - public void onTraversalDone(Integer result) { - vcMergerWriter.close(); - - System.out.println("Number of successive pairs of records: " + vcMergerWriter.getNumRecordsAttemptToMerge()); - System.out.println("Number of potentially merged records (" + vcMergerWriter.getVcMergeRule() + "): " + vcMergerWriter.getNumRecordsSatisfyingMergeRule()); - System.out.println("Number of records merged ("+ vcMergerWriter.getAlleleMergeRule() + "): " + vcMergerWriter.getNumMergedRecords()); - System.out.println(vcMergerWriter.getAltAlleleStats()); - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java index 5ae034b0a..b935600b2 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, The Broad Institute + * Copyright (c) 2011, The Broad Institute * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -44,7 +44,7 @@ import java.util.*; // Streams in VariantContext objects and streams out VariantContexts produced by merging phased segregating polymorphisms into MNP VariantContexts -public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { +class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { private VCFWriter innerWriter; private GenomeLocParser genomeLocParser; @@ -52,7 +52,7 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { private ReferenceSequenceFile referenceFileForMNPmerging; private VariantContextMergeRule vcMergeRule; - private VariantContextUtils.AlleleMergeRule alleleMergeRule; + private PhasingUtils.AlleleMergeRule alleleMergeRule; private String useSingleSample = null; @@ -71,7 +71,7 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { // Should we call innerWriter.close() in close() private boolean takeOwnershipOfInner; - public MergeSegregatingAlternateAllelesVCFWriter(VCFWriter innerWriter, GenomeLocParser genomeLocParser, File referenceFile, VariantContextMergeRule vcMergeRule, VariantContextUtils.AlleleMergeRule alleleMergeRule, String singleSample, boolean emitOnlyMergedRecords, Logger logger, boolean takeOwnershipOfInner, boolean trackAltAlleleStats) { + public MergeSegregatingAlternateAllelesVCFWriter(VCFWriter innerWriter, GenomeLocParser genomeLocParser, File referenceFile, VariantContextMergeRule vcMergeRule, PhasingUtils.AlleleMergeRule alleleMergeRule, String singleSample, boolean emitOnlyMergedRecords, Logger logger, boolean takeOwnershipOfInner, boolean trackAltAlleleStats) { this.innerWriter = innerWriter; this.genomeLocParser = genomeLocParser; try { @@ -179,7 +179,7 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { boolean mergedRecords = false; if (shouldAttemptToMerge) { numRecordsSatisfyingMergeRule++; - VariantContext mergedVc = VariantContextUtils.mergeIntoMNP(genomeLocParser, vcfrWaitingToMerge.vc, vc, referenceFileForMNPmerging, alleleMergeRule); + VariantContext mergedVc = PhasingUtils.mergeIntoMNP(genomeLocParser, vcfrWaitingToMerge.vc, vc, referenceFileForMNPmerging, alleleMergeRule); if (mergedVc != null) { mergedRecords = true; @@ -218,26 +218,6 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { filteredVcfrList.clear(); } - public int getNumRecordsAttemptToMerge() { - return numRecordsAttemptToMerge; - } - - public int getNumRecordsSatisfyingMergeRule() { - return numRecordsSatisfyingMergeRule; - } - - public int getNumMergedRecords() { - return numMergedRecords; - } - - public VariantContextMergeRule getVcMergeRule() { - return vcMergeRule; - } - - public VariantContextUtils.AlleleMergeRule getAlleleMergeRule() { - return alleleMergeRule; - } - /** * Gets a string representation of this object. * @@ -248,13 +228,6 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { return getClass().getName(); } - public String getAltAlleleStats() { - if (altAlleleStats == null) - return ""; - - return "\n" + altAlleleStats.toString(); - } - private static class VCFRecord { public VariantContext vc; public boolean resultedFromMerge; @@ -373,7 +346,7 @@ public class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { if (shouldAttemptToMerge) { aas.numSuccessiveGenotypesAttemptedToBeMerged++; - if (!VariantContextUtils.alleleSegregationIsKnown(gt1, gt2)) { + if (!PhasingUtils.alleleSegregationIsKnown(gt1, gt2)) { aas.segregationUnknown++; logger.debug("Unknown segregation of alleles [not phased] for " + samp + " at " + VariantContextUtils.getLocation(genomeLocParser, vc1) + ", " + VariantContextUtils.getLocation(genomeLocParser, vc2)); } @@ -498,9 +471,9 @@ class DistanceMergeRule extends VariantContextMergeRule { } -class ExistsDoubleAltAlleleMergeRule extends VariantContextUtils.AlleleMergeRule { +class ExistsDoubleAltAlleleMergeRule extends PhasingUtils.AlleleMergeRule { public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2) { - return VariantContextUtils.someSampleHasDoubleNonReferenceAllele(vc1, vc2); + return PhasingUtils.someSampleHasDoubleNonReferenceAllele(vc1, vc2); } public String toString() { @@ -515,7 +488,7 @@ class SegregatingMNPmergeAllelesRule extends ExistsDoubleAltAlleleMergeRule { public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2) { // Must be interesting AND consistent: - return super.allelesShouldBeMerged(vc1, vc2) && VariantContextUtils.doubleAllelesSegregatePerfectlyAmongSamples(vc1, vc2); + return super.allelesShouldBeMerged(vc1, vc2) && PhasingUtils.doubleAllelesSegregatePerfectlyAmongSamples(vc1, vc2); } public String toString() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesWalker.java deleted file mode 100644 index 96d5c471f..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesWalker.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.phasing; - -import org.broadinstitute.sting.commandline.*; -import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; -import org.broadinstitute.sting.gatk.walkers.*; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; -import org.broadinstitute.sting.utils.codecs.vcf.VCFUtils; -import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; -import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; - -import java.util.*; - -import static org.broadinstitute.sting.utils.codecs.vcf.VCFUtils.getVCFHeadersFromRods; - -/** - * Walks along all variant ROD loci, and merges consecutive sites if some sample has segregating alt alleles in the ROD. - */ -@Allows(value = {DataSource.REFERENCE}) -@Requires(value = {DataSource.REFERENCE}) -@By(DataSource.REFERENCE_ORDERED_DATA) - -public class MergeSegregatingAlternateAllelesWalker extends RodWalker { - - @Output(doc = "File to which variants should be written", required = true) - protected VCFWriter writer = null; - private MergeSegregatingAlternateAllelesVCFWriter vcMergerWriter = null; - - @Argument(fullName = "maxGenomicDistance", shortName = "maxDist", doc = "The maximum reference-genome distance between consecutive heterozygous sites to permit merging phased VCF records; [default:1]", required = false) - protected int maxGenomicDistance = 1; - - @Argument(fullName = "useSingleSample", shortName = "useSample", doc = "Only output genotypes for the single sample given; [default:use all samples]", required = false) - protected String useSingleSample = null; - - @Hidden - @Argument(fullName = "emitOnlyMergedRecords", shortName = "emitOnlyMerged", doc = "Only output records that resulted from merging [For DEBUGGING purposes only - DO NOT USE, since it disregards the semantics of '|' as 'phased relative to previous non-filtered VC']; [default:false]", required = false) - protected boolean emitOnlyMergedRecords = false; - - @Argument(fullName = "disablePrintAltAlleleStats", shortName = "noAlleleStats", doc = "Should the print-out of alternate allele statistics be disabled?; [default:false]", required = false) - protected boolean disablePrintAlternateAlleleStatistics = false; - - public final static String IGNORE_REFSEQ = "IGNORE"; - public final static String UNION_REFSEQ = "UNION"; - public final static String INTERSECT_REFSEQ = "INTERSECT"; - - @Argument(fullName = "mergeBasedOnRefSeqAnnotation", shortName = "mergeBasedOnRefSeqAnnotation", doc = "'Should merging be performed if two sites lie on the same RefSeq sequence in the INFO field {" + IGNORE_REFSEQ + ", " + UNION_REFSEQ + ", " + INTERSECT_REFSEQ + "}; [default:" + IGNORE_REFSEQ + "]", required = false) - protected String mergeBasedOnRefSeqAnnotation = IGNORE_REFSEQ; - - @Argument(fullName = "dontRequireSomeSampleHasDoubleAltAllele", shortName = "dontRequireSomeSampleHasDoubleAltAllele", doc = "Should the requirement, that SUCCESSIVE records to be merged have at least one sample with a double alternate allele, be relaxed?; [default:false]", required = false) - protected boolean dontRequireSomeSampleHasDoubleAltAllele = false; - - @Input(fullName="variant", shortName = "V", doc="Select variants from this VCF file", required=true) - public RodBinding variants; - - public void initialize() { - initializeVcfWriter(); - } - - private void initializeVcfWriter() { - GenomeLocParser genomeLocParser = getToolkit().getGenomeLocParser(); - - VariantContextMergeRule vcMergeRule; - if (mergeBasedOnRefSeqAnnotation.equals(IGNORE_REFSEQ)) - vcMergeRule = new DistanceMergeRule(maxGenomicDistance, genomeLocParser); - else - vcMergeRule = new SameGenePlusWithinDistanceMergeRule(maxGenomicDistance, genomeLocParser, mergeBasedOnRefSeqAnnotation); - - VariantContextUtils.AlleleMergeRule alleleMergeRule; - if (dontRequireSomeSampleHasDoubleAltAllele) // if a pair of VariantContext passes the vcMergeRule, then always merge them if there is a trailing prefix of polymorphisms (i.e., upstream polymorphic site): - alleleMergeRule = new PrefixPolymorphismMergeAllelesRule(); - else - alleleMergeRule = new ExistsDoubleAltAlleleMergeRule(); - - // false <-> don't take control of writer, since didn't create it: - vcMergerWriter = new MergeSegregatingAlternateAllelesVCFWriter(writer, genomeLocParser, getToolkit().getArguments().referenceFile, vcMergeRule, alleleMergeRule, useSingleSample, emitOnlyMergedRecords, logger, false, !disablePrintAlternateAlleleStatistics); - writer = null; // so it can't be accessed directly [i.e., not through vcMergerWriter] - - // setup the header fields: - Set hInfo = new HashSet(); - hInfo.addAll(VCFUtils.getHeaderFields(getToolkit())); - hInfo.add(new VCFHeaderLine("reference", getToolkit().getArguments().referenceFile.getName())); - - Map rodNameToHeader = getVCFHeadersFromRods(getToolkit(), Arrays.asList(variants.getName())); - vcMergerWriter.writeHeader(new VCFHeader(hInfo, new TreeSet(rodNameToHeader.get(variants.getName()).getGenotypeSamples()))); - } - - public boolean generateExtendedEvents() { - return false; - } - - public Integer reduceInit() { - return 0; - } - - /** - * For each site, send it to be (possibly) merged with previously observed sites. - * - * @param tracker the meta-data tracker - * @param ref the reference base - * @param context the context for the given locus - * @return dummy Integer - */ - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if (tracker == null) - return null; - - for (VariantContext vc : tracker.getValues(variants, context.getLocation())) - writeVCF(vc); - - return 0; - } - - private void writeVCF(VariantContext vc) { - WriteVCF.writeVCF(vc, vcMergerWriter, logger); - } - - public Integer reduce(Integer result, Integer total) { - if (result == null) - return total; - - return total + result; - } - - /** - * Release any VariantContexts not yet processed. - * - * @param result Empty for now... - */ - public void onTraversalDone(Integer result) { - vcMergerWriter.close(); - - if (useSingleSample != null) - System.out.println("Only considered single sample: " + useSingleSample); - - System.out.println("Number of successive pairs of records: " + vcMergerWriter.getNumRecordsAttemptToMerge()); - System.out.println("Number of potentially merged records (" + vcMergerWriter.getVcMergeRule() + "): " + vcMergerWriter.getNumRecordsSatisfyingMergeRule()); - System.out.println("Number of records merged ("+ vcMergerWriter.getAlleleMergeRule() + "): " + vcMergerWriter.getNumMergedRecords()); - System.out.println(vcMergerWriter.getAltAlleleStats()); - } -} - - -enum MergeBasedOnRefSeqAnnotation { - UNION_WITH_DIST, INTERSECT_WITH_DIST -} - -class SameGenePlusWithinDistanceMergeRule extends DistanceMergeRule { - private MergeBasedOnRefSeqAnnotation mergeBasedOnRefSeqAnnotation; - - public SameGenePlusWithinDistanceMergeRule(int maxGenomicDistanceForMNP, GenomeLocParser genomeLocParser, String mergeBasedOnRefSeqAnnotation) { - super(maxGenomicDistanceForMNP, genomeLocParser); - - if (mergeBasedOnRefSeqAnnotation.equals(MergeSegregatingAlternateAllelesWalker.UNION_REFSEQ)) - this.mergeBasedOnRefSeqAnnotation = MergeBasedOnRefSeqAnnotation.UNION_WITH_DIST; - else if (mergeBasedOnRefSeqAnnotation.equals(MergeSegregatingAlternateAllelesWalker.INTERSECT_REFSEQ)) - this.mergeBasedOnRefSeqAnnotation = MergeBasedOnRefSeqAnnotation.INTERSECT_WITH_DIST; - else - throw new UserException("Must provide " + MergeSegregatingAlternateAllelesWalker.IGNORE_REFSEQ + ", " + MergeSegregatingAlternateAllelesWalker.UNION_REFSEQ + ", or " + MergeSegregatingAlternateAllelesWalker.INTERSECT_REFSEQ + " as argument to mergeBasedOnRefSeqAnnotation!"); - } - - public boolean shouldAttemptToMerge(VariantContext vc1, VariantContext vc2) { - boolean withinDistance = super.shouldAttemptToMerge(vc1, vc2); - - if (mergeBasedOnRefSeqAnnotation == MergeBasedOnRefSeqAnnotation.UNION_WITH_DIST) - return withinDistance || sameGene(vc1, vc2); - else // mergeBasedOnRefSeqAnnotation == MergeBasedOnRefSeqAnnotation.INTERSECT_WITH_DIST - return withinDistance && sameGene(vc1, vc2); - } - - private boolean sameGene(VariantContext vc1, VariantContext vc2) { - Set names_vc1 = RefSeqDataParser.getRefSeqNames(vc1); - Set names_vc2 = RefSeqDataParser.getRefSeqNames(vc2); - names_vc1.retainAll(names_vc2); - - if (!names_vc1.isEmpty()) - return true; - - // Check refseq.name2: - Set names2_vc1 = RefSeqDataParser.getRefSeqNames(vc1, true); - Set names2_vc2 = RefSeqDataParser.getRefSeqNames(vc2, true); - names2_vc1.retainAll(names2_vc2); - - return !names2_vc1.isEmpty(); - } - - public String toString() { - return super.toString() + " " + (mergeBasedOnRefSeqAnnotation == MergeBasedOnRefSeqAnnotation.UNION_WITH_DIST ? "OR" : "AND") + " on the same gene"; - } - - public Map addToMergedAttributes(VariantContext vc1, VariantContext vc2) { - Map addedAttribs = super.addToMergedAttributes(vc1, vc2); - addedAttribs.putAll(RefSeqDataParser.getMergedRefSeqNameAttributes(vc1, vc2)); - return addedAttribs; - } -} - - - -class PrefixPolymorphismMergeAllelesRule extends VariantContextUtils.AlleleMergeRule { - public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2) { - return vc1.isPolymorphic(); - } - - public String toString() { - return super.toString() + ", there exists a polymorphism at the start of the merged allele"; - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraph.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraph.java index fe2792475..8f980ad72 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraph.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraph.java @@ -23,12 +23,10 @@ */ package org.broadinstitute.sting.gatk.walkers.phasing; -import org.broadinstitute.sting.utils.DisjointSet; - import java.util.*; // Represents an undirected graph with no self-edges: -public class PhasingGraph implements Iterable { +class PhasingGraph implements Iterable { private Neighbors[] adj; public PhasingGraph(int numVertices) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraphEdge.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraphEdge.java index 56197a85f..053b09439 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraphEdge.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingGraphEdge.java @@ -26,7 +26,7 @@ package org.broadinstitute.sting.gatk.walkers.phasing; /* Edge class for PhasingGraph */ -public class PhasingGraphEdge implements Comparable { +class PhasingGraphEdge implements Comparable { protected int v1; protected int v2; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingRead.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingRead.java index 63fb33295..a95b13d68 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingRead.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingRead.java @@ -29,7 +29,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import java.util.Arrays; -public class PhasingRead extends BaseArray { +class PhasingRead extends BaseArray { private PreciseNonNegativeDouble mappingProb; // the probability that this read is mapped correctly private PreciseNonNegativeDouble[] baseProbs; // the probabilities that the base identities are CORRECT private PreciseNonNegativeDouble[] baseErrorProbs; // the probabilities that the base identities are INCORRECT diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java new file mode 100644 index 000000000..8b5455e50 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.walkers.phasing; + +import net.sf.picard.reference.ReferenceSequenceFile; +import net.sf.samtools.util.StringUtil; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.variantcontext.*; + +import java.util.*; + +/** + * [Short one sentence description of this walker] + *

+ *

+ * [Functionality of this walker] + *

+ *

+ *

Input

+ *

+ * [Input description] + *

+ *

+ *

Output

+ *

+ * [Output description] + *

+ *

+ *

Examples

+ *
+ *    java
+ *      -jar GenomeAnalysisTK.jar
+ *      -T $WalkerName
+ *  
+ * + * @author Your Name + * @since Date created + */ +class PhasingUtils { + static VariantContext mergeIntoMNP(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile, AlleleMergeRule alleleMergeRule) { + if (!mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2)) + return null; + + // Check that it's logically possible to merge the VCs: + if (!allSamplesAreMergeable(vc1, vc2)) + return null; + + // Check if there's a "point" in merging the VCs (e.g., annotations could be changed) + if (!alleleMergeRule.allelesShouldBeMerged(vc1, vc2)) + return null; + + return reallyMergeIntoMNP(vc1, vc2, referenceFile); + } + + static VariantContext reallyMergeIntoMNP(VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile) { + int startInter = vc1.getEnd() + 1; + int endInter = vc2.getStart() - 1; + byte[] intermediateBases = null; + if (startInter <= endInter) { + intermediateBases = referenceFile.getSubsequenceAt(vc1.getChr(), startInter, endInter).getBases(); + StringUtil.toUpperCase(intermediateBases); + } + MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added + + GenotypeCollection mergedGenotypes = GenotypeCollection.create(); + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); + + List site1Alleles = gt1.getAlleles(); + List site2Alleles = gt2.getAlleles(); + + List mergedAllelesForSample = new LinkedList(); + + /* NOTE: Since merged alleles are added to mergedAllelesForSample in the SAME order as in the input VC records, + we preserve phase information (if any) relative to whatever precedes vc1: + */ + Iterator all2It = site2Alleles.iterator(); + for (Allele all1 : site1Alleles) { + Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() + + Allele mergedAllele = mergeData.ensureMergedAllele(all1, all2); + mergedAllelesForSample.add(mergedAllele); + } + + double mergedGQ = Math.max(gt1.getNegLog10PError(), gt2.getNegLog10PError()); + Set mergedGtFilters = new HashSet(); // Since gt1 and gt2 were unfiltered, the Genotype remains unfiltered + + Map mergedGtAttribs = new HashMap(); + PhaseAndQuality phaseQual = calcPhaseForMergedGenotypes(gt1, gt2); + if (phaseQual.PQ != null) + mergedGtAttribs.put(ReadBackedPhasingWalker.PQ_KEY, phaseQual.PQ); + + Genotype mergedGt = new Genotype(gt1.getSampleName(), mergedAllelesForSample, mergedGQ, mergedGtFilters, mergedGtAttribs, phaseQual.isPhased); + mergedGenotypes.add(mergedGt); + } + + String mergedName = mergeVariantContextNames(vc1.getSource(), vc2.getSource()); + double mergedNegLog10PError = Math.max(vc1.getNegLog10PError(), vc2.getNegLog10PError()); + Set mergedFilters = new HashSet(); // Since vc1 and vc2 were unfiltered, the merged record remains unfiltered + Map mergedAttribs = mergeVariantContextAttributes(vc1, vc2); + + // ids + List mergedIDs = new ArrayList(); + if ( vc1.hasID() ) mergedIDs.add(vc1.getID()); + if ( vc2.hasID() ) mergedIDs.add(vc2.getID()); + String mergedID = Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); + + // TODO -- FIX ID + VariantContext mergedVc = new VariantContext(mergedName, mergedID, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); + + mergedAttribs = new HashMap(mergedVc.getAttributes()); + VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); + mergedVc = VariantContext.modifyAttributes(mergedVc, mergedAttribs); + + return mergedVc; + } + + static String mergeVariantContextNames(String name1, String name2) { + return name1 + "_" + name2; + } + + static Map mergeVariantContextAttributes(VariantContext vc1, VariantContext vc2) { + Map mergedAttribs = new HashMap(); + + List vcList = new LinkedList(); + vcList.add(vc1); + vcList.add(vc2); + + String[] MERGE_OR_ATTRIBS = {VCFConstants.DBSNP_KEY}; + for (String orAttrib : MERGE_OR_ATTRIBS) { + boolean attribVal = false; + for (VariantContext vc : vcList) { + attribVal = vc.getAttributeAsBoolean(orAttrib, false); + if (attribVal) // already true, so no reason to continue: + break; + } + mergedAttribs.put(orAttrib, attribVal); + } + + return mergedAttribs; + } + + static boolean mergeIntoMNPvalidationCheck(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2) { + GenomeLoc loc1 = VariantContextUtils.getLocation(genomeLocParser, vc1); + GenomeLoc loc2 = VariantContextUtils.getLocation(genomeLocParser, vc2); + + if (!loc1.onSameContig(loc2)) + throw new ReviewedStingException("Can only merge vc1, vc2 if on the same chromosome"); + + if (!loc1.isBefore(loc2)) + throw new ReviewedStingException("Can only merge if vc1 is BEFORE vc2"); + + if (vc1.isFiltered() || vc2.isFiltered()) + return false; + + if (!vc1.getSampleNames().equals(vc2.getSampleNames())) // vc1, vc2 refer to different sample sets + return false; + + if (!allGenotypesAreUnfilteredAndCalled(vc1) || !allGenotypesAreUnfilteredAndCalled(vc2)) + return false; + + return true; + } + + static boolean allGenotypesAreUnfilteredAndCalled(VariantContext vc) { + for (final Genotype gt : vc.getGenotypes()) { + if (gt.isNoCall() || gt.isFiltered()) + return false; + } + + return true; + } + + static boolean allSamplesAreMergeable(VariantContext vc1, VariantContext vc2) { + // Check that each sample's genotype in vc2 is uniquely appendable onto its genotype in vc1: + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); + + if (!alleleSegregationIsKnown(gt1, gt2)) // can merge if: phased, or if either is a hom + return false; + } + + return true; + } + + static boolean alleleSegregationIsKnown(Genotype gt1, Genotype gt2) { + if (gt1.getPloidy() != gt2.getPloidy()) + return false; + + /* If gt2 is phased or hom, then could even be MERGED with gt1 [This is standard]. + + HOWEVER, EVEN if this is not the case, but gt1.isHom(), + it is trivially known that each of gt2's alleles segregate with the single allele type present in gt1. + */ + return (gt2.isPhased() || gt2.isHom() || gt1.isHom()); + } + + static PhaseAndQuality calcPhaseForMergedGenotypes(Genotype gt1, Genotype gt2) { + if (gt2.isPhased() || gt2.isHom()) + return new PhaseAndQuality(gt1); // maintain the phase of gt1 + + if (!gt1.isHom()) + throw new ReviewedStingException("alleleSegregationIsKnown(gt1, gt2) implies: gt2.genotypesArePhased() || gt2.isHom() || gt1.isHom()"); + + /* We're dealing with: gt1.isHom(), gt2.isHet(), !gt2.genotypesArePhased(); so, the merged (het) Genotype is not phased relative to the previous Genotype + + For example, if we're merging the third Genotype with the second one: + 0/1 + 1|1 + 0/1 + + Then, we want to output: + 0/1 + 1/2 + */ + return new PhaseAndQuality(gt2); // maintain the phase of gt2 [since !gt2.genotypesArePhased()] + } + + static boolean someSampleHasDoubleNonReferenceAllele(VariantContext vc1, VariantContext vc2) { + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); + + List site1Alleles = gt1.getAlleles(); + List site2Alleles = gt2.getAlleles(); + + Iterator all2It = site2Alleles.iterator(); + for (Allele all1 : site1Alleles) { + Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() + + if (all1.isNonReference() && all2.isNonReference()) // corresponding alleles are alternate + return true; + } + } + + return false; + } + + static boolean doubleAllelesSegregatePerfectlyAmongSamples(VariantContext vc1, VariantContext vc2) { + // Check that Alleles at vc1 and at vc2 always segregate together in all samples (including reference): + Map allele1ToAllele2 = new HashMap(); + Map allele2ToAllele1 = new HashMap(); + + // Note the segregation of the alleles for the reference genome: + allele1ToAllele2.put(vc1.getReference(), vc2.getReference()); + allele2ToAllele1.put(vc2.getReference(), vc1.getReference()); + + // Note the segregation of the alleles for each sample (and check that it is consistent with the reference and all previous samples). + for (final Genotype gt1 : vc1.getGenotypes()) { + Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); + + List site1Alleles = gt1.getAlleles(); + List site2Alleles = gt2.getAlleles(); + + Iterator all2It = site2Alleles.iterator(); + for (Allele all1 : site1Alleles) { + Allele all2 = all2It.next(); + + Allele all1To2 = allele1ToAllele2.get(all1); + if (all1To2 == null) + allele1ToAllele2.put(all1, all2); + else if (!all1To2.equals(all2)) // all1 segregates with two different alleles at site 2 + return false; + + Allele all2To1 = allele2ToAllele1.get(all2); + if (all2To1 == null) + allele2ToAllele1.put(all2, all1); + else if (!all2To1.equals(all1)) // all2 segregates with two different alleles at site 1 + return false; + } + } + + return true; + } + + abstract static class AlleleMergeRule { + // vc1, vc2 are ONLY passed to allelesShouldBeMerged() if mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2) AND allSamplesAreMergeable(vc1, vc2): + abstract public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2); + + public String toString() { + return "all samples are mergeable"; + } + } + + static class AlleleOneAndTwo { + private Allele all1; + private Allele all2; + + public AlleleOneAndTwo(Allele all1, Allele all2) { + this.all1 = all1; + this.all2 = all2; + } + + public int hashCode() { + return all1.hashCode() + all2.hashCode(); + } + + public boolean equals(Object other) { + if (!(other instanceof AlleleOneAndTwo)) + return false; + + AlleleOneAndTwo otherAot = (AlleleOneAndTwo) other; + return (this.all1.equals(otherAot.all1) && this.all2.equals(otherAot.all2)); + } + } + + static class MergedAllelesData { + private Map mergedAlleles; + private byte[] intermediateBases; + private int intermediateLength; + + public MergedAllelesData(byte[] intermediateBases, VariantContext vc1, VariantContext vc2) { + this.mergedAlleles = new HashMap(); // implemented equals() and hashCode() for AlleleOneAndTwo + this.intermediateBases = intermediateBases; + this.intermediateLength = this.intermediateBases != null ? this.intermediateBases.length : 0; + + this.ensureMergedAllele(vc1.getReference(), vc2.getReference(), true); + } + + public Allele ensureMergedAllele(Allele all1, Allele all2) { + return ensureMergedAllele(all1, all2, false); // false <-> since even if all1+all2 = reference, it was already created in the constructor + } + + private Allele ensureMergedAllele(Allele all1, Allele all2, boolean creatingReferenceForFirstTime) { + AlleleOneAndTwo all12 = new AlleleOneAndTwo(all1, all2); + Allele mergedAllele = mergedAlleles.get(all12); + + if (mergedAllele == null) { + byte[] bases1 = all1.getBases(); + byte[] bases2 = all2.getBases(); + + byte[] mergedBases = new byte[bases1.length + intermediateLength + bases2.length]; + System.arraycopy(bases1, 0, mergedBases, 0, bases1.length); + if (intermediateBases != null) + System.arraycopy(intermediateBases, 0, mergedBases, bases1.length, intermediateLength); + System.arraycopy(bases2, 0, mergedBases, bases1.length + intermediateLength, bases2.length); + + mergedAllele = Allele.create(mergedBases, creatingReferenceForFirstTime); + mergedAlleles.put(all12, mergedAllele); + } + + return mergedAllele; + } + + public Set getAllMergedAlleles() { + return new HashSet(mergedAlleles.values()); + } + } + + static class PhaseAndQuality { + public boolean isPhased; + public Double PQ = null; + + public PhaseAndQuality(Genotype gt) { + this.isPhased = gt.isPhased(); + if (this.isPhased) { + this.PQ = gt.getAttributeAsDouble(ReadBackedPhasingWalker.PQ_KEY, -1); + if ( this.PQ == -1 ) this.PQ = null; + } + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PreciseNonNegativeDouble.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PreciseNonNegativeDouble.java index 99446705e..b68739b48 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PreciseNonNegativeDouble.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PreciseNonNegativeDouble.java @@ -26,7 +26,7 @@ package org.broadinstitute.sting.gatk.walkers.phasing; /* PreciseNonNegativeDouble permits arithmetic operations on NON-NEGATIVE double values with precision (prevents underflow by representing in log10 space). */ -public class PreciseNonNegativeDouble implements Comparable { +class PreciseNonNegativeDouble implements Comparable { private static final double EQUALS_THRESH = 1e-6; private static final double INFINITY = Double.POSITIVE_INFINITY; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 94127438d..8cc0d8856 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -34,7 +34,6 @@ import org.broadinstitute.sting.gatk.filters.MappingQualityZeroFilter; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.utils.BaseUtils; -import org.broadinstitute.sting.utils.DisjointSet; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.HasGenomeLocation; import org.broadinstitute.sting.utils.codecs.vcf.*; @@ -1052,7 +1051,7 @@ public class ReadBackedPhasingWalker extends RodWalker { +class ReadBasesAtPosition implements Iterable { // list of: private LinkedList bases; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/RefSeqDataParser.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/RefSeqDataParser.java deleted file mode 100644 index f94140814..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/RefSeqDataParser.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.phasing; - -import org.broadinstitute.sting.utils.variantcontext.VariantContext; - -import java.util.*; - -/* Some methods for extracting RefSeq-related data from annotated VCF INFO fields: - */ -public class RefSeqDataParser { - private static String REFSEQ_PREFIX = "refseq."; - - private static String NUM_RECORDS_KEY = REFSEQ_PREFIX + "numMatchingRecords"; - private static String NAME_KEY = REFSEQ_PREFIX + "name"; - private static String NAME2_KEY = REFSEQ_PREFIX + "name2"; - - private static String[] NAME_KEYS = {NAME_KEY, NAME2_KEY}; - - private static Map getRefSeqEntriesToNames(VariantContext vc, boolean getName2) { - String nameKeyToUse = getName2 ? NAME2_KEY : NAME_KEY; - String nameKeyToUseMultiplePrefix = nameKeyToUse + "_"; - - Map entriesToNames = new HashMap(); - int numRecords = vc.getAttributeAsInt(NUM_RECORDS_KEY, -1); - if (numRecords != -1) { - boolean done = false; - - if (numRecords == 1) { // Check if perhaps the single record doesn't end with "_1": - String name = vc.getAttributeAsString(nameKeyToUse, null); - if (name != null) { - entriesToNames.put(nameKeyToUse, name); - done = true; - } - } - - if (!done) { - for (int i = 1; i <= numRecords; i++) { - String key = nameKeyToUseMultiplePrefix + i; - String name = vc.getAttributeAsString(key, null); - if (name != null) - entriesToNames.put(key, name); - } - } - } - else { // no entry with the # of records: - String name = vc.getAttributeAsString(nameKeyToUse, null); - if (name != null) { - entriesToNames.put(nameKeyToUse, name); - } - else { // Check all INFO fields for a match (if there are multiple entries): - for (Map.Entry entry : vc.getAttributes().entrySet()) { - String key = entry.getKey(); - if (key.startsWith(nameKeyToUseMultiplePrefix)) - entriesToNames.put(key, entry.getValue().toString()); - } - } - } - return entriesToNames; - } - - private static Map getRefSeqEntriesToNames(VariantContext vc) { - return getRefSeqEntriesToNames(vc, false); - } - - public static Set getRefSeqNames(VariantContext vc, boolean getName2) { - return new TreeSet(getRefSeqEntriesToNames(vc, getName2).values()); - } - - public static Set getRefSeqNames(VariantContext vc) { - return getRefSeqNames(vc, false); - } - - public static Map getMergedRefSeqNameAttributes(VariantContext vc1, VariantContext vc2) { - Map refSeqNameAttribs = new HashMap(); - - Map entriesMap1 = getAllRefSeqEntriesByName(vc1); - Map entriesMap2 = getAllRefSeqEntriesByName(vc2); - - Set commonNames = entriesMap1.keySet(); - commonNames.retainAll(entriesMap2.keySet()); - boolean addSuffix = commonNames.size() > 1; - int nextCount = 1; - - for (String name : commonNames) { - RefSeqEntry refseq1 = entriesMap1.get(name); - RefSeqEntry refseq2 = entriesMap2.get(name); - - String keySuffix = ""; - if (addSuffix) - keySuffix = "_" + nextCount; - - boolean added = false; - for (String key : NAME_KEYS) { - Object obj1 = refseq1.info.get(key); - Object obj2 = refseq2.info.get(key); - if (obj1 != null && obj2 != null && obj1.equals(obj2)) { - added = true; - String useKey = key + keySuffix; - refSeqNameAttribs.put(useKey, obj1); - } - } - if (added) - nextCount++; - } - int totalCount = nextCount - 1; // since incremented count one extra time - if (totalCount > 1) - refSeqNameAttribs.put(NUM_RECORDS_KEY, totalCount); - - return refSeqNameAttribs; - } - - public static Map removeRefSeqAttributes(Map attributes) { - Map removedRefSeqAttributes = new HashMap(attributes); - - Iterator> attrIt = removedRefSeqAttributes.entrySet().iterator(); - while (attrIt.hasNext()) { - String key = attrIt.next().getKey(); - if (key.startsWith(REFSEQ_PREFIX)) - attrIt.remove(); - } - - return removedRefSeqAttributes; - } - - private static Map getAllRefSeqEntriesByName(VariantContext vc) { - Map nameToEntries = new TreeMap(); - - List allEntries = getAllRefSeqEntries(vc); - for (RefSeqEntry entry : allEntries) { - Object name = entry.info.get(NAME_KEY); - if (name != null) - nameToEntries.put(name.toString(), entry); - } - - return nameToEntries; - } - - // Returns a List of SEPARATE Map for EACH RefSeq annotation (i.e., each gene), stripping out the "_1", "_2", etc. - private static List getAllRefSeqEntries(VariantContext vc) { - List allRefSeq = new LinkedList(); - - for (Map.Entry entryToName : getRefSeqEntriesToNames(vc).entrySet()) { - String entry = entryToName.getKey(); - String entrySuffix = entry.replaceFirst(NAME_KEY, ""); - allRefSeq.add(new RefSeqEntry(vc, entrySuffix)); - } - - return allRefSeq; - } - - private static class RefSeqEntry { - public Map info; - - public RefSeqEntry(VariantContext vc, String entrySuffix) { - this.info = new HashMap(); - - for (Map.Entry attribEntry : vc.getAttributes().entrySet()) { - String key = attribEntry.getKey(); - if (key.startsWith(REFSEQ_PREFIX) && key.endsWith(entrySuffix)) { - String genericKey = key.replaceAll(entrySuffix, ""); - this.info.put(genericKey, attribEntry.getValue()); - } - } - } - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/SNPallelePair.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/SNPallelePair.java index 153c4a23f..6a2381e29 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/SNPallelePair.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/SNPallelePair.java @@ -28,7 +28,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -public class SNPallelePair extends AllelePair { +class SNPallelePair extends AllelePair { public SNPallelePair(Genotype gt) { super(gt); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/WriteVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/WriteVCF.java deleted file mode 100644 index c10eaa2da..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/WriteVCF.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ -package org.broadinstitute.sting.gatk.walkers.phasing; - -import org.apache.log4j.Logger; -import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; - -public class WriteVCF { - public static void writeVCF(VariantContext vc, VCFWriter writer, Logger logger) { - writer.add(vc); - } -} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index acd3ac0d2..161800009 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -25,13 +25,10 @@ package org.broadinstitute.sting.utils.variantcontext; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; -import net.sf.picard.reference.ReferenceSequenceFile; -import net.sf.samtools.util.StringUtil; import org.apache.commons.jexl2.Expression; import org.apache.commons.jexl2.JexlEngine; import org.apache.log4j.Logger; import org.broad.tribble.util.popgen.HardyWeinbergCalculation; -import org.broadinstitute.sting.gatk.walkers.phasing.ReadBackedPhasingWalker; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -929,340 +926,4 @@ public class VariantContextUtils { public static final GenomeLoc getLocation(GenomeLocParser genomeLocParser,VariantContext vc) { return genomeLocParser.createGenomeLoc(vc.getChr(), vc.getStart(), vc.getEnd(), true); } - - public abstract static class AlleleMergeRule { - // vc1, vc2 are ONLY passed to allelesShouldBeMerged() if mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2) AND allSamplesAreMergeable(vc1, vc2): - abstract public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2); - - public String toString() { - return "all samples are mergeable"; - } - } - - // NOTE: returns null if vc1 and vc2 are not merged into a single MNP record - - public static VariantContext mergeIntoMNP(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile, AlleleMergeRule alleleMergeRule) { - if (!mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2)) - return null; - - // Check that it's logically possible to merge the VCs: - if (!allSamplesAreMergeable(vc1, vc2)) - return null; - - // Check if there's a "point" in merging the VCs (e.g., annotations could be changed) - if (!alleleMergeRule.allelesShouldBeMerged(vc1, vc2)) - return null; - - return reallyMergeIntoMNP(vc1, vc2, referenceFile); - } - - private static VariantContext reallyMergeIntoMNP(VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile) { - int startInter = vc1.getEnd() + 1; - int endInter = vc2.getStart() - 1; - byte[] intermediateBases = null; - if (startInter <= endInter) { - intermediateBases = referenceFile.getSubsequenceAt(vc1.getChr(), startInter, endInter).getBases(); - StringUtil.toUpperCase(intermediateBases); - } - MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added - - GenotypeCollection mergedGenotypes = GenotypeCollection.create(); - for (final Genotype gt1 : vc1.getGenotypes()) { - Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); - - List site1Alleles = gt1.getAlleles(); - List site2Alleles = gt2.getAlleles(); - - List mergedAllelesForSample = new LinkedList(); - - /* NOTE: Since merged alleles are added to mergedAllelesForSample in the SAME order as in the input VC records, - we preserve phase information (if any) relative to whatever precedes vc1: - */ - Iterator all2It = site2Alleles.iterator(); - for (Allele all1 : site1Alleles) { - Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() - - Allele mergedAllele = mergeData.ensureMergedAllele(all1, all2); - mergedAllelesForSample.add(mergedAllele); - } - - double mergedGQ = Math.max(gt1.getNegLog10PError(), gt2.getNegLog10PError()); - Set mergedGtFilters = new HashSet(); // Since gt1 and gt2 were unfiltered, the Genotype remains unfiltered - - Map mergedGtAttribs = new HashMap(); - PhaseAndQuality phaseQual = calcPhaseForMergedGenotypes(gt1, gt2); - if (phaseQual.PQ != null) - mergedGtAttribs.put(ReadBackedPhasingWalker.PQ_KEY, phaseQual.PQ); - - Genotype mergedGt = new Genotype(gt1.getSampleName(), mergedAllelesForSample, mergedGQ, mergedGtFilters, mergedGtAttribs, phaseQual.isPhased); - mergedGenotypes.add(mergedGt); - } - - String mergedName = VariantContextUtils.mergeVariantContextNames(vc1.getSource(), vc2.getSource()); - double mergedNegLog10PError = Math.max(vc1.getNegLog10PError(), vc2.getNegLog10PError()); - Set mergedFilters = new HashSet(); // Since vc1 and vc2 were unfiltered, the merged record remains unfiltered - Map mergedAttribs = VariantContextUtils.mergeVariantContextAttributes(vc1, vc2); - - // ids - List mergedIDs = new ArrayList(); - if ( vc1.hasID() ) mergedIDs.add(vc1.getID()); - if ( vc2.hasID() ) mergedIDs.add(vc2.getID()); - String mergedID = Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); - - // TODO -- FIX ID - VariantContext mergedVc = new VariantContext(mergedName, mergedID, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); - - mergedAttribs = new HashMap(mergedVc.getAttributes()); - VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); - mergedVc = VariantContext.modifyAttributes(mergedVc, mergedAttribs); - - return mergedVc; - } - - private static class AlleleOneAndTwo { - private Allele all1; - private Allele all2; - - public AlleleOneAndTwo(Allele all1, Allele all2) { - this.all1 = all1; - this.all2 = all2; - } - - public int hashCode() { - return all1.hashCode() + all2.hashCode(); - } - - public boolean equals(Object other) { - if (!(other instanceof AlleleOneAndTwo)) - return false; - - AlleleOneAndTwo otherAot = (AlleleOneAndTwo) other; - return (this.all1.equals(otherAot.all1) && this.all2.equals(otherAot.all2)); - } - } - - private static class MergedAllelesData { - private Map mergedAlleles; - private byte[] intermediateBases; - private int intermediateLength; - - public MergedAllelesData(byte[] intermediateBases, VariantContext vc1, VariantContext vc2) { - this.mergedAlleles = new HashMap(); // implemented equals() and hashCode() for AlleleOneAndTwo - this.intermediateBases = intermediateBases; - this.intermediateLength = this.intermediateBases != null ? this.intermediateBases.length : 0; - - this.ensureMergedAllele(vc1.getReference(), vc2.getReference(), true); - } - - public Allele ensureMergedAllele(Allele all1, Allele all2) { - return ensureMergedAllele(all1, all2, false); // false <-> since even if all1+all2 = reference, it was already created in the constructor - } - - private Allele ensureMergedAllele(Allele all1, Allele all2, boolean creatingReferenceForFirstTime) { - AlleleOneAndTwo all12 = new AlleleOneAndTwo(all1, all2); - Allele mergedAllele = mergedAlleles.get(all12); - - if (mergedAllele == null) { - byte[] bases1 = all1.getBases(); - byte[] bases2 = all2.getBases(); - - byte[] mergedBases = new byte[bases1.length + intermediateLength + bases2.length]; - System.arraycopy(bases1, 0, mergedBases, 0, bases1.length); - if (intermediateBases != null) - System.arraycopy(intermediateBases, 0, mergedBases, bases1.length, intermediateLength); - System.arraycopy(bases2, 0, mergedBases, bases1.length + intermediateLength, bases2.length); - - mergedAllele = Allele.create(mergedBases, creatingReferenceForFirstTime); - mergedAlleles.put(all12, mergedAllele); - } - - return mergedAllele; - } - - public Set getAllMergedAlleles() { - return new HashSet(mergedAlleles.values()); - } - } - - private static String mergeVariantContextNames(String name1, String name2) { - return name1 + "_" + name2; - } - - private static Map mergeVariantContextAttributes(VariantContext vc1, VariantContext vc2) { - Map mergedAttribs = new HashMap(); - - List vcList = new LinkedList(); - vcList.add(vc1); - vcList.add(vc2); - - String[] MERGE_OR_ATTRIBS = {VCFConstants.DBSNP_KEY}; - for (String orAttrib : MERGE_OR_ATTRIBS) { - boolean attribVal = false; - for (VariantContext vc : vcList) { - attribVal = vc.getAttributeAsBoolean(orAttrib, false); - if (attribVal) // already true, so no reason to continue: - break; - } - mergedAttribs.put(orAttrib, attribVal); - } - - return mergedAttribs; - } - - private static boolean mergeIntoMNPvalidationCheck(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2) { - GenomeLoc loc1 = VariantContextUtils.getLocation(genomeLocParser, vc1); - GenomeLoc loc2 = VariantContextUtils.getLocation(genomeLocParser, vc2); - - if (!loc1.onSameContig(loc2)) - throw new ReviewedStingException("Can only merge vc1, vc2 if on the same chromosome"); - - if (!loc1.isBefore(loc2)) - throw new ReviewedStingException("Can only merge if vc1 is BEFORE vc2"); - - if (vc1.isFiltered() || vc2.isFiltered()) - return false; - - if (!vc1.getSampleNames().equals(vc2.getSampleNames())) // vc1, vc2 refer to different sample sets - return false; - - if (!allGenotypesAreUnfilteredAndCalled(vc1) || !allGenotypesAreUnfilteredAndCalled(vc2)) - return false; - - return true; - } - - private static boolean allGenotypesAreUnfilteredAndCalled(VariantContext vc) { - for (final Genotype gt : vc.getGenotypes()) { - if (gt.isNoCall() || gt.isFiltered()) - return false; - } - - return true; - } - - // Assumes that vc1 and vc2 were already checked to have the same sample names: - - private static boolean allSamplesAreMergeable(VariantContext vc1, VariantContext vc2) { - // Check that each sample's genotype in vc2 is uniquely appendable onto its genotype in vc1: - for (final Genotype gt1 : vc1.getGenotypes()) { - Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); - - if (!alleleSegregationIsKnown(gt1, gt2)) // can merge if: phased, or if either is a hom - return false; - } - - return true; - } - - public static boolean alleleSegregationIsKnown(Genotype gt1, Genotype gt2) { - if (gt1.getPloidy() != gt2.getPloidy()) - return false; - - /* If gt2 is phased or hom, then could even be MERGED with gt1 [This is standard]. - - HOWEVER, EVEN if this is not the case, but gt1.isHom(), - it is trivially known that each of gt2's alleles segregate with the single allele type present in gt1. - */ - return (gt2.isPhased() || gt2.isHom() || gt1.isHom()); - } - - private static class PhaseAndQuality { - public boolean isPhased; - public Double PQ = null; - - public PhaseAndQuality(Genotype gt) { - this.isPhased = gt.isPhased(); - if (this.isPhased) { - this.PQ = gt.getAttributeAsDouble(ReadBackedPhasingWalker.PQ_KEY, -1); - if ( this.PQ == -1 ) this.PQ = null; - } - } - } - - // Assumes that alleleSegregationIsKnown(gt1, gt2): - - private static PhaseAndQuality calcPhaseForMergedGenotypes(Genotype gt1, Genotype gt2) { - if (gt2.isPhased() || gt2.isHom()) - return new PhaseAndQuality(gt1); // maintain the phase of gt1 - - if (!gt1.isHom()) - throw new ReviewedStingException("alleleSegregationIsKnown(gt1, gt2) implies: gt2.genotypesArePhased() || gt2.isHom() || gt1.isHom()"); - - /* We're dealing with: gt1.isHom(), gt2.isHet(), !gt2.genotypesArePhased(); so, the merged (het) Genotype is not phased relative to the previous Genotype - - For example, if we're merging the third Genotype with the second one: - 0/1 - 1|1 - 0/1 - - Then, we want to output: - 0/1 - 1/2 - */ - return new PhaseAndQuality(gt2); // maintain the phase of gt2 [since !gt2.genotypesArePhased()] - } - - /* Checks if any sample has a MNP of ALT alleles (segregating together): - [Assumes that vc1 and vc2 were already checked to have the same sample names && allSamplesAreMergeable(vc1, vc2)] - */ - - public static boolean someSampleHasDoubleNonReferenceAllele(VariantContext vc1, VariantContext vc2) { - for (final Genotype gt1 : vc1.getGenotypes()) { - Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); - - List site1Alleles = gt1.getAlleles(); - List site2Alleles = gt2.getAlleles(); - - Iterator all2It = site2Alleles.iterator(); - for (Allele all1 : site1Alleles) { - Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() - - if (all1.isNonReference() && all2.isNonReference()) // corresponding alleles are alternate - return true; - } - } - - return false; - } - - /* Checks if all samples are consistent in their haplotypes: - [Assumes that vc1 and vc2 were already checked to have the same sample names && allSamplesAreMergeable(vc1, vc2)] - */ - - public static boolean doubleAllelesSegregatePerfectlyAmongSamples(VariantContext vc1, VariantContext vc2) { - // Check that Alleles at vc1 and at vc2 always segregate together in all samples (including reference): - Map allele1ToAllele2 = new HashMap(); - Map allele2ToAllele1 = new HashMap(); - - // Note the segregation of the alleles for the reference genome: - allele1ToAllele2.put(vc1.getReference(), vc2.getReference()); - allele2ToAllele1.put(vc2.getReference(), vc1.getReference()); - - // Note the segregation of the alleles for each sample (and check that it is consistent with the reference and all previous samples). - for (final Genotype gt1 : vc1.getGenotypes()) { - Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); - - List site1Alleles = gt1.getAlleles(); - List site2Alleles = gt2.getAlleles(); - - Iterator all2It = site2Alleles.iterator(); - for (Allele all1 : site1Alleles) { - Allele all2 = all2It.next(); - - Allele all1To2 = allele1ToAllele2.get(all1); - if (all1To2 == null) - allele1ToAllele2.put(all1, all2); - else if (!all1To2.equals(all2)) // all1 segregates with two different alleles at site 2 - return false; - - Allele all2To1 = allele2ToAllele1.get(all2); - if (all2To1 == null) - allele2ToAllele1.put(all2, all1); - else if (!all2To1.equals(all1)) // all2 segregates with two different alleles at site 1 - return false; - } - } - - return true; - } -} \ No newline at end of file +} From 231c47c039b2cdfcb565c1640e00aa1225f7ff71 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 15 Nov 2011 16:42:50 -0500 Subject: [PATCH 031/113] Bugfixes on way to a working refactored VariantContext --- .../sting/gatk/walkers/phasing/PhasingUtils.java | 3 +-- .../sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java | 4 +++- .../sting/utils/variantcontext/GenotypeCollection.java | 5 +---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java index 8b5455e50..ce35baf15 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java @@ -129,9 +129,8 @@ class PhasingUtils { List mergedIDs = new ArrayList(); if ( vc1.hasID() ) mergedIDs.add(vc1.getID()); if ( vc2.hasID() ) mergedIDs.add(vc2.getID()); - String mergedID = Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); + String mergedID = mergedIDs.isEmpty() ? VCFConstants.EMPTY_ID_FIELD : Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); - // TODO -- FIX ID VariantContext mergedVc = new VariantContext(mergedName, mergedID, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); mergedAttribs = new HashMap(mergedVc.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 8cc0d8856..f2d870068 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -1126,9 +1126,11 @@ public class ReadBackedPhasingWalker extends RodWalker filters; private Map attributes; + private String id; public UnfinishedVariantContext(VariantContext vc) { this.name = vc.getSource(); + this.id = vc.getID(); this.contig = vc.getChr(); this.start = vc.getStart(); this.stop = vc.getEnd(); @@ -1140,7 +1142,7 @@ public class ReadBackedPhasingWalker extends RodWalker { return new GenotypeCollection(nGenotypes, false); } - // todo -- differentiate between empty constructor and copy constructor - // todo -- create constructor (Genotype ... genotypes) - public static final GenotypeCollection create(final ArrayList genotypes) { return genotypes == null ? NO_GENOTYPES : new GenotypeCollection(genotypes, false); } @@ -82,7 +79,7 @@ public class GenotypeCollection implements List { } public static final GenotypeCollection copy(final GenotypeCollection toCopy) { - return create(toCopy.genotypes); + return create(new ArrayList(toCopy.genotypes)); } public static final GenotypeCollection copy(final Collection toCopy) { From 0be23aae4e33a78f6cc56ef15e42f46d7742c1f4 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 15 Nov 2011 17:20:14 -0500 Subject: [PATCH 032/113] Bugfixes on way to a working refactored VariantContext --- .../sting/gatk/walkers/diffengine/VCFDiffableReader.java | 8 +++++++- .../sting/utils/variantcontext/VariantContext.java | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java index 4b8a703a5..6b5ffd3ed 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java @@ -24,6 +24,7 @@ package org.broadinstitute.sting.gatk.walkers.diffengine; +import org.apache.log4j.Logger; import org.broad.tribble.readers.AsciiLineReader; import org.broad.tribble.readers.LineReader; import org.broadinstitute.sting.utils.codecs.vcf.*; @@ -46,6 +47,8 @@ import java.util.Map; * Class implementing diffnode reader for VCF */ public class VCFDiffableReader implements DiffableReader { + private static Logger logger = Logger.getLogger(VCFDiffableReader.class); + @Override public String getName() { return "VCF"; } @@ -68,7 +71,10 @@ public class VCFDiffableReader implements DiffableReader { String key = headerLine.getKey(); if ( headerLine instanceof VCFNamedHeaderLine ) key += "_" + ((VCFNamedHeaderLine) headerLine).getName(); - root.add(key, headerLine.toString()); + if ( root.hasElement(key) ) + logger.warn("Skipping duplicate header line: file=" + file + " line=" + headerLine.toString()); + else + root.add(key, headerLine.toString()); } String line = lineReader.readLine(); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 661ed2bf4..77dc88cc5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -388,10 +388,14 @@ public class VariantContext implements Feature { // to enable tribble intergrati if ( this.ID.equals("") ) throw new IllegalArgumentException("ID field cannot be the empty string"); if ( !genotypesAreUnparsed && attributes != null ) { - if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) + if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) { + attributes = new HashMap(attributes); attributes.remove(UNPARSED_GENOTYPE_MAP_KEY); - if ( attributes.containsKey(UNPARSED_GENOTYPE_PARSER_KEY) ) + } + if ( attributes.containsKey(UNPARSED_GENOTYPE_PARSER_KEY) ) { + attributes = new HashMap(attributes); attributes.remove(UNPARSED_GENOTYPE_PARSER_KEY); + } } this.commonInfo = new CommonInfo(source, negLog10PError, filters, attributes); From df415da4abd83f55bb59d900ea28f23e04314aca Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 15 Nov 2011 17:38:12 -0500 Subject: [PATCH 033/113] More bug fixes on the way to passing all tests --- .../variantcontext/GenotypeCollection.java | 5 +- .../phasing/MergeMNPsIntegrationTest.java | 51 ------------------- 2 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsIntegrationTest.java diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java index b8c628717..6ccb2a9ff 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java @@ -219,12 +219,9 @@ public class GenotypeCollection implements List { public Genotype get(final String sampleName) { buildCache(); Integer offset = sampleNameToOffset.get(sampleName); - if ( offset == null ) - throw new IllegalArgumentException("Sample " + sampleName + " not found in this GenotypeCollection"); - return genotypes.get(offset); + return offset == null ? null : genotypes.get(offset); } - @Override public int indexOf(final Object o) { return genotypes.indexOf(o); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsIntegrationTest.java deleted file mode 100644 index 2e4556af0..000000000 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeMNPsIntegrationTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.phasing; - -import org.broadinstitute.sting.WalkerTest; -import org.testng.annotations.Test; - -import java.util.Arrays; - -public class MergeMNPsIntegrationTest extends WalkerTest { - - public static String baseTestString(String reference, String VCF, int maxDistMNP) { - return "-T MergeMNPs" + - " -R " + reference + - " --variant:vcf " + validationDataLocation + VCF + - " --maxGenomicDistanceForMNP " + maxDistMNP + - " -o %s" + - " -NO_HEADER"; - } - - - @Test - public void test1() { - WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(hg18Reference, "merging_test_chr20_556259_756570.vcf", 1) - + " -L chr20:556259-756570", - 1, - Arrays.asList("7f11f7f75d1526077f0173c7ed1fc6c4")); - executeTest("Merge MNP sites within genomic distance of 1 [TEST ONE]", spec); - } - - @Test - public void test2() { - WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(hg18Reference, "merging_test_chr20_556259_756570.vcf", 10) - + " -L chr20:556259-756570", - 1, - Arrays.asList("53dd312468296826bdd3c22387390c88")); - executeTest("Merge MNP sites within genomic distance of 10 [TEST TWO]", spec); - } - - @Test - public void test3() { - WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(hg18Reference, "merging_test_chr20_556259_756570.vcf", 100) - + " -L chr20:556259-756570", - 1, - Arrays.asList("e26f92d2fb9f4eaeac7f9d8ee27410ee")); - executeTest("Merge MNP sites within genomic distance of 100 [TEST THREE]", spec); - } - - -} \ No newline at end of file From 0d163e3f52b4de1b54c2213aea5aca14f336acd0 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Tue, 15 Nov 2011 16:05:20 -0500 Subject: [PATCH 034/113] SnpEff 2.0.4 support -Modified the SnpEff parser to work with the SnpEff 2.0.4 VCF output format -Assigning functional classes and effect impacts now handled directly by SnpEff rather than the GATK -Removed support for SnpEff 2.0.2, as we no longer trust the output of that version since it doesn't exclude effects associated with certain nonsensical transcripts. These effects are excluded as of 2.0.4. -Updated unit and integration tests This support is based on a *release-candidate* of SnpEff 2.0.4, and so is subject to change between now and the next GATK release. --- .../sting/gatk/walkers/annotator/SnpEff.java | 180 +++++++++--------- .../walkers/annotator/SnpEffUnitTest.java | 20 +- .../VariantAnnotatorIntegrationTest.java | 4 +- .../VariantEvalIntegrationTest.java | 6 +- 4 files changed, 106 insertions(+), 104 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java index 85977bf8e..1956dac6c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java @@ -56,7 +56,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio // We refuse to parse SnpEff output files generated by unsupported versions, or // lacking a SnpEff version number in the VCF header: - public static final String[] SUPPORTED_SNPEFF_VERSIONS = { "2.0.2" }; + public static final String[] SUPPORTED_SNPEFF_VERSIONS = { "2.0.4" }; public static final String SNPEFF_VCF_HEADER_VERSION_LINE_KEY = "SnpEffVersion"; public static final String SNPEFF_VCF_HEADER_COMMAND_LINE_KEY = "SnpEffCmd"; @@ -77,13 +77,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio public enum InfoFieldKey { EFFECT_KEY ("SNPEFF_EFFECT", -1), IMPACT_KEY ("SNPEFF_IMPACT", 0), - CODON_CHANGE_KEY ("SNPEFF_CODON_CHANGE", 1), - AMINO_ACID_CHANGE_KEY ("SNPEFF_AMINO_ACID_CHANGE", 2), - GENE_NAME_KEY ("SNPEFF_GENE_NAME", 3), - GENE_BIOTYPE_KEY ("SNPEFF_GENE_BIOTYPE", 4), - TRANSCRIPT_ID_KEY ("SNPEFF_TRANSCRIPT_ID", 6), - EXON_ID_KEY ("SNPEFF_EXON_ID", 7), - FUNCTIONAL_CLASS_KEY ("SNPEFF_FUNCTIONAL_CLASS", -1); + FUNCTIONAL_CLASS_KEY ("SNPEFF_FUNCTIONAL_CLASS", 1), + CODON_CHANGE_KEY ("SNPEFF_CODON_CHANGE", 2), + AMINO_ACID_CHANGE_KEY ("SNPEFF_AMINO_ACID_CHANGE", 3), + GENE_NAME_KEY ("SNPEFF_GENE_NAME", 4), + GENE_BIOTYPE_KEY ("SNPEFF_GENE_BIOTYPE", 5), + TRANSCRIPT_ID_KEY ("SNPEFF_TRANSCRIPT_ID", 7), + EXON_ID_KEY ("SNPEFF_EXON_ID", 8); // Actual text of the key private final String keyName; @@ -110,70 +110,53 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio // are validated against this list. public enum EffectType { // High-impact effects: - FRAME_SHIFT (EffectFunctionalClass.NONE, false), - STOP_GAINED (EffectFunctionalClass.NONSENSE, false), - START_LOST (EffectFunctionalClass.NONE, false), - SPLICE_SITE_ACCEPTOR (EffectFunctionalClass.NONE, false), - SPLICE_SITE_DONOR (EffectFunctionalClass.NONE, false), - EXON_DELETED (EffectFunctionalClass.NONE, false), - STOP_LOST (EffectFunctionalClass.NONE, false), + SPLICE_SITE_ACCEPTOR, + SPLICE_SITE_DONOR, + START_LOST, + EXON_DELETED, + FRAME_SHIFT, + STOP_GAINED, + STOP_LOST, // Moderate-impact effects: - NON_SYNONYMOUS_CODING (EffectFunctionalClass.MISSENSE, false), - CODON_CHANGE (EffectFunctionalClass.NONE, false), - CODON_INSERTION (EffectFunctionalClass.NONE, false), - CODON_CHANGE_PLUS_CODON_INSERTION (EffectFunctionalClass.NONE, false), - CODON_DELETION (EffectFunctionalClass.NONE, false), - CODON_CHANGE_PLUS_CODON_DELETION (EffectFunctionalClass.NONE, false), - UTR_5_DELETED (EffectFunctionalClass.NONE, false), - UTR_3_DELETED (EffectFunctionalClass.NONE, false), + NON_SYNONYMOUS_CODING, + CODON_CHANGE, + CODON_INSERTION, + CODON_CHANGE_PLUS_CODON_INSERTION, + CODON_DELETION, + CODON_CHANGE_PLUS_CODON_DELETION, + UTR_5_DELETED, + UTR_3_DELETED, // Low-impact effects: - SYNONYMOUS_CODING (EffectFunctionalClass.SILENT, false), - SYNONYMOUS_START (EffectFunctionalClass.SILENT, false), - NON_SYNONYMOUS_START (EffectFunctionalClass.SILENT, false), - SYNONYMOUS_STOP (EffectFunctionalClass.SILENT, false), - NON_SYNONYMOUS_STOP (EffectFunctionalClass.SILENT, false), - START_GAINED (EffectFunctionalClass.NONE, false), + SYNONYMOUS_START, + NON_SYNONYMOUS_START, + START_GAINED, + SYNONYMOUS_CODING, + SYNONYMOUS_STOP, + NON_SYNONYMOUS_STOP, // Modifiers: - NONE (EffectFunctionalClass.NONE, true), - CHROMOSOME (EffectFunctionalClass.NONE, true), - INTERGENIC (EffectFunctionalClass.NONE, true), - UPSTREAM (EffectFunctionalClass.NONE, true), - UTR_5_PRIME (EffectFunctionalClass.NONE, true), - CDS (EffectFunctionalClass.NONE, true), - GENE (EffectFunctionalClass.NONE, true), - TRANSCRIPT (EffectFunctionalClass.NONE, true), - EXON (EffectFunctionalClass.NONE, true), - INTRON (EffectFunctionalClass.NONE, true), - UTR_3_PRIME (EffectFunctionalClass.NONE, true), - DOWNSTREAM (EffectFunctionalClass.NONE, true), - INTRON_CONSERVED (EffectFunctionalClass.NONE, true), - INTERGENIC_CONSERVED (EffectFunctionalClass.NONE, true), - REGULATION (EffectFunctionalClass.NONE, true), - CUSTOM (EffectFunctionalClass.NONE, true), - WITHIN_NON_CODING_GENE (EffectFunctionalClass.NONE, true); - - private final EffectFunctionalClass functionalClass; - private final boolean isModifier; - - EffectType ( EffectFunctionalClass functionalClass, boolean isModifier ) { - this.functionalClass = functionalClass; - this.isModifier = isModifier; - } - - public EffectFunctionalClass getFunctionalClass() { - return functionalClass; - } - - public boolean isModifier() { - return isModifier; - } + NONE, + CHROMOSOME, + CUSTOM, + CDS, + GENE, + TRANSCRIPT, + EXON, + INTRON_CONSERVED, + UTR_5_PRIME, + UTR_3_PRIME, + DOWNSTREAM, + INTRAGENIC, + INTERGENIC, + INTERGENIC_CONSERVED, + UPSTREAM, + REGULATION, + INTRON } - // SnpEff labels each effect as either LOW, MODERATE, or HIGH impact. We take the additional step of - // classifying some of the LOW impact effects as MODIFIERs. + // SnpEff labels each effect as either LOW, MODERATE, or HIGH impact, or as a MODIFIER. public enum EffectImpact { MODIFIER (0), LOW (1), @@ -202,7 +185,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio UNKNOWN } - // We assign a functional class to each SnpEff effect. + // SnpEff assigns a functional class to each effect. public enum EffectFunctionalClass { NONE (0), SILENT (1), @@ -379,13 +362,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio public List getKeyNames() { return Arrays.asList( InfoFieldKey.EFFECT_KEY.getKeyName(), InfoFieldKey.IMPACT_KEY.getKeyName(), + InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), InfoFieldKey.CODON_CHANGE_KEY.getKeyName(), InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), InfoFieldKey.GENE_NAME_KEY.getKeyName(), InfoFieldKey.GENE_BIOTYPE_KEY.getKeyName(), InfoFieldKey.TRANSCRIPT_ID_KEY.getKeyName(), - InfoFieldKey.EXON_ID_KEY.getKeyName(), - InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName() + InfoFieldKey.EXON_ID_KEY.getKeyName() ); } @@ -393,13 +376,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio return Arrays.asList( new VCFInfoHeaderLine(InfoFieldKey.EFFECT_KEY.getKeyName(), 1, VCFHeaderLineType.String, "The highest-impact effect resulting from the current variant (or one of the highest-impact effects, if there is a tie)"), new VCFInfoHeaderLine(InfoFieldKey.IMPACT_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Impact of the highest-impact effect resulting from the current variant " + Arrays.toString(EffectImpact.values())), + new VCFInfoHeaderLine(InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Functional class of the highest-impact effect resulting from the current variant: " + Arrays.toString(EffectFunctionalClass.values())), new VCFInfoHeaderLine(InfoFieldKey.CODON_CHANGE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Old/New codon for the highest-impact effect resulting from the current variant"), - new VCFInfoHeaderLine(InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Old/New amino acid for the highest-impact effect resulting from the current variant"), + new VCFInfoHeaderLine(InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Old/New amino acid for the highest-impact effect resulting from the current variant (in HGVS style)"), new VCFInfoHeaderLine(InfoFieldKey.GENE_NAME_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Gene name for the highest-impact effect resulting from the current variant"), new VCFInfoHeaderLine(InfoFieldKey.GENE_BIOTYPE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Gene biotype for the highest-impact effect resulting from the current variant"), new VCFInfoHeaderLine(InfoFieldKey.TRANSCRIPT_ID_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Transcript ID for the highest-impact effect resulting from the current variant"), - new VCFInfoHeaderLine(InfoFieldKey.EXON_ID_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Exon ID for the highest-impact effect resulting from the current variant"), - new VCFInfoHeaderLine(InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Functional class of the highest-impact effect resulting from the current variant: " + Arrays.toString(EffectFunctionalClass.values())) + new VCFInfoHeaderLine(InfoFieldKey.EXON_ID_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Exon ID for the highest-impact effect resulting from the current variant") ); } @@ -409,6 +392,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio protected static class SnpEffEffect { private EffectType effect; private EffectImpact impact; + private EffectFunctionalClass functionalClass; private String codonChange; private String aminoAcidChange; private String geneName; @@ -420,16 +404,21 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio private String parseError = null; private boolean isWellFormed = true; - private static final int EXPECTED_NUMBER_OF_METADATA_FIELDS = 8; - private static final int NUMBER_OF_METADATA_FIELDS_UPON_WARNING = 9; - private static final int NUMBER_OF_METADATA_FIELDS_UPON_ERROR = 10; + private static final int EXPECTED_NUMBER_OF_METADATA_FIELDS = 9; + private static final int NUMBER_OF_METADATA_FIELDS_UPON_EITHER_WARNING_OR_ERROR = 10; + private static final int NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR = 11; - // Note that contrary to the description for the EFF field layout that SnpEff adds to the VCF header, - // errors come after warnings, not vice versa: - private static final int SNPEFF_WARNING_FIELD_INDEX = NUMBER_OF_METADATA_FIELDS_UPON_WARNING - 1; - private static final int SNPEFF_ERROR_FIELD_INDEX = NUMBER_OF_METADATA_FIELDS_UPON_ERROR - 1; + // If there is either a warning OR an error, it will be in the last field. If there is both + // a warning AND an error, the warning will be in the second-to-last field, and the error will + // be in the last field. + private static final int SNPEFF_WARNING_OR_ERROR_FIELD_UPON_SINGLE_ERROR = NUMBER_OF_METADATA_FIELDS_UPON_EITHER_WARNING_OR_ERROR - 1; + private static final int SNPEFF_WARNING_FIELD_UPON_BOTH_WARNING_AND_ERROR = NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR - 2; + private static final int SNPEFF_ERROR_FIELD_UPON_BOTH_WARNING_AND_ERROR = NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR - 1; - private static final int SNPEFF_CODING_FIELD_INDEX = 5; + // Position of the field indicating whether the effect is coding or non-coding. This field is used + // in selecting the most significant effect, but is not included in the annotations we return + // since it can be deduced from the SNPEFF_GENE_BIOTYPE field. + private static final int SNPEFF_CODING_FIELD_INDEX = 6; public SnpEffEffect ( String effectName, String[] effectMetadata ) { parseEffectName(effectName); @@ -447,11 +436,14 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio private void parseEffectMetadata ( String[] effectMetadata ) { if ( effectMetadata.length != EXPECTED_NUMBER_OF_METADATA_FIELDS ) { - if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_WARNING ) { - parseError(String.format("SnpEff issued the following warning: %s", effectMetadata[SNPEFF_WARNING_FIELD_INDEX])); + if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_EITHER_WARNING_OR_ERROR ) { + parseError(String.format("SnpEff issued the following warning or error: \"%s\"", + effectMetadata[SNPEFF_WARNING_OR_ERROR_FIELD_UPON_SINGLE_ERROR])); } - else if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_ERROR ) { - parseError(String.format("SnpEff issued the following error: %s", effectMetadata[SNPEFF_ERROR_FIELD_INDEX])); + else if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR ) { + parseError(String.format("SnpEff issued the following warning: \"%s\", and the following error: \"%s\"", + effectMetadata[SNPEFF_WARNING_FIELD_UPON_BOTH_WARNING_AND_ERROR], + effectMetadata[SNPEFF_ERROR_FIELD_UPON_BOTH_WARNING_AND_ERROR])); } else { parseError(String.format("Wrong number of effect metadata fields. Expected %d but found %d", @@ -461,23 +453,33 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio return; } - if ( effect != null && effect.isModifier() ) { - impact = EffectImpact.MODIFIER; + // The impact field will never be empty, and should always contain one of the enumerated values: + try { + impact = EffectImpact.valueOf(effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()]); } - else { + catch ( IllegalArgumentException e ) { + parseError(String.format("Unrecognized value for effect impact: %s", effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()])); + } + + // The functional class field will be empty when the effect has no functional class associated with it: + if ( effectMetadata[InfoFieldKey.FUNCTIONAL_CLASS_KEY.getFieldIndex()].trim().length() > 0 ) { try { - impact = EffectImpact.valueOf(effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()]); + functionalClass = EffectFunctionalClass.valueOf(effectMetadata[InfoFieldKey.FUNCTIONAL_CLASS_KEY.getFieldIndex()]); } catch ( IllegalArgumentException e ) { - parseError(String.format("Unrecognized value for effect impact: %s", effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()])); + parseError(String.format("Unrecognized value for effect functional class: %s", effectMetadata[InfoFieldKey.FUNCTIONAL_CLASS_KEY.getFieldIndex()])); } } + else { + functionalClass = EffectFunctionalClass.NONE; + } codonChange = effectMetadata[InfoFieldKey.CODON_CHANGE_KEY.getFieldIndex()]; aminoAcidChange = effectMetadata[InfoFieldKey.AMINO_ACID_CHANGE_KEY.getFieldIndex()]; geneName = effectMetadata[InfoFieldKey.GENE_NAME_KEY.getFieldIndex()]; geneBiotype = effectMetadata[InfoFieldKey.GENE_BIOTYPE_KEY.getFieldIndex()]; + // The coding field will be empty when SnpEff has no coding info for the effect: if ( effectMetadata[SNPEFF_CODING_FIELD_INDEX].trim().length() > 0 ) { try { coding = EffectCoding.valueOf(effectMetadata[SNPEFF_CODING_FIELD_INDEX]); @@ -534,7 +536,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio return true; } else if ( impact.isSameImpactAs(other.impact) ) { - return effect.getFunctionalClass().isHigherPriorityThan(other.effect.getFunctionalClass()); + return functionalClass.isHigherPriorityThan(other.functionalClass); } return false; @@ -545,13 +547,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio addAnnotation(annotations, InfoFieldKey.EFFECT_KEY.getKeyName(), effect.toString()); addAnnotation(annotations, InfoFieldKey.IMPACT_KEY.getKeyName(), impact.toString()); + addAnnotation(annotations, InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), functionalClass.toString()); addAnnotation(annotations, InfoFieldKey.CODON_CHANGE_KEY.getKeyName(), codonChange); addAnnotation(annotations, InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), aminoAcidChange); addAnnotation(annotations, InfoFieldKey.GENE_NAME_KEY.getKeyName(), geneName); addAnnotation(annotations, InfoFieldKey.GENE_BIOTYPE_KEY.getKeyName(), geneBiotype); addAnnotation(annotations, InfoFieldKey.TRANSCRIPT_ID_KEY.getKeyName(), transcriptID); addAnnotation(annotations, InfoFieldKey.EXON_ID_KEY.getKeyName(), exonID); - addAnnotation(annotations, InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), effect.getFunctionalClass().toString()); return annotations; } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java index 462abeba1..5c8fa32a8 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java @@ -33,7 +33,7 @@ public class SnpEffUnitTest { @Test public void testParseWellFormedEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertTrue( effect.isWellFormed() && effect.isCoding() ); @@ -42,7 +42,7 @@ public class SnpEffUnitTest { @Test public void testParseInvalidEffectNameEffect() { String effectName = "MADE_UP_EFFECT"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertFalse(effect.isWellFormed()); @@ -51,7 +51,7 @@ public class SnpEffUnitTest { @Test public void testParseInvalidEffectImpactEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MEDIUM", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; + String[] effectMetadata = { "MEDIUM", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertFalse(effect.isWellFormed()); @@ -60,27 +60,27 @@ public class SnpEffUnitTest { @Test public void testParseWrongNumberOfMetadataFieldsEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertFalse(effect.isWellFormed()); } @Test - public void testParseSnpEffWarningEffect() { + public void testParseSnpEffOneWarningOrErrorEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "SNPEFF_WARNING" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "SNPEFF_WARNING_OR_ERROR_TEXT" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); - Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following warning: SNPEFF_WARNING") ); + Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following warning or error: \"SNPEFF_WARNING_OR_ERROR_TEXT\"") ); } @Test - public void testParseSnpEffErrorEffect() { + public void testParseSnpEffBothWarningAndErrorEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "", "SNPEFF_ERROR" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "SNPEFF_WARNING_TEXT", "SNPEFF_ERROR_TEXT" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); - Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following error: SNPEFF_ERROR") ); + Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following warning: \"SNPEFF_WARNING_TEXT\", and the following error: \"SNPEFF_ERROR_TEXT\"") ); } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index bde4c4a8f..919e3d9bd 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -148,9 +148,9 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { WalkerTestSpec spec = new WalkerTestSpec( "-T VariantAnnotator -R " + hg19Reference + " -NO_HEADER -o %s -A SnpEff --variant " + validationDataLocation + "1kg_exomes_unfiltered.AFR.unfiltered.vcf --snpEffFile " + validationDataLocation + - "snpEff.AFR.unfiltered.vcf -L 1:1-1,500,000 -L 2:232,325,429", + "snpEff2.0.4.AFR.unfiltered.vcf -L 1:1-1,500,000 -L 2:232,325,429", 1, - Arrays.asList("122321a85e448f21679f6ca15c5e22ad") + Arrays.asList("51258f5c880bd1ca3eb45a1711335c66") ); executeTest("Testing SnpEff annotations", spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java index 3dceb9bd2..102d4715e 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java @@ -21,16 +21,16 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-T VariantEval", "-R " + b37KGReference, "--dbsnp " + b37dbSNP132, - "--eval " + validationDataLocation + "snpEff.AFR.unfiltered.VariantAnnotator.output.vcf", + "--eval " + validationDataLocation + "snpEff2.0.4.AFR.unfiltered.VariantAnnotator.output.vcf", "-noEV", "-EV TiTvVariantEvaluator", "-noST", "-ST FunctionalClass", - "-L " + validationDataLocation + "snpEff.AFR.unfiltered.VariantAnnotator.output.vcf", + "-L " + validationDataLocation + "snpEff2.0.4.AFR.unfiltered.VariantAnnotator.output.vcf", "-o %s" ), 1, - Arrays.asList("d9dcb352c53106f54fcc981f15d35a90") + Arrays.asList("a36414421621b377d6146d58d2fcecd0") ); executeTest("testFunctionClassWithSnpeff", spec); } From 7d77fc51f55948510e9d230864dbbe78e1f791b2 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 16 Nov 2011 03:32:43 -0500 Subject: [PATCH 035/113] Corrected bug causing PhaseByTransmission to crash in case of new Genotype.Type --- .../walkers/phasing/PhaseByTransmission.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 6394e0e24..847165e3e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -151,16 +151,16 @@ public class PhaseByTransmission extends RodWalker, HashMa alleles.add(VAR); alleles.add(VAR); } - else if(genotype == Genotype.Type.NO_CALL){ - alleles.add(NO_CALL); - alleles.add(NO_CALL); - } else{ return null; } return alleles; } + private boolean isPhasable(Genotype.Type genotype){ + return genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HET || genotype == Genotype.Type.HOM_VAR; + } + //Create a new Genotype based on information from a single individual //Homozygous genotypes will be set as phased, heterozygous won't be private void phaseSingleIndividualAlleles(Genotype.Type genotype, FamilyMember familyMember){ @@ -271,21 +271,21 @@ public class PhaseByTransmission extends RodWalker, HashMa public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Take care of cases where one or more family members are no call - if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ + if(!isPhasable(child)){ phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } - else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ + else if(!isPhasable(mother)){ phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + if(!isPhasable(father)){ phaseSingleIndividualAlleles(father, FamilyMember.FATHER); phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } else phasePairAlleles(father, child, FamilyMember.FATHER); } - else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + else if(!isPhasable(father)){ phasePairAlleles(mother, child, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); } @@ -327,7 +327,7 @@ public class PhaseByTransmission extends RodWalker, HashMa //Note that only cases where a null/missing/unavailable genotype was passed in the first place can lead to a null/missing/unavailable //genotype so it is safe to return the original genotype in this case. //In addition, if the phasing confidence is 0, then return the unphased, original genotypes. - if(phredScoreTransmission ==0 || genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + if(phredScoreTransmission ==0 || genotype == null || !isPhasable(genotype.getType())) return genotype; //Add the transmission probability From 7ac5cf8430e82822d008c35b99d37f35b151230e Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Wed, 16 Nov 2011 09:53:59 -0500 Subject: [PATCH 036/113] Getting rid of unsupported CountReadPairs walker in stable. Removal of remainder of pairs processing framework to follow in unstable. --- .../gatk/walkers/qc/CountPairsWalker.java | 140 ------------------ 1 file changed, 140 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java deleted file mode 100644 index e770418c1..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2010. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR - * THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.qc; - -import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.commandline.Output; -import org.broadinstitute.sting.gatk.walkers.ReadPairWalker; -import org.broadinstitute.sting.utils.collections.ExpandingArrayList; - -import java.io.PrintStream; -import java.util.Collection; -import java.util.List; - -/** - * Counts the number of read pairs encountered in a file sorted in - * query name order. Breaks counts down by total pairs and number - * of paired reads. - * - * - *

Input

- *

- * One or more bam files. - *

- * - *

Output

- *

- * Number of pairs seen. - *

- * - *

Examples

- *
- * java -Xmx2g -jar GenomeAnalysisTK.jar \
- *   -R ref.fasta \
- *   -T CountPairs \
- *   -o output.txt \
- *   -I input.bam
- * 
- * - * @author mhanna - */ -public class CountPairsWalker extends ReadPairWalker { - @Output - private PrintStream out; - - /** - * How many reads are the first in a pair, based on flag 0x0040 from the SAM spec. - */ - private long firstOfPair = 0; - - /** - * How many reads are the second in a pair, based on flag 0x0080 from the SAM spec. - */ - private long secondOfPair = 0; - - /** - * A breakdown of the total number of reads seen with exactly the same read name. - */ - private List pairCountsByType = new ExpandingArrayList(); - - /** - * Maps a read pair to a given reduce of type MapType. Semantics determined by subclasser. - * @param reads Collection of reads having the same name. - * @return Semantics defined by implementer. - */ - @Override - public Integer map(Collection reads) { - if(pairCountsByType.get(reads.size()) != null) - pairCountsByType.set(reads.size(),pairCountsByType.get(reads.size())+1); - else - pairCountsByType.set(reads.size(),1L); - - for(SAMRecord read: reads) { - if(read.getFirstOfPairFlag()) firstOfPair++; - if(read.getSecondOfPairFlag()) secondOfPair++; - } - - return 1; - } - - /** - * No pairs at the beginning of a traversal. - * @return 0 always. - */ - @Override - public Long reduceInit() { - return 0L; - } - - /** - * Combine number of pairs seen in this iteration (always 1) with total number of pairs - * seen in previous iterations. - * @param value Pairs in this iteration (1), from the map function. - * @param sum Count of all pairs in prior iterations. - * @return All pairs encountered in previous iterations + all pairs encountered in this iteration (sum + 1). - */ - @Override - public Long reduce(Integer value, Long sum) { - return value + sum; - } - - /** - * Print summary statistics over the entire traversal. - * @param sum A count of all read pairs viewed. - */ - @Override - public void onTraversalDone(Long sum) { - out.printf("Total number of pairs : %d%n",sum); - out.printf("Total number of first reads in pair : %d%n",firstOfPair); - out.printf("Total number of second reads in pair: %d%n",secondOfPair); - for(int i = 1; i < pairCountsByType.size(); i++) { - if(pairCountsByType.get(i) == null) - continue; - out.printf("Pairs of size %d: %d%n",i,pairCountsByType.get(i)); - } - } - -} From e56d52006a9a31079960bd55dc9249b10892c93b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 10:39:17 -0500 Subject: [PATCH 037/113] Continuing bugfixes to get new VC working --- .../beagle/BeagleOutputToVCFWalker.java | 4 +- .../walkers/variantutils/SelectVariants.java | 4 + .../variantcontext/GenotypeCollection.java | 78 +++++++++++++++---- .../utils/variantcontext/VariantContext.java | 13 +++- ...gatingAlternateAllelesIntegrationTest.java | 51 ------------ 5 files changed, 77 insertions(+), 73 deletions(-) delete mode 100644 public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesIntegrationTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 549c26575..297203aec 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -333,11 +333,11 @@ public class BeagleOutputToVCFWalker extends RodWalker { VariantContext filteredVC; if ( beagleVarCounts > 0 || DONT_FILTER_MONOMORPHIC_SITES ) - filteredVC = new VariantContext("outputvcf", VCFConstants.EMPTY_ID_FIELD, vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), vc_input.getAlleles(), genotypes, vc_input.getNegLog10PError(), vc_input.filtersWereApplied() ? vc_input.getFilters() : null, vc_input.getAttributes()); + filteredVC = new VariantContext("outputvcf", vc_input.getID(), vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), vc_input.getAlleles(), genotypes, vc_input.getNegLog10PError(), vc_input.filtersWereApplied() ? vc_input.getFilters() : null, vc_input.getAttributes()); else { Set removedFilters = vc_input.filtersWereApplied() ? new HashSet(vc_input.getFilters()) : new HashSet(1); removedFilters.add(String.format("BGL_RM_WAS_%s",vc_input.getAlternateAllele(0))); - filteredVC = new VariantContext("outputvcf", VCFConstants.EMPTY_ID_FIELD, vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), new HashSet(Arrays.asList(vc_input.getReference())), genotypes, vc_input.getNegLog10PError(), removedFilters, vc_input.getAttributes()); + filteredVC = new VariantContext("outputvcf", vc_input.getID(), vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), new HashSet(Arrays.asList(vc_input.getReference())), genotypes, vc_input.getNegLog10PError(), removedFilters, vc_input.getAttributes()); } HashMap attributes = new HashMap(filteredVC.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 3c92bf00f..6fec0fac2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -654,7 +654,10 @@ public class SelectVariants extends RodWalker { if ( samples == null || samples.isEmpty() ) return vc; +// logger.info("Genotypes in full vc: " + vc.getGenotypes()); +// logger.info("My own sub : " + vc.getGenotypes().subsetToSamples(samples)); VariantContext sub = vc.subContextFromSamples(samples, vc.getAlleles()); +// logger.info("Genotypes in sub vc: " + sub.getGenotypes()); // if we have fewer alternate alleles in the selected VC than in the original VC, we need to strip out the GL/PLs (because they are no longer accurate) if ( vc.getAlleles().size() != sub.getAlleles().size() ) @@ -691,6 +694,7 @@ public class SelectVariants extends RodWalker { sub = VariantContext.modifyAttributes(sub, attributes); +// logger.info("Genotypes in final vc: " + sub.getGenotypes()); return sub; } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java index 6ccb2a9ff..4dbc23e63 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java @@ -30,11 +30,13 @@ import java.util.*; * */ public class GenotypeCollection implements List { - public final static GenotypeCollection NO_GENOTYPES = new GenotypeCollection(); + public final static GenotypeCollection NO_GENOTYPES = + new GenotypeCollection(new ArrayList(0), new HashMap(0), new HashSet(0), true); + Set sampleNamesInOrder = null; Map sampleNameToOffset = null; boolean cacheIsInvalid = true; - final ArrayList genotypes; + List genotypes; boolean immutable = false; // --------------------------------------------------------------------------- @@ -54,6 +56,19 @@ public class GenotypeCollection implements List { private GenotypeCollection(final ArrayList genotypes, final boolean immutable) { this.genotypes = genotypes; this.immutable = immutable; + this.sampleNameToOffset = null; + this.cacheIsInvalid = true; + } + + private GenotypeCollection(final ArrayList genotypes, + final Map sampleNameToOffset, + final Set sampleNamesInOrder, + final boolean immutable) { + this.genotypes = genotypes; + this.immutable = immutable; + this.sampleNameToOffset = sampleNameToOffset; + this.sampleNamesInOrder = sampleNamesInOrder; + this.cacheIsInvalid = false; } // --------------------------------------------------------------------------- @@ -108,12 +123,8 @@ public class GenotypeCollection implements List { // // --------------------------------------------------------------------------- - public final GenotypeCollection mutable() { - immutable = false; - return this; - } - public final GenotypeCollection immutable() { + this.genotypes = Collections.unmodifiableList(genotypes); immutable = true; return this; } @@ -135,17 +146,20 @@ public class GenotypeCollection implements List { private void invalidateCaches() { cacheIsInvalid = true; - if ( sampleNameToOffset != null ) sampleNameToOffset.clear(); + sampleNamesInOrder = null; + sampleNameToOffset = null; } private void buildCache() { cacheIsInvalid = false; + sampleNamesInOrder = new TreeSet(); + sampleNameToOffset = new HashMap(genotypes.size()); - if ( sampleNameToOffset == null ) - sampleNameToOffset = new HashMap(genotypes.size()); - - for ( int i = 0; i < genotypes.size(); i++ ) - sampleNameToOffset.put(genotypes.get(i).getSampleName(), i); + for ( int i = 0; i < genotypes.size(); i++ ) { + final Genotype g = genotypes.get(i); + sampleNamesInOrder.add(g.getSampleName()); + sampleNameToOffset.put(g.getSampleName(), i); + } } @@ -341,7 +355,8 @@ public class GenotypeCollection implements List { } public Set getSampleNamesOrderedByName() { - return new TreeSet(getSampleNames()); + buildCache(); + return sampleNamesInOrder; } public boolean containsSample(final String sample) { @@ -365,10 +380,41 @@ public class GenotypeCollection implements List { return NO_GENOTYPES; else { GenotypeCollection subset = create(samples.size()); - for ( final Genotype g : genotypes ) - if ( samples.contains(g.getSampleName()) ) + for ( final Genotype g : genotypes ) { + if ( samples.contains(g.getSampleName()) ) { subset.add(g); + } + } return subset; } } + + @Override + public String toString() { + final List gS = new ArrayList(); + for ( final Genotype g : this.iterateInSampleNameOrder() ) + gS.add(g.toString()); + return "[" + join(",", gS) + "]"; + } + + // copied from Utils + private static String join(final String separator, final Collection objects) { + if (objects.isEmpty()) { // fast path for empty collection + return ""; + } else { + final Iterator iter = objects.iterator(); + final T first = iter.next(); + + if ( ! iter.hasNext() ) // fast path for singleton collections + return first.toString(); + else { // full path for 2+ collection that actually need a join + final StringBuilder ret = new StringBuilder(first.toString()); + while(iter.hasNext()) { + ret.append(separator); + ret.append(iter.next().toString()); + } + return ret.toString(); + } + } + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 77dc88cc5..d0f88e2ec 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -411,7 +411,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati // we need to make this a LinkedHashSet in case the user prefers a given ordering of alleles this.alleles = makeAlleles(alleles); - if ( genotypes == null ) { + if ( genotypes == null || genotypes == NO_GENOTYPES ) { this.genotypes = NO_GENOTYPES; } else { this.genotypes = genotypes.immutable(); @@ -543,8 +543,10 @@ public class VariantContext implements Feature { // to enable tribble intergrati // } public VariantContext subContextFromSamples(Set sampleNames, Collection alleles) { + loadGenotypes(); + GenotypeCollection newGenotypes = genotypes.subsetToSamples(sampleNames); return new VariantContext(getSource(), getID(), contig, start, stop, alleles, - genotypes.subsetToSamples(sampleNames), + newGenotypes, getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), @@ -552,6 +554,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati } public VariantContext subContextFromSamples(Set sampleNames) { + loadGenotypes(); GenotypeCollection newGenotypes = genotypes.subsetToSamples(sampleNames); return new VariantContext(getSource(), getID(), contig, start, stop, allelesOfGenotypes(newGenotypes), newGenotypes, @@ -562,7 +565,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati } public VariantContext subContextFromSample(String sampleName) { - return subContextFromSamples(new HashSet(Arrays.asList(sampleName))); + return subContextFromSamples(Collections.singleton(sampleName)); } /** @@ -1460,7 +1463,9 @@ public class VariantContext implements Feature { // to enable tribble intergrati public String toString() { return String.format("[VC %s @ %s of type=%s alleles=%s attr=%s GT=%s", getSource(), contig + ":" + (start - stop == 0 ? start : start + "-" + stop), this.getType(), - ParsingUtils.sortList(this.getAlleles()), ParsingUtils.sortedString(this.getAttributes()), this.getGenotypesSortedByName()); + ParsingUtils.sortList(this.getAlleles()), + ParsingUtils.sortedString(this.getAttributes()), + this.getGenotypes()); } // protected basic manipulation routines diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesIntegrationTest.java deleted file mode 100644 index db1e4a82f..000000000 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesIntegrationTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.phasing; - -import org.broadinstitute.sting.WalkerTest; -import org.testng.annotations.Test; - -import java.util.Arrays; - -public class MergeSegregatingAlternateAllelesIntegrationTest extends WalkerTest { - - public static String baseTestString(String reference, String VCF, int maxDist) { - return "-T MergeSegregatingAlternateAlleles" + - " -R " + reference + - " --variant:vcf " + validationDataLocation + VCF + - " --maxGenomicDistance " + maxDist + - " -o %s" + - " -NO_HEADER"; - } - - - @Test - public void test1() { - WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(hg18Reference, "merging_test_chr20_556259_756570.vcf", 1) - + " -L chr20:556259-756570", - 1, - Arrays.asList("af5e1370822551c0c6f50f23447dc627")); - executeTest("Merge sites within genomic distance of 1 [TEST ONE]", spec); - } - - @Test - public void test2() { - WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(hg18Reference, "merging_test_chr20_556259_756570.vcf", 10) - + " -L chr20:556259-756570", - 1, - Arrays.asList("dd8c44ae1ef059a7fe85399467e102eb")); - executeTest("Merge sites within genomic distance of 10 [TEST TWO]", spec); - } - - @Test - public void test3() { - WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(hg18Reference, "merging_test_chr20_556259_756570.vcf", 100) - + " -L chr20:556259-756570", - 1, - Arrays.asList("f81fd72ecaa57b3215406fcea860bcc5")); - executeTest("Merge sites within genomic distance of 100 [TEST THREE]", spec); - } - - -} \ No newline at end of file From 101ffc4dfd5ce83f7d6bf0b5de4a99ebfac447de Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 13:35:16 -0500 Subject: [PATCH 039/113] Expanded, contrastive VariantContextBenchmark -- Compares performance across a bunch of common operations with GATK 1.3 version of VariantContext and GATK 1.4 -- 1.3 VC and associated utilities copied wholesale into test directory under v13 --- .../VariantContextBenchmark.java | 303 +++- .../variantcontext/v13/AbstractVCFCodec.java | 635 +++++++ .../utils/variantcontext/v13/Allele.java | 456 +++++ .../utils/variantcontext/v13/Genotype.java | 349 ++++ .../v13/GenotypeLikelihoods.java | 196 ++ .../variantcontext/v13/IndexingVCFWriter.java | 143 ++ .../v13/InferredGeneticContext.java | 243 +++ .../variantcontext/v13/MutableGenotype.java | 68 + .../v13/MutableVariantContext.java | 213 +++ .../utils/variantcontext/v13/VCF3Codec.java | 198 ++ .../variantcontext/v13/VCFAltHeaderLine.java | 28 + .../utils/variantcontext/v13/VCFCodec.java | 228 +++ .../v13/VCFCompoundHeaderLine.java | 224 +++ .../variantcontext/v13/VCFConstants.java | 112 ++ .../v13/VCFFilterHeaderLine.java | 28 + .../v13/VCFFormatHeaderLine.java | 32 + .../utils/variantcontext/v13/VCFHeader.java | 198 ++ .../variantcontext/v13/VCFHeaderLine.java | 134 ++ .../v13/VCFHeaderLineCount.java | 8 + .../v13/VCFHeaderLineTranslator.java | 124 ++ .../variantcontext/v13/VCFHeaderLineType.java | 8 + .../variantcontext/v13/VCFHeaderVersion.java | 91 + .../variantcontext/v13/VCFInfoHeaderLine.java | 29 + .../v13/VCFNamedHeaderLine.java | 30 + .../utils/variantcontext/v13/VCFParser.java | 22 + .../v13/VCFSimpleHeaderLine.java | 81 + .../utils/variantcontext/v13/VCFUtils.java | 227 +++ .../utils/variantcontext/v13/VCFWriter.java | 16 + .../variantcontext/v13/VariantContext.java | 1615 +++++++++++++++++ .../v13/VariantContextUtils.java | 1407 ++++++++++++++ .../v13/VariantJEXLContext.java | 315 ++++ 31 files changed, 7727 insertions(+), 34 deletions(-) create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java create mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index 06ce61627..e16176dff 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -27,8 +27,14 @@ package org.broadinstitute.sting.utils.variantcontext; import com.google.caliper.Param; import com.google.caliper.SimpleBenchmark; import com.google.caliper.runner.CaliperMain; +import net.sf.picard.reference.ReferenceSequenceFile; +import org.broad.tribble.Feature; +import org.broad.tribble.FeatureCodec; import org.broad.tribble.readers.AsciiLineReader; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.codecs.vcf.VCFCodec; +import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; import java.io.*; import java.util.*; @@ -46,24 +52,39 @@ public class VariantContextBenchmark extends SimpleBenchmark { @Param({"100"}) int nSamplesToTake; // set automatically by framework - @Param({"READ", "READ_SUBSET"}) - Operation operation; // set automatically by framework + @Param({"10"}) + int dupsToMerge; // set automatically by framework - @Param({"OF_SAMPLES"}) - SubContextOp subContextOp; // set automatically by framework + @Param + Operation operation; // set automatically by framework private String INPUT_STRING; public enum Operation { READ, - READ_SUBSET + SUBSET_TO_SAMPLES, + GET_TYPE, + GET_ID, + GET_GENOTYPES, + GET_ATTRIBUTE_STRING, + GET_ATTRIBUTE_INT, + GET_N_SAMPLES, + GET_GENOTYPES_FOR_SAMPLES, + GET_GENOTYPES_IN_ORDER_OF_NAME, + CALC_GENOTYPE_COUNTS, + MERGE } - public enum SubContextOp { - OF_SAMPLES - } + private GenomeLocParser b37GenomeLocParser; @Override protected void setUp() { + try { + ReferenceSequenceFile seq = new CachingIndexedFastaSequenceFile(new File(BaseTest.b37KGReference)); + b37GenomeLocParser = new GenomeLocParser(seq); + } catch ( FileNotFoundException e) { + throw new RuntimeException(e); + } + // read it into a String so that we don't try to benchmark IO issues try { FileInputStream s = new FileInputStream(new File(vcfFile)); @@ -83,52 +104,266 @@ public class VariantContextBenchmark extends SimpleBenchmark { } } - private void parseGenotypes(VCFCodec codec, Operation op, SubContextOp subop ) { + private interface FunctionToBenchmark { + public void run(T vc); + } + + private void runBenchmark(FeatureCodec codec, FunctionToBenchmark func) { try { InputStream is = new ByteArrayInputStream(INPUT_STRING.getBytes()); AsciiLineReader lineReader = new AsciiLineReader(is); codec.readHeader(lineReader); int counter = 0; - Set samples = null; while (counter++ < linesToRead ) { String line = lineReader.readLine(); if ( line == null ) break; - VariantContext vc = (VariantContext)codec.decode(line); - if ( samples == null ) { - samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); - } - - if ( op == Operation.READ_SUBSET) - processOneVC(vc, samples, subop); + T vc = codec.decode(line); + func.run(vc); } } catch (Exception e) { System.out.println("Benchmarking run failure because of " + e.getMessage()); } } - public void timeMe(int rep) { - for ( int i = 0; i < rep; i++ ) - parseGenotypes(new VCFCodec(), operation, subContextOp); + public void timeV14(int rep) { + for ( int i = 0; i < rep; i++ ) { + FunctionToBenchmark func = getV14FunctionToBenchmark(); + FeatureCodec codec = new VCFCodec(); + runBenchmark(codec, func); + } + } + + public FunctionToBenchmark getV14FunctionToBenchmark() { + switch ( operation ) { + case READ: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + ; // empty operation + } + }; + case SUBSET_TO_SAMPLES: + return new FunctionToBenchmark() { + Set samples; + public void run(final VariantContext vc) { + if ( samples == null ) + samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); + VariantContext sub = vc.subContextFromSamples(samples); + sub.getNSamples(); + } + }; + case GET_TYPE: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getType(); + } + }; + case GET_ID: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getID(); + } + }; + case GET_GENOTYPES: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getGenotypes().size(); + } + }; + + case GET_GENOTYPES_FOR_SAMPLES: + return new FunctionToBenchmark() { + Set samples; + public void run(final VariantContext vc) { + if ( samples == null ) + samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); + vc.getGenotypes(samples).size(); + } + }; + + case GET_ATTRIBUTE_STRING: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getAttribute("AN", null); + } + }; + + case GET_ATTRIBUTE_INT: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getAttributeAsInt("AC", 0); + } + }; + + case GET_N_SAMPLES: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getNSamples(); + } + }; + + case GET_GENOTYPES_IN_ORDER_OF_NAME: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + ; // TODO - TEST IS BROKEN +// int n = 0; +// for ( final Genotype g: vc.getGenotypesSortedByName() ) n++; + } + }; + + case CALC_GENOTYPE_COUNTS: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + vc.getHetCount(); + } + }; + + case MERGE: + return new FunctionToBenchmark() { + public void run(final VariantContext vc) { + List toMerge = new ArrayList(); + + for ( int i = 0; i < dupsToMerge; i++ ) { + GenotypeCollection gc = GenotypeCollection.create(vc.getNSamples()); + for ( final Genotype g : vc.getGenotypes() ) { + gc.add(new Genotype(g.getSampleName()+"_"+i, g)); + } + toMerge.add(VariantContext.modifyGenotypes(vc, gc)); + } + + VariantContextUtils.simpleMerge(b37GenomeLocParser, toMerge, null, + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.UNSORTED, + true, false, "set", false, true); + } + }; + + default: throw new IllegalArgumentException("Unexpected operation " + operation); + } + } + + public void timeV13(int rep) { + for ( int i = 0; i < rep; i++ ) { + FunctionToBenchmark func = getV13FunctionToBenchmark(); + FeatureCodec codec = new org.broadinstitute.sting.utils.variantcontext.v13.VCFCodec(); + runBenchmark(codec, func); + } + } + + public FunctionToBenchmark getV13FunctionToBenchmark() { + switch ( operation ) { + case READ: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + ; // empty operation + } + }; + case SUBSET_TO_SAMPLES: + return new FunctionToBenchmark() { + List samples; + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + if ( samples == null ) + samples = new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake); + org.broadinstitute.sting.utils.variantcontext.v13.VariantContext sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values()); + sub.getNSamples(); + } + }; + + case GET_TYPE: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getType(); + } + }; + case GET_ID: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getID(); + } + }; + case GET_GENOTYPES: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getGenotypes().size(); + } + }; + + case GET_GENOTYPES_FOR_SAMPLES: + return new FunctionToBenchmark() { + Set samples; + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + if ( samples == null ) + samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); + vc.getGenotypes(samples).size(); + } + }; + + case GET_ATTRIBUTE_STRING: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getAttribute("AN", null); + } + }; + + case GET_ATTRIBUTE_INT: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getAttributeAsInt("AC", 0); + } + }; + + case GET_N_SAMPLES: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getNSamples(); + } + }; + + case GET_GENOTYPES_IN_ORDER_OF_NAME: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + ; // TODO - TEST IS BROKEN + //vc.getGenotypesSortedByName(); + } + }; + + case CALC_GENOTYPE_COUNTS: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + vc.getHetCount(); + } + }; + + case MERGE: + return new FunctionToBenchmark() { + public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { + List toMerge = new ArrayList(); + + for ( int i = 0; i < dupsToMerge; i++ ) { + Map gc = new HashMap(); + for ( final org.broadinstitute.sting.utils.variantcontext.v13.Genotype g : vc.getGenotypes().values() ) { + String name = g.getSampleName()+"_"+i; + gc.put(name, new org.broadinstitute.sting.utils.variantcontext.v13.Genotype(name, + g.getAlleles(), g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased(), g.getLikelihoods().getAsVector())); + toMerge.add(org.broadinstitute.sting.utils.variantcontext.v13.VariantContext.modifyGenotypes(vc, gc)); + } + } + + org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.simpleMerge(b37GenomeLocParser, + toMerge, null, + org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.GenotypeMergeType.UNSORTED, + true, false, "set", false, true); + } + }; + + default: throw new IllegalArgumentException("Unexpected operation " + operation); + } } public static void main(String[] args) { CaliperMain.main(VariantContextBenchmark.class, args); } - - private static final void processOneVC(VariantContext vc, Set samples, SubContextOp subop) { - VariantContext sub; - - switch ( subop ) { - case OF_SAMPLES: - sub = vc.subContextFromSamples(samples, vc.getAlleles()); - break; - default: - throw new RuntimeException("Unexpected op: " + subop); - } - - sub.getNSamples(); - } } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java new file mode 100755 index 000000000..5310313e0 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java @@ -0,0 +1,635 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.apache.log4j.Logger; +import org.broad.tribble.Feature; +import org.broad.tribble.FeatureCodec; +import org.broad.tribble.NameAwareCodec; +import org.broad.tribble.TribbleException; +import org.broad.tribble.readers.LineReader; +import org.broad.tribble.util.BlockCompressedInputStream; +import org.broad.tribble.util.ParsingUtils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; + +import java.io.*; +import java.util.*; +import java.util.zip.GZIPInputStream; + + +abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, VCFParser { + + protected final static Logger log = Logger.getLogger(VCFCodec.class); + protected final static int NUM_STANDARD_FIELDS = 8; // INFO is the 8th column + + protected VCFHeaderVersion version; + + // we have to store the list of strings that make up the header until they're needed + protected VCFHeader header = null; + + // a mapping of the allele + protected Map> alleleMap = new HashMap>(3); + + // for ParsingUtils.split + protected String[] GTValueArray = new String[100]; + protected String[] genotypeKeyArray = new String[100]; + protected String[] infoFieldArray = new String[1000]; + protected String[] infoValueArray = new String[1000]; + + // for performance testing purposes + public static boolean validate = true; + + // a key optimization -- we need a per thread string parts array, so we don't allocate a big array over and over + // todo: make this thread safe? + protected String[] parts = null; + protected String[] genotypeParts = null; + + // for performance we cache the hashmap of filter encodings for quick lookup + protected HashMap> filterHash = new HashMap>(); + + // a mapping of the VCF fields to their type, filter fields, and format fields, for quick lookup to validate against + TreeMap infoFields = new TreeMap(); + TreeMap formatFields = new TreeMap(); + Set filterFields = new HashSet(); + + // we store a name to give to each of the variant contexts we emit + protected String name = "Unknown"; + + protected int lineNo = 0; + + protected Map stringCache = new HashMap(); + + + /** + * @param reader the line reader to take header lines from + * @return the number of header lines + */ + public abstract Object readHeader(LineReader reader); + + /** + * create a genotype map + * @param str the string + * @param alleles the list of alleles + * @param chr chrom + * @param pos position + * @return a mapping of sample name to genotype object + */ + public abstract Map createGenotypeMap(String str, List alleles, String chr, int pos); + + + /** + * parse the filter string, first checking to see if we already have parsed it in a previous attempt + * @param filterString the string to parse + * @return a set of the filters applied + */ + protected abstract Set parseFilters(String filterString); + + /** + * create a VCF header + * @param headerStrings a list of strings that represent all the ## entries + * @param line the single # line (column names) + * @return the count of header lines + */ + protected Object createHeader(List headerStrings, String line) { + + headerStrings.add(line); + + Set metaData = new TreeSet(); + Set auxTags = new LinkedHashSet(); + // iterate over all the passed in strings + for ( String str : headerStrings ) { + if ( !str.startsWith(VCFHeader.METADATA_INDICATOR) ) { + String[] strings = str.substring(1).split(VCFConstants.FIELD_SEPARATOR); + if ( strings.length < VCFHeader.HEADER_FIELDS.values().length ) + throw new TribbleException.InvalidHeader("there are not enough columns present in the header line: " + str); + + int arrayIndex = 0; + for (VCFHeader.HEADER_FIELDS field : VCFHeader.HEADER_FIELDS.values()) { + try { + if (field != VCFHeader.HEADER_FIELDS.valueOf(strings[arrayIndex])) + throw new TribbleException.InvalidHeader("we were expecting column name '" + field + "' but we saw '" + strings[arrayIndex] + "'"); + } catch (IllegalArgumentException e) { + throw new TribbleException.InvalidHeader("unknown column name '" + strings[arrayIndex] + "'; it does not match a legal column header name."); + } + arrayIndex++; + } + + boolean sawFormatTag = false; + if ( arrayIndex < strings.length ) { + if ( !strings[arrayIndex].equals("FORMAT") ) + throw new TribbleException.InvalidHeader("we were expecting column name 'FORMAT' but we saw '" + strings[arrayIndex] + "'"); + sawFormatTag = true; + arrayIndex++; + } + + while ( arrayIndex < strings.length ) + auxTags.add(strings[arrayIndex++]); + + if ( sawFormatTag && auxTags.size() == 0 ) + throw new UserException.MalformedVCFHeader("The FORMAT field was provided but there is no genotype/sample data"); + + } else { + if ( str.startsWith("##INFO=") ) { + VCFInfoHeaderLine info = new VCFInfoHeaderLine(str.substring(7),version); + metaData.add(info); + infoFields.put(info.getName(), info.getType()); + } else if ( str.startsWith("##FILTER=") ) { + VCFFilterHeaderLine filter = new VCFFilterHeaderLine(str.substring(9),version); + metaData.add(filter); + filterFields.add(filter.getName()); + } else if ( str.startsWith("##FORMAT=") ) { + VCFFormatHeaderLine format = new VCFFormatHeaderLine(str.substring(9),version); + metaData.add(format); + formatFields.put(format.getName(), format.getType()); + } else { + int equals = str.indexOf("="); + if ( equals != -1 ) + metaData.add(new VCFHeaderLine(str.substring(2, equals), str.substring(equals+1))); + } + } + } + + header = new VCFHeader(metaData, auxTags); + return header; + } + + /** + * the fast decode function + * @param line the line of text for the record + * @return a feature, (not guaranteed complete) that has the correct start and stop + */ + public Feature decodeLoc(String line) { + lineNo++; + + // the same line reader is not used for parsing the header and parsing lines, if we see a #, we've seen a header line + if (line.startsWith(VCFHeader.HEADER_INDICATOR)) return null; + + // our header cannot be null, we need the genotype sample names and counts + if (header == null) throw new ReviewedStingException("VCF Header cannot be null when decoding a record"); + + final String[] locParts = new String[6]; + int nParts = ParsingUtils.split(line, locParts, VCFConstants.FIELD_SEPARATOR_CHAR, true); + + if ( nParts != 6 ) + throw new UserException.MalformedVCF("there aren't enough columns for line " + line, lineNo); + + // get our alleles (because the end position depends on them) + final String ref = getCachedString(locParts[3].toUpperCase()); + final String alts = getCachedString(locParts[4].toUpperCase()); + final List alleles = parseAlleles(ref, alts, lineNo); + + // find out our location + final int start = Integer.valueOf(locParts[1]); + int stop = start; + + // ref alleles don't need to be single bases for monomorphic sites + if ( alleles.size() == 1 ) { + stop = start + alleles.get(0).length() - 1; + } else if ( !isSingleNucleotideEvent(alleles) ) { + stop = clipAlleles(start, ref, alleles, null, lineNo); + } + + return new VCFLocFeature(locParts[0], start, stop); + } + + private final static class VCFLocFeature implements Feature { + + final String chr; + final int start, stop; + + private VCFLocFeature(String chr, int start, int stop) { + this.chr = chr; + this.start = start; + this.stop = stop; + } + + public String getChr() { return chr; } + public int getStart() { return start; } + public int getEnd() { return stop; } + } + + + /** + * decode the line into a feature (VariantContext) + * @param line the line + * @return a VariantContext + */ + public Feature decode(String line) { + // the same line reader is not used for parsing the header and parsing lines, if we see a #, we've seen a header line + if (line.startsWith(VCFHeader.HEADER_INDICATOR)) return null; + + // our header cannot be null, we need the genotype sample names and counts + if (header == null) throw new ReviewedStingException("VCF Header cannot be null when decoding a record"); + + if (parts == null) + parts = new String[Math.min(header.getColumnCount(), NUM_STANDARD_FIELDS+1)]; + + int nParts = ParsingUtils.split(line, parts, VCFConstants.FIELD_SEPARATOR_CHAR, true); + + // if we have don't have a header, or we have a header with no genotyping data check that we have eight columns. Otherwise check that we have nine (normal colummns + genotyping data) + if (( (header == null || !header.hasGenotypingData()) && nParts != NUM_STANDARD_FIELDS) || + (header != null && header.hasGenotypingData() && nParts != (NUM_STANDARD_FIELDS + 1)) ) + throw new UserException.MalformedVCF("there aren't enough columns for line " + line + " (we expected " + (header == null ? NUM_STANDARD_FIELDS : NUM_STANDARD_FIELDS + 1) + + " tokens, and saw " + nParts + " )", lineNo); + + return parseVCFLine(parts); + } + + protected void generateException(String message) { + throw new UserException.MalformedVCF(message, lineNo); + } + + protected static void generateException(String message, int lineNo) { + throw new UserException.MalformedVCF(message, lineNo); + } + + /** + * parse out the VCF line + * + * @param parts the parts split up + * @return a variant context object + */ + private VariantContext parseVCFLine(String[] parts) { + // increment the line count + lineNo++; + + // parse out the required fields + String contig = getCachedString(parts[0]); + int pos = Integer.valueOf(parts[1]); + String id = null; + if ( parts[2].length() == 0 ) + generateException("The VCF specification requires a valid ID field"); + else if ( parts[2].equals(VCFConstants.EMPTY_ID_FIELD) ) + id = VCFConstants.EMPTY_ID_FIELD; + else + id = new String(parts[2]); + String ref = getCachedString(parts[3].toUpperCase()); + String alts = getCachedString(parts[4].toUpperCase()); + Double qual = parseQual(parts[5]); + String filter = getCachedString(parts[6]); + String info = new String(parts[7]); + + // get our alleles, filters, and setup an attribute map + List alleles = parseAlleles(ref, alts, lineNo); + Set filters = parseFilters(filter); + Map attributes = parseInfo(info, id); + + // find out our current location, and clip the alleles down to their minimum length + int loc = pos; + // ref alleles don't need to be single bases for monomorphic sites + if ( alleles.size() == 1 ) { + loc = pos + alleles.get(0).length() - 1; + } else if ( !isSingleNucleotideEvent(alleles) ) { + ArrayList newAlleles = new ArrayList(); + loc = clipAlleles(pos, ref, alleles, newAlleles, lineNo); + alleles = newAlleles; + } + + // do we have genotyping data + if (parts.length > NUM_STANDARD_FIELDS) { + attributes.put(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, new String(parts[8])); + attributes.put(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY, this); + } + + VariantContext vc = null; + try { + vc = new VariantContext(name, contig, pos, loc, alleles, qual, filters, attributes, ref.getBytes()[0]); + } catch (Exception e) { + generateException(e.getMessage()); + } + + // did we resort the sample names? If so, we need to load the genotype data + if ( !header.samplesWereAlreadySorted() ) + vc.getGenotypes(); + + return vc; + } + + /** + * + * @return the type of record + */ + public Class getFeatureType() { + return VariantContext.class; + } + + /** + * get the name of this codec + * @return our set name + */ + public String getName() { + return name; + } + + /** + * set the name of this codec + * @param name new name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return a cached copy of the supplied string. + * + * @param str string + * @return interned string + */ + protected String getCachedString(String str) { + String internedString = stringCache.get(str); + if ( internedString == null ) { + internedString = new String(str); + stringCache.put(internedString, internedString); + } + return internedString; + } + + /** + * parse out the info fields + * @param infoField the fields + * @param id the indentifier + * @return a mapping of keys to objects + */ + private Map parseInfo(String infoField, String id) { + Map attributes = new HashMap(); + + if ( infoField.length() == 0 ) + generateException("The VCF specification requires a valid info field"); + + if ( !infoField.equals(VCFConstants.EMPTY_INFO_FIELD) ) { + if ( infoField.indexOf("\t") != -1 || infoField.indexOf(" ") != -1 ) + generateException("The VCF specification does not allow for whitespace in the INFO field"); + + int infoFieldSplitSize = ParsingUtils.split(infoField, infoFieldArray, VCFConstants.INFO_FIELD_SEPARATOR_CHAR, false); + for (int i = 0; i < infoFieldSplitSize; i++) { + String key; + Object value; + + int eqI = infoFieldArray[i].indexOf("="); + if ( eqI != -1 ) { + key = infoFieldArray[i].substring(0, eqI); + String str = infoFieldArray[i].substring(eqI+1); + + // split on the INFO field separator + int infoValueSplitSize = ParsingUtils.split(str, infoValueArray, VCFConstants.INFO_FIELD_ARRAY_SEPARATOR_CHAR, false); + if ( infoValueSplitSize == 1 ) { + value = infoValueArray[0]; + } else { + ArrayList valueList = new ArrayList(infoValueSplitSize); + for ( int j = 0; j < infoValueSplitSize; j++ ) + valueList.add(infoValueArray[j]); + value = valueList; + } + } else { + key = infoFieldArray[i]; + value = true; + } + + attributes.put(key, value); + } + } + + if ( ! id.equals(VCFConstants.EMPTY_ID_FIELD) ) + attributes.put(VariantContext.ID_KEY, id); + return attributes; + } + + /** + * create a an allele from an index and an array of alleles + * @param index the index + * @param alleles the alleles + * @return an Allele + */ + protected static Allele oneAllele(String index, List alleles) { + if ( index.equals(VCFConstants.EMPTY_ALLELE) ) + return Allele.NO_CALL; + int i = Integer.valueOf(index); + if ( i >= alleles.size() ) + throw new TribbleException.InternalCodecException("The allele with index " + index + " is not defined in the REF/ALT columns in the record"); + return alleles.get(i); + } + + + /** + * parse genotype alleles from the genotype string + * @param GT GT string + * @param alleles list of possible alleles + * @param cache cache of alleles for GT + * @return the allele list for the GT string + */ + protected static List parseGenotypeAlleles(String GT, List alleles, Map> cache) { + // cache results [since they are immutable] and return a single object for each genotype + List GTAlleles = cache.get(GT); + + if ( GTAlleles == null ) { + StringTokenizer st = new StringTokenizer(GT, VCFConstants.PHASING_TOKENS); + GTAlleles = new ArrayList(st.countTokens()); + while ( st.hasMoreTokens() ) { + String genotype = st.nextToken(); + GTAlleles.add(oneAllele(genotype, alleles)); + } + cache.put(GT, GTAlleles); + } + + return GTAlleles; + } + + /** + * parse out the qual value + * @param qualString the quality string + * @return return a double + */ + protected static Double parseQual(String qualString) { + // if we're the VCF 4 missing char, return immediately + if ( qualString.equals(VCFConstants.MISSING_VALUE_v4)) + return VariantContext.NO_NEG_LOG_10PERROR; + + Double val = Double.valueOf(qualString); + + // check to see if they encoded the missing qual score in VCF 3 style, with either the -1 or -1.0. check for val < 0 to save some CPU cycles + if ((val < 0) && (Math.abs(val - VCFConstants.MISSING_QUALITY_v3_DOUBLE) < VCFConstants.VCF_ENCODING_EPSILON)) + return VariantContext.NO_NEG_LOG_10PERROR; + + // scale and return the value + return val / 10.0; + } + + /** + * parse out the alleles + * @param ref the reference base + * @param alts a string of alternates to break into alleles + * @param lineNo the line number for this record + * @return a list of alleles, and a pair of the shortest and longest sequence + */ + protected static List parseAlleles(String ref, String alts, int lineNo) { + List alleles = new ArrayList(2); // we are almost always biallelic + // ref + checkAllele(ref, true, lineNo); + Allele refAllele = Allele.create(ref, true); + alleles.add(refAllele); + + if ( alts.indexOf(",") == -1 ) // only 1 alternatives, don't call string split + parseSingleAltAllele(alleles, alts, lineNo); + else + for ( String alt : alts.split(",") ) + parseSingleAltAllele(alleles, alt, lineNo); + + return alleles; + } + + /** + * check to make sure the allele is an acceptable allele + * @param allele the allele to check + * @param isRef are we the reference allele? + * @param lineNo the line number for this record + */ + private static void checkAllele(String allele, boolean isRef, int lineNo) { + if ( allele == null || allele.length() == 0 ) + generateException("Empty alleles are not permitted in VCF records", lineNo); + + if ( isSymbolicAllele(allele) ) { + if ( isRef ) { + generateException("Symbolic alleles not allowed as reference allele: " + allele, lineNo); + } + } else { + // check for VCF3 insertions or deletions + if ( (allele.charAt(0) == VCFConstants.DELETION_ALLELE_v3) || (allele.charAt(0) == VCFConstants.INSERTION_ALLELE_v3) ) + generateException("Insertions/Deletions are not supported when reading 3.x VCF's. Please" + + " convert your file to VCF4 using VCFTools, available at http://vcftools.sourceforge.net/index.html", lineNo); + + if (!Allele.acceptableAlleleBases(allele)) + generateException("Unparsable vcf record with allele " + allele, lineNo); + + if ( isRef && allele.equals(VCFConstants.EMPTY_ALLELE) ) + generateException("The reference allele cannot be missing", lineNo); + } + } + + /** + * return true if this is a symbolic allele (e.g. ) otherwise false + * @param allele the allele to check + * @return true if the allele is a symbolic allele, otherwise false + */ + private static boolean isSymbolicAllele(String allele) { + return (allele != null && allele.startsWith("<") && allele.endsWith(">") && allele.length() > 2); + } + + /** + * parse a single allele, given the allele list + * @param alleles the alleles available + * @param alt the allele to parse + * @param lineNo the line number for this record + */ + private static void parseSingleAltAllele(List alleles, String alt, int lineNo) { + checkAllele(alt, false, lineNo); + + Allele allele = Allele.create(alt, false); + if ( ! allele.isNoCall() ) + alleles.add(allele); + } + + protected static boolean isSingleNucleotideEvent(List alleles) { + for ( Allele a : alleles ) { + if ( a.length() != 1 ) + return false; + } + return true; + } + + public static int computeForwardClipping(List unclippedAlleles, String ref) { + boolean clipping = true; + + for ( Allele a : unclippedAlleles ) { + if ( a.isSymbolic() ) + continue; + + if ( a.length() < 1 || (a.getBases()[0] != ref.getBytes()[0]) ) { + clipping = false; + break; + } + } + + return (clipping) ? 1 : 0; + } + + protected static int computeReverseClipping(List unclippedAlleles, String ref, int forwardClipping, int lineNo) { + int clipping = 0; + boolean stillClipping = true; + + while ( stillClipping ) { + for ( Allele a : unclippedAlleles ) { + if ( a.isSymbolic() ) + continue; + + if ( a.length() - clipping <= forwardClipping || a.length() - forwardClipping == 0 ) + stillClipping = false; + else if ( ref.length() == clipping ) + generateException("bad alleles encountered", lineNo); + else if ( a.getBases()[a.length()-clipping-1] != ref.getBytes()[ref.length()-clipping-1] ) + stillClipping = false; + } + if ( stillClipping ) + clipping++; + } + + return clipping; + } + /** + * clip the alleles, based on the reference + * + * @param position the unadjusted start position (pre-clipping) + * @param ref the reference string + * @param unclippedAlleles the list of unclipped alleles + * @param clippedAlleles output list of clipped alleles + * @param lineNo the current line number in the file + * @return the new reference end position of this event + */ + protected static int clipAlleles(int position, String ref, List unclippedAlleles, List clippedAlleles, int lineNo) { + + int forwardClipping = computeForwardClipping(unclippedAlleles, ref); + int reverseClipping = computeReverseClipping(unclippedAlleles, ref, forwardClipping, lineNo); + + if ( clippedAlleles != null ) { + for ( Allele a : unclippedAlleles ) { + if ( a.isSymbolic() ) { + clippedAlleles.add(a); + } else { + clippedAlleles.add(Allele.create(Arrays.copyOfRange(a.getBases(), forwardClipping, a.getBases().length-reverseClipping), a.isReference())); + } + } + } + + // the new reference length + int refLength = ref.length() - reverseClipping; + + return position+Math.max(refLength - 1,0); + } + + public final static boolean canDecodeFile(final File potentialInput, final String MAGIC_HEADER_LINE) { + try { + return isVCFStream(new FileInputStream(potentialInput), MAGIC_HEADER_LINE) || + isVCFStream(new GZIPInputStream(new FileInputStream(potentialInput)), MAGIC_HEADER_LINE) || + isVCFStream(new BlockCompressedInputStream(new FileInputStream(potentialInput)), MAGIC_HEADER_LINE); + } catch ( FileNotFoundException e ) { + return false; + } catch ( IOException e ) { + return false; + } + } + + private final static boolean isVCFStream(final InputStream stream, final String MAGIC_HEADER_LINE) { + try { + byte[] buff = new byte[MAGIC_HEADER_LINE.length()]; + int nread = stream.read(buff, 0, MAGIC_HEADER_LINE.length()); + boolean eq = Arrays.equals(buff, MAGIC_HEADER_LINE.getBytes()); + return eq; +// String firstLine = new String(buff); +// return firstLine.startsWith(MAGIC_HEADER_LINE); + } catch ( IOException e ) { + return false; + } catch ( RuntimeException e ) { + return false; + } finally { + try { stream.close(); } catch ( IOException e ) {} + } + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java new file mode 100755 index 000000000..f43cd7b98 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java @@ -0,0 +1,456 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Immutable representation of an allele + * + * Types of alleles: + * + * Ref: a t C g a // C is the reference base + * + * : a t G g a // C base is a G in some individuals + * + * : a t - g a // C base is deleted w.r.t. the reference + * + * : a t CAg a // A base is inserted w.r.t. the reference sequence + * + * In these cases, where are the alleles? + * + * SNP polymorphism of C/G -> { C , G } -> C is the reference allele + * 1 base deletion of C -> { C , - } -> C is the reference allele + * 1 base insertion of A -> { - ; A } -> Null is the reference allele + * + * Suppose I see a the following in the population: + * + * Ref: a t C g a // C is the reference base + * : a t G g a // C base is a G in some individuals + * : a t - g a // C base is deleted w.r.t. the reference + * + * How do I represent this? There are three segregating alleles: + * + * { C , G , - } + * + * Now suppose I have this more complex example: + * + * Ref: a t C g a // C is the reference base + * : a t - g a + * : a t - - a + * : a t CAg a + * + * There are actually four segregating alleles: + * + * { C g , - g, - -, and CAg } over bases 2-4 + * + * However, the molecular equivalence explicitly listed above is usually discarded, so the actual + * segregating alleles are: + * + * { C g, g, -, C a g } + * + * Critically, it should be possible to apply an allele to a reference sequence to create the + * correct haplotype sequence: + * + * Allele + reference => haplotype + * + * For convenience, we are going to create Alleles where the GenomeLoc of the allele is stored outside of the + * Allele object itself. So there's an idea of an A/C polymorphism independent of it's surrounding context. + * + * Given list of alleles it's possible to determine the "type" of the variation + * + * A / C @ loc => SNP with + * - / A => INDEL + * + * If you know where allele is the reference, you can determine whether the variant is an insertion or deletion. + * + * Alelle also supports is concept of a NO_CALL allele. This Allele represents a haplotype that couldn't be + * determined. This is usually represented by a '.' allele. + * + * Note that Alleles store all bases as bytes, in **UPPER CASE**. So 'atc' == 'ATC' from the perspective of an + * Allele. + + * @author ebanks, depristo + */ +class Allele implements Comparable { + private static final byte[] EMPTY_ALLELE_BASES = new byte[0]; + + private boolean isRef = false; + private boolean isNull = false; + private boolean isNoCall = false; + private boolean isSymbolic = false; + + private byte[] bases = null; + + public final static String NULL_ALLELE_STRING = "-"; + public final static String NO_CALL_STRING = "."; + /** A generic static NO_CALL allele for use */ + + // no public way to create an allele + private Allele(byte[] bases, boolean isRef) { + // standardize our representation of null allele and bases + if ( wouldBeNullAllele(bases) ) { + bases = EMPTY_ALLELE_BASES; + isNull = true; + } else if ( wouldBeNoCallAllele(bases) ) { + bases = EMPTY_ALLELE_BASES; + isNoCall = true; + if ( isRef ) throw new IllegalArgumentException("Cannot tag a NoCall allele as the reference allele"); + } else if ( wouldBeSymbolicAllele(bases) ) { + isSymbolic = true; + if ( isRef ) throw new IllegalArgumentException("Cannot tag a symbolic allele as the reference allele"); + } +// else +// bases = new String(bases).toUpperCase().getBytes(); // todo -- slow performance + + this.isRef = isRef; + this.bases = bases; + + if ( ! acceptableAlleleBases(bases) ) + throw new IllegalArgumentException("Unexpected base in allele bases \'" + new String(bases)+"\'"); + } + + private Allele(String bases, boolean isRef) { + this(bases.getBytes(), isRef); + } + + + private final static Allele REF_A = new Allele("A", true); + private final static Allele ALT_A = new Allele("A", false); + private final static Allele REF_C = new Allele("C", true); + private final static Allele ALT_C = new Allele("C", false); + private final static Allele REF_G = new Allele("G", true); + private final static Allele ALT_G = new Allele("G", false); + private final static Allele REF_T = new Allele("T", true); + private final static Allele ALT_T = new Allele("T", false); + private final static Allele REF_N = new Allele("N", true); + private final static Allele ALT_N = new Allele("N", false); + private final static Allele REF_NULL = new Allele(NULL_ALLELE_STRING, true); + private final static Allele ALT_NULL = new Allele(NULL_ALLELE_STRING, false); + public final static Allele NO_CALL = new Allele(NO_CALL_STRING, false); + + // --------------------------------------------------------------------------------------------------------- + // + // creation routines + // + // --------------------------------------------------------------------------------------------------------- + + /** + * Create a new Allele that includes bases and if tagged as the reference allele if isRef == true. If bases + * == '-', a Null allele is created. If bases == '.', a no call Allele is created. + * + * @param bases the DNA sequence of this variation, '-', of '.' + * @param isRef should we make this a reference allele? + * @throws IllegalArgumentException if bases contains illegal characters or is otherwise malformated + */ + public static Allele create(byte[] bases, boolean isRef) { + if ( bases == null ) + throw new IllegalArgumentException("create: the Allele base string cannot be null; use new Allele() or new Allele(\"\") to create a Null allele"); + + if ( bases.length == 1 ) { + // optimization to return a static constant Allele for each single base object + switch (bases[0]) { + case '.': + if ( isRef ) throw new IllegalArgumentException("Cannot tag a NoCall allele as the reference allele"); + return NO_CALL; + case '-': return isRef ? REF_NULL : ALT_NULL; + case 'A': case 'a' : return isRef ? REF_A : ALT_A; + case 'C': case 'c' : return isRef ? REF_C : ALT_C; + case 'G': case 'g' : return isRef ? REF_G : ALT_G; + case 'T': case 't' : return isRef ? REF_T : ALT_T; + case 'N': case 'n' : return isRef ? REF_N : ALT_N; + default: throw new IllegalArgumentException("Illegal base: " + (char)bases[0]); + } + } else { + return new Allele(bases, isRef); + } + } + + public static Allele create(byte base, boolean isRef) { +// public Allele(byte base, boolean isRef) { + return create( new byte[]{ base }, isRef); + } + + public static Allele create(byte base) { + return create( base, false ); + } + + public static Allele extend(Allele left, byte[] right) { + if (left.isSymbolic()) + throw new IllegalArgumentException("Cannot extend a symbolic allele"); + byte[] bases = null; + if ( left.length() == 0 ) + bases = right; + else { + bases = new byte[left.length() + right.length]; + System.arraycopy(left.getBases(), 0, bases, 0, left.length()); + System.arraycopy(right, 0, bases, left.length(), right.length); + } + + return create(bases, left.isReference()); + } + + /** + * @param bases bases representing an allele + * @return true if the bases represent the null allele + */ + public static boolean wouldBeNullAllele(byte[] bases) { + return (bases.length == 1 && bases[0] == '-') || bases.length == 0; + } + + /** + * @param bases bases representing an allele + * @return true if the bases represent the NO_CALL allele + */ + public static boolean wouldBeNoCallAllele(byte[] bases) { + return bases.length == 1 && bases[0] == '.'; + } + + /** + * @param bases bases representing an allele + * @return true if the bases represent a symbolic allele + */ + public static boolean wouldBeSymbolicAllele(byte[] bases) { + return bases.length > 2 && bases[0] == '<' && bases[bases.length-1] == '>'; + } + + /** + * @param bases bases representing an allele + * @return true if the bases represent the well formatted allele + */ + public static boolean acceptableAlleleBases(String bases) { + return acceptableAlleleBases(bases.getBytes()); + } + + /** + * @param bases bases representing an allele + * @return true if the bases represent the well formatted allele + */ + public static boolean acceptableAlleleBases(byte[] bases) { + if ( wouldBeNullAllele(bases) || wouldBeNoCallAllele(bases) || wouldBeSymbolicAllele(bases) ) + return true; + + for ( int i = 0; i < bases.length; i++ ) { + switch (bases[i]) { + case 'A': case 'C': case 'G': case 'T': case 'N' : case 'a': case 'c': case 'g': case 't': case 'n' : + break; + default: + return false; + } + } + + return true; + } + + /** + * @see Allele(byte[], boolean) + * + * @param bases bases representing an allele + * @param isRef is this the reference allele? + */ + public static Allele create(String bases, boolean isRef) { + //public Allele(String bases, boolean isRef) { + return create(bases.getBytes(), isRef); + } + + + /** + * Creates a non-Ref allele. @see Allele(byte[], boolean) for full information + * + * @param bases bases representing an allele + */ + public static Allele create(String bases) { + return create(bases, false); + } + + /** + * Creates a non-Ref allele. @see Allele(byte[], boolean) for full information + * + * @param bases bases representing an allele + */ + public static Allele create(byte[] bases) { + return create(bases, false); + //this(bases, false); + } + + // --------------------------------------------------------------------------------------------------------- + // + // accessor routines + // + // --------------------------------------------------------------------------------------------------------- + + //Returns true if this is the null allele + public boolean isNull() { return isNull; } + // Returns true if this is not the null allele + public boolean isNonNull() { return ! isNull(); } + + // Returns true if this is the NO_CALL allele + public boolean isNoCall() { return isNoCall; } + // Returns true if this is not the NO_CALL allele + public boolean isCalled() { return ! isNoCall(); } + + // Returns true if this Allele is the reference allele + public boolean isReference() { return isRef; } + // Returns true if this Allele is not the reference allele + public boolean isNonReference() { return ! isReference(); } + + // Returns true if this Allele is symbolic (i.e. no well-defined base sequence) + public boolean isSymbolic() { return isSymbolic; } + + // Returns a nice string representation of this object + public String toString() { + return (isNull() ? NULL_ALLELE_STRING : ( isNoCall() ? NO_CALL_STRING : getDisplayString() )) + (isReference() ? "*" : ""); + } + + /** + * Return the DNA bases segregating in this allele. Note this isn't reference polarized, + * so the Null allele is represented by a vector of length 0 + * + * @return the segregating bases + */ + public byte[] getBases() { return isSymbolic ? EMPTY_ALLELE_BASES : bases; } + + /** + * Return the DNA bases segregating in this allele in String format. + * This is useful, because toString() adds a '*' to reference alleles and getBases() returns garbage when you call toString() on it. + * + * @return the segregating bases + */ + public String getBaseString() { return new String(getBases()); } + + /** + * Return the printed representation of this allele. + * Same as getBaseString(), except for symbolic alleles. + * For symbolic alleles, the base string is empty while the display string contains . + * + * @return the allele string representation + */ + public String getDisplayString() { return new String(bases); } + + /** + * @param other the other allele + * + * @return true if these alleles are equal + */ + public boolean equals(Object other) { + return ( ! (other instanceof Allele) ? false : equals((Allele)other, false) ); + } + + /** + * @return hash code + */ + public int hashCode() { + int hash = 1; + for (int i = 0; i < bases.length; i++) + hash += (i+1) * bases[i]; + return hash; + } + + /** + * Returns true if this and other are equal. If ignoreRefState is true, then doesn't require both alleles has the + * same ref tag + * + * @param other allele to compare to + * @param ignoreRefState if true, ignore ref state in comparison + * @return true if this and other are equal + */ + public boolean equals(Allele other, boolean ignoreRefState) { + return this == other || (isRef == other.isRef || ignoreRefState) && isNull == other.isNull && isNoCall == other.isNoCall && (bases == other.bases || Arrays.equals(bases, other.bases)); + } + + /** + * @param test bases to test against + * + * @return true if this Alelle contains the same bases as test, regardless of its reference status; handles Null and NO_CALL alleles + */ + public boolean basesMatch(byte[] test) { return !isSymbolic && (bases == test || Arrays.equals(bases, test)); } + + /** + * @param test bases to test against + * + * @return true if this Alelle contains the same bases as test, regardless of its reference status; handles Null and NO_CALL alleles + */ + public boolean basesMatch(String test) { return basesMatch(test.toUpperCase().getBytes()); } + + /** + * @param test allele to test against + * + * @return true if this Alelle contains the same bases as test, regardless of its reference status; handles Null and NO_CALL alleles + */ + public boolean basesMatch(Allele test) { return basesMatch(test.getBases()); } + + /** + * @return the length of this allele. Null and NO_CALL alleles have 0 length. + */ + public int length() { + return isSymbolic ? 0 : bases.length; + } + + // --------------------------------------------------------------------------------------------------------- + // + // useful static functions + // + // --------------------------------------------------------------------------------------------------------- + + public static Allele getMatchingAllele(Collection allAlleles, String alleleBases) { + return getMatchingAllele(allAlleles, alleleBases.getBytes()); + } + + public static Allele getMatchingAllele(Collection allAlleles, byte[] alleleBases) { + for ( Allele a : allAlleles ) { + if ( a.basesMatch(alleleBases) ) { + return a; + } + } + + if ( wouldBeNoCallAllele(alleleBases) ) + return NO_CALL; + else + return null; // couldn't find anything + } + + public static List resolveAlleles(List possibleAlleles, List alleleStrings) { + List myAlleles = new ArrayList(alleleStrings.size()); + + for ( String alleleString : alleleStrings ) { + Allele allele = getMatchingAllele(possibleAlleles, alleleString); + + if ( allele == null ) { + if ( Allele.wouldBeNoCallAllele(alleleString.getBytes()) ) { + allele = create(alleleString); + } else { + throw new IllegalArgumentException("Allele " + alleleString + " not present in the list of alleles " + possibleAlleles); + } + } + + myAlleles.add(allele); + } + + return myAlleles; + } + + public int compareTo(Allele other) { + if ( isReference() && other.isNonReference() ) + return -1; + else if ( isNonReference() && other.isReference() ) + return 1; + else + return getBaseString().compareTo(other.getBaseString()); // todo -- potential performance issue + } + + public static boolean oneIsPrefixOfOther(Allele a1, Allele a2) { + if ( a1.isNull() || a2.isNull() ) + return true; + + if ( a2.length() >= a1.length() ) + return firstIsPrefixOfSecond(a1, a2); + else + return firstIsPrefixOfSecond(a2, a1); + } + + private static boolean firstIsPrefixOfSecond(Allele a1, Allele a2) { + String a1String = a1.getBaseString(); + return a2.getBaseString().substring(0, a1String.length()).equals(a1String); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java new file mode 100755 index 000000000..91aa3b1da --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java @@ -0,0 +1,349 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + + +import org.broad.tribble.util.ParsingUtils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.*; + +/** + * This class encompasses all the basic information about a genotype. It is immutable. + * + * @author Mark DePristo + */ +public class Genotype { + + public final static String PHASED_ALLELE_SEPARATOR = "|"; + public final static String UNPHASED_ALLELE_SEPARATOR = "/"; + + protected InferredGeneticContext commonInfo; + public final static double NO_NEG_LOG_10PERROR = InferredGeneticContext.NO_NEG_LOG_10PERROR; + protected List alleles = null; // new ArrayList(); + protected Type type = null; + + protected boolean isPhased = false; + protected boolean filtersWereAppliedToContext; + + public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased) { + this(sampleName, alleles, negLog10PError, filters, attributes, isPhased, null); + } + + public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { + if ( alleles != null ) + this.alleles = Collections.unmodifiableList(alleles); + commonInfo = new InferredGeneticContext(sampleName, negLog10PError, filters, attributes); + if ( log10Likelihoods != null ) + commonInfo.putAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); + filtersWereAppliedToContext = filters != null; + this.isPhased = isPhased; + validate(); + } + + /** + * Creates a new Genotype for sampleName with genotype according to alleles. + * @param sampleName + * @param alleles + * @param negLog10PError the confidence in these alleles + * @param log10Likelihoods a log10 likelihoods for each of the genotype combinations possible for alleles, in the standard VCF ordering, or null if not known + */ + public Genotype(String sampleName, List alleles, double negLog10PError, double[] log10Likelihoods) { + this(sampleName, alleles, negLog10PError, null, null, false, log10Likelihoods); + } + + public Genotype(String sampleName, List alleles, double negLog10PError) { + this(sampleName, alleles, negLog10PError, null, null, false); + } + + public Genotype(String sampleName, List alleles) { + this(sampleName, alleles, NO_NEG_LOG_10PERROR, null, null, false); + } + + + // --------------------------------------------------------------------------------------------------------- + // + // Partial-cloning routines (because Genotype is immutable). + // + // --------------------------------------------------------------------------------------------------------- + + public static Genotype modifyName(Genotype g, String name) { + return new Genotype(name, g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); + } + + public static Genotype modifyAttributes(Genotype g, Map attributes) { + return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attributes, g.isPhased()); + } + + public static Genotype modifyAlleles(Genotype g, List alleles) { + return new Genotype(g.getSampleName(), alleles, g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); + } + + /** + * @return the alleles for this genotype + */ + public List getAlleles() { + return alleles; + } + + public List getAlleles(Allele allele) { + if ( getType() == Type.UNAVAILABLE ) + throw new ReviewedStingException("Requesting alleles for an UNAVAILABLE genotype"); + + List al = new ArrayList(); + for ( Allele a : alleles ) + if ( a.equals(allele) ) + al.add(a); + + return Collections.unmodifiableList(al); + } + + public Allele getAllele(int i) { + if ( getType() == Type.UNAVAILABLE ) + throw new ReviewedStingException("Requesting alleles for an UNAVAILABLE genotype"); + return alleles.get(i); + } + + public boolean isPhased() { return isPhased; } + + /** + * @return the ploidy of this genotype + */ + public int getPloidy() { + if ( alleles == null ) + throw new ReviewedStingException("Requesting ploidy for an UNAVAILABLE genotype"); + return alleles.size(); + } + + public enum Type { + NO_CALL, + HOM_REF, + HET, + HOM_VAR, + UNAVAILABLE, + MIXED // no-call and call in the same genotype + } + + public Type getType() { + if ( type == null ) { + type = determineType(); + } + return type; + } + + protected Type determineType() { + if ( alleles == null ) + return Type.UNAVAILABLE; + + boolean sawNoCall = false, sawMultipleAlleles = false; + Allele observedAllele = null; + + for ( Allele allele : alleles ) { + if ( allele.isNoCall() ) + sawNoCall = true; + else if ( observedAllele == null ) + observedAllele = allele; + else if ( !allele.equals(observedAllele) ) + sawMultipleAlleles = true; + } + + if ( sawNoCall ) { + if ( observedAllele == null ) + return Type.NO_CALL; + return Type.MIXED; + } + + if ( observedAllele == null ) + throw new ReviewedStingException("BUG: there are no alleles present in this genotype but the alleles list is not null"); + + return sawMultipleAlleles ? Type.HET : observedAllele.isReference() ? Type.HOM_REF : Type.HOM_VAR; + } + + /** + * @return true if all observed alleles are the same (regardless of whether they are ref or alt); if any alleles are no-calls, this method will return false. + */ + public boolean isHom() { return isHomRef() || isHomVar(); } + + /** + * @return true if all observed alleles are ref; if any alleles are no-calls, this method will return false. + */ + public boolean isHomRef() { return getType() == Type.HOM_REF; } + + /** + * @return true if all observed alleles are alt; if any alleles are no-calls, this method will return false. + */ + public boolean isHomVar() { return getType() == Type.HOM_VAR; } + + /** + * @return true if we're het (observed alleles differ); if the ploidy is less than 2 or if any alleles are no-calls, this method will return false. + */ + public boolean isHet() { return getType() == Type.HET; } + + /** + * @return true if this genotype is not actually a genotype but a "no call" (e.g. './.' in VCF); if any alleles are not no-calls (even if some are), this method will return false. + */ + public boolean isNoCall() { return getType() == Type.NO_CALL; } + + /** + * @return true if this genotype is comprised of any alleles that are not no-calls (even if some are). + */ + public boolean isCalled() { return getType() != Type.NO_CALL && getType() != Type.UNAVAILABLE; } + + /** + * @return true if this genotype is comprised of both calls and no-calls. + */ + public boolean isMixed() { return getType() == Type.MIXED; } + + /** + * @return true if the type of this genotype is set. + */ + public boolean isAvailable() { return getType() != Type.UNAVAILABLE; } + + // + // Useful methods for getting genotype likelihoods for a genotype object, if present + // + public boolean hasLikelihoods() { + return (hasAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY) && !getAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY).equals(VCFConstants.MISSING_VALUE_v4)) || + (hasAttribute(VCFConstants.GENOTYPE_LIKELIHOODS_KEY) && !getAttribute(VCFConstants.GENOTYPE_LIKELIHOODS_KEY).equals(VCFConstants.MISSING_VALUE_v4)); + } + + public GenotypeLikelihoods getLikelihoods() { + GenotypeLikelihoods x = getLikelihoods(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, true); + if ( x != null ) + return x; + else { + x = getLikelihoods(VCFConstants.GENOTYPE_LIKELIHOODS_KEY, false); + if ( x != null ) return x; + else + throw new IllegalStateException("BUG: genotype likelihood field in " + this.getSampleName() + " sample are not either a string or a genotype likelihood class!"); + } + } + + private GenotypeLikelihoods getLikelihoods(String key, boolean asPL) { + Object x = getAttribute(key); + if ( x instanceof String ) { + if ( asPL ) + return GenotypeLikelihoods.fromPLField((String)x); + else + return GenotypeLikelihoods.fromGLField((String)x); + } + else if ( x instanceof GenotypeLikelihoods ) return (GenotypeLikelihoods)x; + else return null; + } + + public void validate() { + if ( alleles == null ) return; + if ( alleles.size() == 0) throw new IllegalArgumentException("BUG: alleles cannot be of size 0"); + + // int nNoCalls = 0; + for ( Allele allele : alleles ) { + if ( allele == null ) + throw new IllegalArgumentException("BUG: allele cannot be null in Genotype"); + // nNoCalls += allele.isNoCall() ? 1 : 0; + } + + // Technically, the spec does allow for the below case so this is not an illegal state + //if ( nNoCalls > 0 && nNoCalls != alleles.size() ) + // throw new IllegalArgumentException("BUG: alleles include some No Calls and some Calls, an illegal state " + this); + } + + public String getGenotypeString() { + return getGenotypeString(true); + } + + public String getGenotypeString(boolean ignoreRefState) { + if ( alleles == null ) + return null; + + // Notes: + // 1. Make sure to use the appropriate separator depending on whether the genotype is phased + // 2. If ignoreRefState is true, then we want just the bases of the Alleles (ignoring the '*' indicating a ref Allele) + // 3. So that everything is deterministic with regards to integration tests, we sort Alleles (when the genotype isn't phased, of course) + return ParsingUtils.join(isPhased() ? PHASED_ALLELE_SEPARATOR : UNPHASED_ALLELE_SEPARATOR, + ignoreRefState ? getAlleleStrings() : (isPhased() ? getAlleles() : ParsingUtils.sortList(getAlleles()))); + } + + private List getAlleleStrings() { + List al = new ArrayList(); + for ( Allele a : alleles ) + al.add(a.getBaseString()); + + return al; + } + + public String toString() { + int Q = (int)Math.round(getPhredScaledQual()); + return String.format("[%s %s Q%s %s]", getSampleName(), getGenotypeString(false), + Q == -10 ? "." : String.format("%2d",Q), sortedString(getAttributes())); + } + + public String toBriefString() { + return String.format("%s:Q%.2f", getGenotypeString(false), getPhredScaledQual()); + } + + public boolean sameGenotype(Genotype other) { + return sameGenotype(other, true); + } + + public boolean sameGenotype(Genotype other, boolean ignorePhase) { + if (getPloidy() != other.getPloidy()) + return false; // gotta have the same number of allele to be equal + + // By default, compare the elements in the lists of alleles, element-by-element + Collection thisAlleles = this.getAlleles(); + Collection otherAlleles = other.getAlleles(); + + if (ignorePhase) { // do not care about order, only identity of Alleles + thisAlleles = new TreeSet(thisAlleles); //implemented Allele.compareTo() + otherAlleles = new TreeSet(otherAlleles); + } + + return thisAlleles.equals(otherAlleles); + } + + /** + * a utility method for generating sorted strings from a map key set. + * @param c the map + * @param the key type + * @param the value type + * @return a sting, enclosed in {}, with comma seperated key value pairs in order of the keys + */ + private static , V> String sortedString(Map c) { + // NOTE -- THIS IS COPIED FROM GATK UTILS TO ALLOW US TO KEEP A SEPARATION BETWEEN THE GATK AND VCF CODECS + List t = new ArrayList(c.keySet()); + Collections.sort(t); + + List pairs = new ArrayList(); + for (T k : t) { + pairs.add(k + "=" + c.get(k)); + } + + return "{" + ParsingUtils.join(", ", pairs.toArray(new String[pairs.size()])) + "}"; + } + + + // --------------------------------------------------------------------------------------------------------- + // + // get routines to access context info fields + // + // --------------------------------------------------------------------------------------------------------- + public String getSampleName() { return commonInfo.getName(); } + public Set getFilters() { return commonInfo.getFilters(); } + public boolean isFiltered() { return commonInfo.isFiltered(); } + public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } + public boolean filtersWereApplied() { return filtersWereAppliedToContext; } + public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } + public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } + public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } + + public Map getAttributes() { return commonInfo.getAttributes(); } + public boolean hasAttribute(String key) { return commonInfo.hasAttribute(key); } + public Object getAttribute(String key) { return commonInfo.getAttribute(key); } + + public Object getAttribute(String key, Object defaultValue) { + return commonInfo.getAttribute(key, defaultValue); + } + + public String getAttributeAsString(String key, String defaultValue) { return commonInfo.getAttributeAsString(key, defaultValue); } + public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } + public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } + public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java new file mode 100755 index 000000000..02fb32451 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2010, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broad.tribble.TribbleException; +import org.broadinstitute.sting.utils.MathUtils; + +import java.util.EnumMap; +import java.util.Map; + +public class GenotypeLikelihoods { + public static final boolean CAP_PLS = false; + public static final int PL_CAP = 255; + + // + // There are two objects here because we are lazy in creating both representations + // for this object: a vector of log10 Probs and the PL phred-scaled string. Supports + // having one set during initializating, and dynamic creation of the other, if needed + // + private double[] log10Likelihoods = null; + private String likelihoodsAsString_PLs = null; + + public final static GenotypeLikelihoods fromPLField(String PLs) { + return new GenotypeLikelihoods(PLs); + } + + public final static GenotypeLikelihoods fromGLField(String GLs) { + return new GenotypeLikelihoods(parseDeprecatedGLString(GLs)); + } + + public final static GenotypeLikelihoods fromLog10Likelihoods(double[] log10Likelihoods) { + return new GenotypeLikelihoods(log10Likelihoods); + } + + // + // You must use the factory methods now + // + protected GenotypeLikelihoods(String asString) { + likelihoodsAsString_PLs = asString; + } + + protected GenotypeLikelihoods(double[] asVector) { + log10Likelihoods = asVector; + } + + /** + * Returns the genotypes likelihoods in negative log10 vector format. pr{AA} = x, this + * vector returns math.log10(x) for each of the genotypes. Can return null if the + * genotype likelihoods are "missing". + * + * @return + */ + public double[] getAsVector() { + // assumes one of the likelihoods vector or the string isn't null + if ( log10Likelihoods == null ) { + // make sure we create the GL string if it doesn't already exist + log10Likelihoods = parsePLsIntoLikelihoods(likelihoodsAsString_PLs); + } + + return log10Likelihoods; + } + + public String toString() { + return getAsString(); + } + + public String getAsString() { + if ( likelihoodsAsString_PLs == null ) { + // todo -- should we accept null log10Likelihoods and set PLs as MISSING? + if ( log10Likelihoods == null ) + throw new TribbleException("BUG: Attempted to get likelihoods as strings and neither the vector nor the string is set!"); + likelihoodsAsString_PLs = convertLikelihoodsToPLString(log10Likelihoods); + } + + return likelihoodsAsString_PLs; + } + + //Return genotype likelihoods as an EnumMap with Genotypes as keys and likelihoods as values + //Returns null in case of missing likelihoods + public EnumMap getAsMap(boolean normalizeFromLog10){ + //Make sure that the log10likelihoods are set + double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); + if(likelihoods == null) + return null; + EnumMap likelihoodsMap = new EnumMap(Genotype.Type.class); + likelihoodsMap.put(Genotype.Type.HOM_REF,likelihoods[Genotype.Type.HOM_REF.ordinal()-1]); + likelihoodsMap.put(Genotype.Type.HET,likelihoods[Genotype.Type.HET.ordinal()-1]); + likelihoodsMap.put(Genotype.Type.HOM_VAR, likelihoods[Genotype.Type.HOM_VAR.ordinal() - 1]); + return likelihoodsMap; + } + + //Return the neg log10 Genotype Quality (GQ) for the given genotype + //Returns Double.NEGATIVE_INFINITY in case of missing genotype + public double getNegLog10GQ(Genotype.Type genotype){ + + double qual = Double.NEGATIVE_INFINITY; + EnumMap likelihoods = getAsMap(false); + if(likelihoods == null) + return qual; + for(Map.Entry likelihood : likelihoods.entrySet()){ + if(likelihood.getKey() == genotype) + continue; + if(likelihood.getValue() > qual) + qual = likelihood.getValue(); + + } + + //Quality of the most likely genotype = likelihood(most likely) - likelihood (2nd best) + qual = likelihoods.get(genotype) - qual; + + //Quality of other genotypes 1-P(G) + if (qual < 0) { + double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); + double chosenGenotype = normalized[genotype.ordinal()-1]; + qual = -1.0 * Math.log10(1.0 - chosenGenotype); + } + return qual; + } + + private final static double[] parsePLsIntoLikelihoods(String likelihoodsAsString_PLs) { + if ( !likelihoodsAsString_PLs.equals(VCFConstants.MISSING_VALUE_v4) ) { + String[] strings = likelihoodsAsString_PLs.split(","); + double[] likelihoodsAsVector = new double[strings.length]; + for ( int i = 0; i < strings.length; i++ ) { + likelihoodsAsVector[i] = Integer.parseInt(strings[i]) / -10.0; + } + return likelihoodsAsVector; + } else + return null; + } + + /** + * Back-compatibility function to read old style GL formatted genotype likelihoods in VCF format + * @param GLString + * @return + */ + private final static double[] parseDeprecatedGLString(String GLString) { + if ( !GLString.equals(VCFConstants.MISSING_VALUE_v4) ) { + String[] strings = GLString.split(","); + double[] likelihoodsAsVector = new double[strings.length]; + for ( int i = 0; i < strings.length; i++ ) { + likelihoodsAsVector[i] = Double.parseDouble(strings[i]); + } + return likelihoodsAsVector; + } + + return null; + } + + private final static String convertLikelihoodsToPLString(double[] GLs) { + if ( GLs == null ) + return VCFConstants.MISSING_VALUE_v4; + + StringBuilder s = new StringBuilder(); + + double adjust = Double.NEGATIVE_INFINITY; + for ( double l : GLs ) adjust = Math.max(adjust, l); + + boolean first = true; + for ( double l : GLs ) { + if ( ! first ) + s.append(","); + else + first = false; + + long PL = Math.round(-10 * (l - adjust)); + if ( CAP_PLS ) + PL = Math.min(PL, PL_CAP); + s.append(Long.toString(PL)); + } + + return s.toString(); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java new file mode 100644 index 000000000..85444c325 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import com.google.java.contract.Ensures; +import com.google.java.contract.Requires; +import net.sf.samtools.SAMSequenceDictionary; +import org.broad.tribble.Tribble; +import org.broad.tribble.TribbleException; +import org.broad.tribble.index.DynamicIndexCreator; +import org.broad.tribble.index.Index; +import org.broad.tribble.index.IndexFactory; +import org.broad.tribble.util.LittleEndianOutputStream; +import org.broad.tribble.util.PositionalStream; +import org.broadinstitute.sting.gatk.refdata.tracks.IndexDictionaryUtils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; + +import java.io.*; + +/** + * this class writes VCF files + */ +abstract class IndexingVCFWriter implements VCFWriter { + final private String name; + private final SAMSequenceDictionary refDict; + + private OutputStream outputStream; + private PositionalStream positionalStream = null; + private DynamicIndexCreator indexer = null; + private LittleEndianOutputStream idxStream = null; + + @Requires({"name != null", + "! ( location == null && output == null )", + "! ( enableOnTheFlyIndexing && location == null )"}) + protected IndexingVCFWriter(final String name, final File location, final OutputStream output, final SAMSequenceDictionary refDict, final boolean enableOnTheFlyIndexing) { + outputStream = output; + this.name = name; + this.refDict = refDict; + + if ( enableOnTheFlyIndexing ) { + try { + idxStream = new LittleEndianOutputStream(new FileOutputStream(Tribble.indexFile(location))); + //System.out.println("Creating index on the fly for " + location); + indexer = new DynamicIndexCreator(IndexFactory.IndexBalanceApproach.FOR_SEEK_TIME); + indexer.initialize(location, indexer.defaultBinSize()); + positionalStream = new PositionalStream(output); + outputStream = positionalStream; + } catch ( IOException ex ) { + // No matter what we keep going, since we don't care if we can't create the index file + idxStream = null; + indexer = null; + positionalStream = null; + } + } + } + + @Ensures("result != null") + public OutputStream getOutputStream() { + return outputStream; + } + + @Ensures("result != null") + public String getStreamName() { + return name; + } + + public abstract void writeHeader(VCFHeader header); + + /** + * attempt to close the VCF file + */ + public void close() { + // try to close the index stream (keep it separate to help debugging efforts) + if ( indexer != null ) { + try { + Index index = indexer.finalizeIndex(positionalStream.getPosition()); + IndexDictionaryUtils.setIndexSequenceDictionary(index, refDict); + index.write(idxStream); + idxStream.close(); + } catch (IOException e) { + throw new ReviewedStingException("Unable to close index for " + getStreamName(), e); + } + } + } + + /** + * add a record to the file + * + * @param vc the Variant Context object + */ + public void add(VariantContext vc) { + // if we are doing on the fly indexing, add the record ***before*** we write any bytes + if ( indexer != null ) + indexer.addFeature(vc, positionalStream.getPosition()); + } + + /** + * Returns a reasonable "name" for this writer, to display to the user if something goes wrong + * + * @param location + * @param stream + * @return + */ + protected static final String writerName(final File location, final OutputStream stream) { + return location == null ? stream.toString() : location.getAbsolutePath(); + } + + /** + * Returns a output stream writing to location, or throws a UserException if this fails + * @param location + * @return + */ + protected static OutputStream openOutputStream(final File location) { + try { + return new FileOutputStream(location); + } catch (FileNotFoundException e) { + throw new UserException.CouldNotCreateOutputFile(location, "Unable to create VCF writer", e); + } + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java new file mode 100755 index 000000000..43f61343e --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java @@ -0,0 +1,243 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + + +import java.util.*; + + +/** + * Common utility routines for VariantContext and Genotype + * + * @author depristo + */ +final class InferredGeneticContext { + public static final double NO_NEG_LOG_10PERROR = -1.0; + + private static Set NO_FILTERS = Collections.unmodifiableSet(new HashSet()); + private static Map NO_ATTRIBUTES = Collections.unmodifiableMap(new HashMap()); + + private double negLog10PError = NO_NEG_LOG_10PERROR; + private String name = null; + private Set filters = NO_FILTERS; + private Map attributes = NO_ATTRIBUTES; + +// public InferredGeneticContext(String name) { +// this.name = name; +// } +// +// public InferredGeneticContext(String name, double negLog10PError) { +// this(name); +// setNegLog10PError(negLog10PError); +// } + + public InferredGeneticContext(String name, double negLog10PError, Set filters, Map attributes) { + this.name = name; + setNegLog10PError(negLog10PError); + if ( filters != null ) + setFilters(filters); + if ( attributes != null ) + setAttributes(attributes); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * Sets the name + * + * @param name the name associated with this information + */ + public void setName(String name) { + if ( name == null ) throw new IllegalArgumentException("Name cannot be null " + this); + this.name = name; + } + + + // --------------------------------------------------------------------------------------------------------- + // + // Filter + // + // --------------------------------------------------------------------------------------------------------- + + public Set getFilters() { + return Collections.unmodifiableSet(filters); + } + + public boolean isFiltered() { + return filters.size() > 0; + } + + public boolean isNotFiltered() { + return ! isFiltered(); + } + + public void addFilter(String filter) { + if ( filters == NO_FILTERS ) // immutable -> mutable + filters = new HashSet(filters); + + if ( filter == null ) throw new IllegalArgumentException("BUG: Attempting to add null filter " + this); + if ( getFilters().contains(filter) ) throw new IllegalArgumentException("BUG: Attempting to add duplicate filter " + filter + " at " + this); + filters.add(filter); + } + + public void addFilters(Collection filters) { + if ( filters == null ) throw new IllegalArgumentException("BUG: Attempting to add null filters at" + this); + for ( String f : filters ) + addFilter(f); + } + + public void clearFilters() { + filters = new HashSet(); + } + + public void setFilters(Collection filters) { + clearFilters(); + addFilters(filters); + } + + // --------------------------------------------------------------------------------------------------------- + // + // Working with log error rates + // + // --------------------------------------------------------------------------------------------------------- + + public boolean hasNegLog10PError() { + return getNegLog10PError() != NO_NEG_LOG_10PERROR; + } + + /** + * @return the -1 * log10-based error estimate + */ + public double getNegLog10PError() { return negLog10PError; } + public double getPhredScaledQual() { return getNegLog10PError() * 10; } + + public void setNegLog10PError(double negLog10PError) { + if ( negLog10PError < 0 && negLog10PError != NO_NEG_LOG_10PERROR ) throw new IllegalArgumentException("BUG: negLog10PError cannot be < than 0 : " + negLog10PError); + if ( Double.isInfinite(negLog10PError) ) throw new IllegalArgumentException("BUG: negLog10PError should not be Infinity"); + if ( Double.isNaN(negLog10PError) ) throw new IllegalArgumentException("BUG: negLog10PError should not be NaN"); + + this.negLog10PError = negLog10PError; + } + + // --------------------------------------------------------------------------------------------------------- + // + // Working with attributes + // + // --------------------------------------------------------------------------------------------------------- + public void clearAttributes() { + attributes = new HashMap(); + } + + /** + * @return the attribute map + */ + public Map getAttributes() { + return Collections.unmodifiableMap(attributes); + } + + // todo -- define common attributes as enum + + public void setAttributes(Map map) { + clearAttributes(); + putAttributes(map); + } + + public void putAttribute(String key, Object value) { + putAttribute(key, value, false); + } + + public void putAttribute(String key, Object value, boolean allowOverwrites) { + if ( ! allowOverwrites && hasAttribute(key) ) + throw new IllegalStateException("Attempting to overwrite key->value binding: key = " + key + " this = " + this); + + if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable + attributes = new HashMap(); + + attributes.put(key, value); + } + + public void removeAttribute(String key) { + if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable + attributes = new HashMap(); + attributes.remove(key); + } + + public void putAttributes(Map map) { + if ( map != null ) { + // for efficiency, we can skip the validation if the map is empty + if ( attributes.size() == 0 ) { + if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable + attributes = new HashMap(); + attributes.putAll(map); + } else { + for ( Map.Entry elt : map.entrySet() ) { + putAttribute(elt.getKey(), elt.getValue(), false); + } + } + } + } + + public boolean hasAttribute(String key) { + return attributes.containsKey(key); + } + + public int getNumAttributes() { + return attributes.size(); + } + + /** + * @param key the attribute key + * + * @return the attribute value for the given key (or null if not set) + */ + public Object getAttribute(String key) { + return attributes.get(key); + } + + public Object getAttribute(String key, Object defaultValue) { + if ( hasAttribute(key) ) + return attributes.get(key); + else + return defaultValue; + } + + public String getAttributeAsString(String key, String defaultValue) { + Object x = getAttribute(key); + if ( x == null ) return defaultValue; + if ( x instanceof String ) return (String)x; + return String.valueOf(x); // throws an exception if this isn't a string + } + + public int getAttributeAsInt(String key, int defaultValue) { + Object x = getAttribute(key); + if ( x == null || x == VCFConstants.MISSING_VALUE_v4 ) return defaultValue; + if ( x instanceof Integer ) return (Integer)x; + return Integer.valueOf((String)x); // throws an exception if this isn't a string + } + + public double getAttributeAsDouble(String key, double defaultValue) { + Object x = getAttribute(key); + if ( x == null ) return defaultValue; + if ( x instanceof Double ) return (Double)x; + return Double.valueOf((String)x); // throws an exception if this isn't a string + } + + public boolean getAttributeAsBoolean(String key, boolean defaultValue) { + Object x = getAttribute(key); + if ( x == null ) return defaultValue; + if ( x instanceof Boolean ) return (Boolean)x; + return Boolean.valueOf((String)x); // throws an exception if this isn't a string + } + +// public String getAttributeAsString(String key) { return (String.valueOf(getAttribute(key))); } // **NOTE**: will turn a null Object into the String "null" +// public int getAttributeAsInt(String key) { Object x = getAttribute(key); return x instanceof Integer ? (Integer)x : Integer.valueOf((String)x); } +// public double getAttributeAsDouble(String key) { Object x = getAttribute(key); return x instanceof Double ? (Double)x : Double.valueOf((String)x); } +// public boolean getAttributeAsBoolean(String key) { Object x = getAttribute(key); return x instanceof Boolean ? (Boolean)x : Boolean.valueOf((String)x); } +// public Integer getAttributeAsIntegerNoException(String key) { try {return getAttributeAsInt(key);} catch (Exception e) {return null;} } +// public Double getAttributeAsDoubleNoException(String key) { try {return getAttributeAsDouble(key);} catch (Exception e) {return null;} } +// public String getAttributeAsStringNoException(String key) { if (getAttribute(key) == null) return null; return getAttributeAsString(key); } +// public Boolean getAttributeAsBooleanNoException(String key) { try {return getAttributeAsBoolean(key);} catch (Exception e) {return null;} } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java new file mode 100755 index 000000000..f5072e040 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java @@ -0,0 +1,68 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import java.util.*; + +/** + * This class emcompasses all the basic information about a genotype. It is immutable. + * + * @author Mark DePristo + */ +class MutableGenotype extends Genotype { + public MutableGenotype(Genotype parent) { + super(parent.getSampleName(), parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); + } + + public MutableGenotype(String sampleName, Genotype parent) { + super(sampleName, parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); + } + + + public MutableGenotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean genotypesArePhased) { + super(sampleName, alleles, negLog10PError, filters, attributes, genotypesArePhased); + } + + public MutableGenotype(String sampleName, List alleles, double negLog10PError) { + super(sampleName, alleles, negLog10PError); + } + + public MutableGenotype(String sampleName, List alleles) { + super(sampleName, alleles); + } + + public Genotype unmodifiableGenotype() { + return new Genotype(getSampleName(), getAlleles(), getNegLog10PError(), getFilters(), getAttributes(), isPhased()); + } + + + /** + * + * @param alleles list of alleles + */ + public void setAlleles(List alleles) { + this.alleles = new ArrayList(alleles); + validate(); + } + + public void setPhase(boolean isPhased) { + super.isPhased = isPhased; + } + + // --------------------------------------------------------------------------------------------------------- + // + // InferredGeneticContext mutation operators + // + // --------------------------------------------------------------------------------------------------------- + public void setName(String name) { commonInfo.setName(name); } + public void addFilter(String filter) { commonInfo.addFilter(filter); } + public void addFilters(Collection filters) { commonInfo.addFilters(filters); } + public void clearFilters() { commonInfo.clearFilters(); } + public void setFilters(Collection filters) { commonInfo.setFilters(filters); } + public void setAttributes(Map map) { commonInfo.setAttributes(map); } + public void clearAttributes() { commonInfo.clearAttributes(); } + public void putAttribute(String key, Object value) { commonInfo.putAttribute(key, value); } + public void removeAttribute(String key) { commonInfo.removeAttribute(key); } + public void putAttributes(Map map) { commonInfo.putAttributes(map); } + public void setNegLog10PError(double negLog10PError) { commonInfo.setNegLog10PError(negLog10PError); } + public void putAttribute(String key, Object value, boolean allowOverwrites) { commonInfo.putAttribute(key, value, allowOverwrites); } + +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java new file mode 100755 index 000000000..24e71ae50 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java @@ -0,0 +1,213 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Mutable version of VariantContext + * + * @author depristo + */ +class MutableVariantContext extends VariantContext { + // --------------------------------------------------------------------------------------------------------- + // + // constructors + // + // --------------------------------------------------------------------------------------------------------- + + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); + } + + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { + super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); + } + + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles) { + super(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + } + + public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { + super(source, contig, start, stop, alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + } + + public MutableVariantContext(VariantContext parent) { + super(parent.getSource(), parent.contig, parent.start, parent.stop, parent.getAlleles(), parent.getGenotypes(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.getReferenceBaseForIndel()); + } + + /** + * Sets the alleles segregating in this context to the collect of alleles. Each of which must be unique according + * to equals() in Allele. Validate() should be called when you are done modifying the context. + * + * @param alleles + */ + public void setAlleles(Collection alleles) { + this.alleles.clear(); + for ( Allele a : alleles ) + addAllele(a); + } + + /** + * Adds allele to the segregating allele list in this context to the collection of alleles. The new + * allele must be be unique according to equals() in Allele. + * Validate() should be called when you are done modifying the context. + * + * @param allele + */ + public void addAllele(Allele allele) { + final boolean allowDuplicates = false; // used to be a parameter + + type = null; + + for ( Allele a : alleles ) { + if ( a.basesMatch(allele) && ! allowDuplicates ) + throw new IllegalArgumentException("Duplicate allele added to VariantContext" + this); + } + + // we are a novel allele + alleles.add(allele); + } + + public void clearGenotypes() { + genotypes = new TreeMap(); + } + + /** + * Adds this single genotype to the context, not allowing duplicate genotypes to be added + * @param genotype + */ + public void addGenotypes(Genotype genotype) { + putGenotype(genotype.getSampleName(), genotype, false); + } + + /** + * Adds these genotypes to the context, not allowing duplicate genotypes to be added + * @param genotypes + */ + public void addGenotypes(Collection genotypes) { + for ( Genotype g : genotypes ) { + addGenotype(g); + } + } + + /** + * Adds these genotype to the context, not allowing duplicate genotypes to be added. + * @param genotypes + */ + public void addGenotypes(Map genotypes) { + + for ( Map.Entry elt : genotypes.entrySet() ) { + addGenotype(elt.getValue()); + } + } + + /** + * Adds these genotypes to the context. + * + * @param genotypes + */ + public void putGenotypes(Map genotypes) { + for ( Map.Entry g : genotypes.entrySet() ) + putGenotype(g.getKey(), g.getValue()); + } + + /** + * Adds these genotypes to the context. + * + * @param genotypes + */ + public void putGenotypes(Collection genotypes) { + for ( Genotype g : genotypes ) + putGenotype(g); + } + + /** + * Adds this genotype to the context, throwing an error if it's already bound. + * + * @param genotype + */ + public void addGenotype(Genotype genotype) { + addGenotype(genotype.getSampleName(), genotype); + } + + /** + * Adds this genotype to the context, throwing an error if it's already bound. + * + * @param genotype + */ + public void addGenotype(String sampleName, Genotype genotype) { + putGenotype(sampleName, genotype, false); + } + + /** + * Adds this genotype to the context. + * + * @param genotype + */ + public void putGenotype(Genotype genotype) { + putGenotype(genotype.getSampleName(), genotype); + } + + /** + * Adds this genotype to the context. + * + * @param genotype + */ + public void putGenotype(String sampleName, Genotype genotype) { + putGenotype(sampleName, genotype, true); + } + + private void putGenotype(String sampleName, Genotype genotype, boolean allowOverwrites) { + if ( hasGenotype(sampleName) && ! allowOverwrites ) + throw new IllegalStateException("Attempting to overwrite sample->genotype binding: " + sampleName + " this=" + this); + + if ( ! sampleName.equals(genotype.getSampleName()) ) + throw new IllegalStateException("Sample name doesn't equal genotype.getSample(): " + sampleName + " genotype=" + genotype); + + this.genotypes.put(sampleName, genotype); + } + + /** + * Removes the binding from sampleName to genotype. If this doesn't exist, throws an IllegalArgumentException + * @param sampleName + */ + public void removeGenotype(String sampleName) { + if ( ! this.genotypes.containsKey(sampleName) ) + throw new IllegalArgumentException("Sample name isn't contained in genotypes " + sampleName + " genotypes =" + genotypes); + + this.genotypes.remove(sampleName); + } + + /** + * Removes genotype from the context. If this doesn't exist, throws an IllegalArgumentException + * @param genotype + */ + public void removeGenotype(Genotype genotype) { + removeGenotype(genotype.getSampleName()); + } + + // todo -- add replace genotype routine + + // --------------------------------------------------------------------------------------------------------- + // + // InferredGeneticContext mutation operators + // + // --------------------------------------------------------------------------------------------------------- + + public void setSource(String source) { commonInfo.setName(source); } + public void addFilter(String filter) { commonInfo.addFilter(filter); } + public void addFilters(Collection filters) { commonInfo.addFilters(filters); } + public void clearFilters() { commonInfo.clearFilters(); } + public void setFilters(Collection filters) { commonInfo.setFilters(filters); } + public void setAttributes(Map map) { commonInfo.setAttributes(map); } + public void clearAttributes() { commonInfo.clearAttributes(); } + public void putAttribute(String key, Object value) { commonInfo.putAttribute(key, value); } + public void removeAttribute(String key) { commonInfo.removeAttribute(key); } + public void putAttributes(Map map) { commonInfo.putAttributes(map); } + public void setNegLog10PError(double negLog10PError) { commonInfo.setNegLog10PError(negLog10PError); } + public void putAttribute(String key, Object value, boolean allowOverwrites) { commonInfo.putAttribute(key, value, allowOverwrites); } + public void setID(String id) { putAttribute(ID_KEY, id, true); } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java new file mode 100755 index 000000000..9f653872a --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java @@ -0,0 +1,198 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broad.tribble.TribbleException; +import org.broad.tribble.readers.LineReader; +import org.broad.tribble.util.ParsingUtils; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; + + +/** + * A feature codec for the VCF3 specification, to read older VCF files. VCF3 has been + * depreciated in favor of VCF4 (See VCF codec for the latest information) + * + *

+ * Reads historical VCF3 encoded files (1000 Genomes Pilot results, for example) + *

+ * + *

+ * See also: @see VCF specification
+ * See also: @see VCF spec. publication + *

+ * + * @author Mark DePristo + * @since 2010 + */ +class VCF3Codec extends AbstractVCFCodec { + public final static String VCF3_MAGIC_HEADER = "##fileformat=VCFv3"; + + + /** + * @param reader the line reader to take header lines from + * @return the number of header lines + */ + public Object readHeader(LineReader reader) { + List headerStrings = new ArrayList(); + + String line; + try { + boolean foundHeaderVersion = false; + while ((line = reader.readLine()) != null) { + lineNo++; + if (line.startsWith(VCFHeader.METADATA_INDICATOR)) { + String[] lineFields = line.substring(2).split("="); + if (lineFields.length == 2 && VCFHeaderVersion.isFormatString(lineFields[0]) ) { + if ( !VCFHeaderVersion.isVersionString(lineFields[1]) ) + throw new TribbleException.InvalidHeader(lineFields[1] + " is not a supported version"); + foundHeaderVersion = true; + version = VCFHeaderVersion.toHeaderVersion(lineFields[1]); + if ( version != VCFHeaderVersion.VCF3_3 && version != VCFHeaderVersion.VCF3_2 ) + throw new TribbleException.InvalidHeader("This codec is strictly for VCFv3 and does not support " + lineFields[1]); + } + headerStrings.add(line); + } + else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { + if (!foundHeaderVersion) { + throw new TribbleException.InvalidHeader("We never saw a header line specifying VCF version"); + } + return createHeader(headerStrings, line); + } + else { + throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); + } + + } + } catch (IOException e) { + throw new RuntimeException("IO Exception ", e); + } + throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); + } + + + /** + * parse the filter string, first checking to see if we already have parsed it in a previous attempt + * @param filterString the string to parse + * @return a set of the filters applied + */ + protected Set parseFilters(String filterString) { + + // null for unfiltered + if ( filterString.equals(VCFConstants.UNFILTERED) ) + return null; + + // empty set for passes filters + LinkedHashSet fFields = new LinkedHashSet(); + + if ( filterString.equals(VCFConstants.PASSES_FILTERS_v3) ) + return fFields; + + if ( filterString.length() == 0 ) + generateException("The VCF specification requires a valid filter status"); + + // do we have the filter string cached? + if ( filterHash.containsKey(filterString) ) + return filterHash.get(filterString); + + // otherwise we have to parse and cache the value + if ( filterString.indexOf(VCFConstants.FILTER_CODE_SEPARATOR) == -1 ) + fFields.add(filterString); + else + fFields.addAll(Arrays.asList(filterString.split(VCFConstants.FILTER_CODE_SEPARATOR))); + + filterHash.put(filterString, fFields); + + return fFields; + } + + /** + * create a genotype map + * @param str the string + * @param alleles the list of alleles + * @param chr chrom + * @param pos position + * @return a mapping of sample name to genotype object + */ + public Map createGenotypeMap(String str, List alleles, String chr, int pos) { + if (genotypeParts == null) + genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; + + int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); + + Map genotypes = new LinkedHashMap(nParts); + + // get the format keys + int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); + + // cycle through the sample names + Iterator sampleNameIterator = header.getGenotypeSamples().iterator(); + + // clear out our allele mapping + alleleMap.clear(); + + // cycle through the genotype strings + for (int genotypeOffset = 1; genotypeOffset < nParts; genotypeOffset++) { + int GTValueSplitSize = ParsingUtils.split(genotypeParts[genotypeOffset], GTValueArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); + + double GTQual = VariantContext.NO_NEG_LOG_10PERROR; + Set genotypeFilters = null; + Map gtAttributes = null; + String sampleName = sampleNameIterator.next(); + + // check to see if the value list is longer than the key list, which is a problem + if (nGTKeys < GTValueSplitSize) + generateException("There are too many keys for the sample " + sampleName + ", keys = " + parts[8] + ", values = " + parts[genotypeOffset]); + + int genotypeAlleleLocation = -1; + if (nGTKeys >= 1) { + gtAttributes = new HashMap(nGTKeys - 1); + + for (int i = 0; i < nGTKeys; i++) { + final String gtKey = new String(genotypeKeyArray[i]); + boolean missing = i >= GTValueSplitSize; + + if (gtKey.equals(VCFConstants.GENOTYPE_KEY)) { + genotypeAlleleLocation = i; + } else if (gtKey.equals(VCFConstants.GENOTYPE_QUALITY_KEY)) { + GTQual = missing ? parseQual(VCFConstants.MISSING_VALUE_v4) : parseQual(GTValueArray[i]); + } else if (gtKey.equals(VCFConstants.GENOTYPE_FILTER_KEY)) { + genotypeFilters = missing ? parseFilters(VCFConstants.MISSING_VALUE_v4) : parseFilters(getCachedString(GTValueArray[i])); + } else if ( missing || GTValueArray[i].equals(VCFConstants.MISSING_GENOTYPE_QUALITY_v3) ) { + gtAttributes.put(gtKey, VCFConstants.MISSING_VALUE_v4); + } else { + gtAttributes.put(gtKey, new String(GTValueArray[i])); + } + } + } + + // check to make sure we found a genotype field + if ( genotypeAlleleLocation < 0 ) + generateException("Unable to find the GT field for the record; the GT field is required"); + if ( genotypeAlleleLocation > 0 ) + generateException("Saw GT field at position " + genotypeAlleleLocation + ", but it must be at the first position for genotypes"); + + boolean phased = GTValueArray[genotypeAlleleLocation].indexOf(VCFConstants.PHASED) != -1; + + // add it to the list + try { + genotypes.put(sampleName, new Genotype(sampleName, + parseGenotypeAlleles(GTValueArray[genotypeAlleleLocation], alleles, alleleMap), + GTQual, + genotypeFilters, + gtAttributes, + phased)); + } catch (TribbleException e) { + throw new TribbleException.InternalCodecException(e.getMessage() + ", at position " + chr+":"+pos); + } + } + + return genotypes; + } + + @Override + public boolean canDecode(final File potentialInput) { + return canDecodeFile(potentialInput, VCF3_MAGIC_HEADER); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java new file mode 100644 index 000000000..e432fe411 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java @@ -0,0 +1,28 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +/** + * @author ebanks + * A class representing a key=value entry for ALT fields in the VCF header + */ +class VCFAltHeaderLine extends VCFSimpleHeaderLine { + + /** + * create a VCF filter header line + * + * @param name the name for this header line + * @param description the description for this header line + */ + public VCFAltHeaderLine(String name, String description) { + super(name, description, SupportedHeaderLineType.ALT); + } + + /** + * create a VCF info header line + * + * @param line the header line + * @param version the vcf header version + */ + protected VCFAltHeaderLine(String line, VCFHeaderVersion version) { + super(line, version, SupportedHeaderLineType.ALT); + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java new file mode 100755 index 000000000..f873aebcc --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java @@ -0,0 +1,228 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broad.tribble.TribbleException; +import org.broad.tribble.readers.LineReader; +import org.broad.tribble.util.ParsingUtils; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; + +/** + * A feature codec for the VCF 4 specification + * + *

+ * VCF is a text file format (most likely stored in a compressed manner). It contains meta-information lines, a + * header line, and then data lines each containing information about a position in the genome. + *

+ *

One of the main uses of next-generation sequencing is to discover variation amongst large populations + * of related samples. Recently the format for storing next-generation read alignments has been + * standardised by the SAM/BAM file format specification. This has significantly improved the + * interoperability of next-generation tools for alignment, visualisation, and variant calling. + * We propose the Variant Call Format (VCF) as a standarised format for storing the most prevalent + * types of sequence variation, including SNPs, indels and larger structural variants, together + * with rich annotations. VCF is usually stored in a compressed manner and can be indexed for + * fast data retrieval of variants from a range of positions on the reference genome. + * The format was developed for the 1000 Genomes Project, and has also been adopted by other projects + * such as UK10K, dbSNP, or the NHLBI Exome Project. VCFtools is a software suite that implements + * various utilities for processing VCF files, including validation, merging and comparing, + * and also provides a general Perl and Python API. + * The VCF specification and VCFtools are available from http://vcftools.sourceforge.net.

+ * + *

+ * See also: @see VCF specification
+ * See also: @see VCF spec. publication + *

+ * + *

File format example

+ *
+ *     ##fileformat=VCFv4.0
+ *     #CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO    FORMAT  NA12878
+ *     chr1    109     .       A       T       0       PASS  AC=1    GT:AD:DP:GL:GQ  0/1:610,327:308:-316.30,-95.47,-803.03:99
+ *     chr1    147     .       C       A       0       PASS  AC=1    GT:AD:DP:GL:GQ  0/1:294,49:118:-57.87,-34.96,-338.46:99
+ * 
+ * + * @author Mark DePristo + * @since 2010 + */ +public class VCFCodec extends AbstractVCFCodec { + // Our aim is to read in the records and convert to VariantContext as quickly as possible, relying on VariantContext to do the validation of any contradictory (or malformed) record parameters. + + public final static String VCF4_MAGIC_HEADER = "##fileformat=VCFv4"; + + /** + * @param reader the line reader to take header lines from + * @return the number of header lines + */ + public Object readHeader(LineReader reader) { + List headerStrings = new ArrayList(); + + String line; + try { + boolean foundHeaderVersion = false; + while ((line = reader.readLine()) != null) { + lineNo++; + if (line.startsWith(VCFHeader.METADATA_INDICATOR)) { + String[] lineFields = line.substring(2).split("="); + if (lineFields.length == 2 && VCFHeaderVersion.isFormatString(lineFields[0]) ) { + if ( !VCFHeaderVersion.isVersionString(lineFields[1]) ) + throw new TribbleException.InvalidHeader(lineFields[1] + " is not a supported version"); + foundHeaderVersion = true; + version = VCFHeaderVersion.toHeaderVersion(lineFields[1]); + if ( version == VCFHeaderVersion.VCF3_3 || version == VCFHeaderVersion.VCF3_2 ) + throw new TribbleException.InvalidHeader("This codec is strictly for VCFv4; please use the VCF3 codec for " + lineFields[1]); + if ( version != VCFHeaderVersion.VCF4_0 && version != VCFHeaderVersion.VCF4_1 ) + throw new TribbleException.InvalidHeader("This codec is strictly for VCFv4 and does not support " + lineFields[1]); + } + headerStrings.add(line); + } + else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { + if (!foundHeaderVersion) { + throw new TribbleException.InvalidHeader("We never saw a header line specifying VCF version"); + } + return createHeader(headerStrings, line); + } + else { + throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); + } + + } + } catch (IOException e) { + throw new RuntimeException("IO Exception ", e); + } + throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); + } + + + /** + * parse the filter string, first checking to see if we already have parsed it in a previous attempt + * + * @param filterString the string to parse + * @return a set of the filters applied or null if filters were not applied to the record (e.g. as per the missing value in a VCF) + */ + protected Set parseFilters(String filterString) { + return parseFilters(filterHash, lineNo, filterString); + } + + public static Set parseFilters(final Map> cache, final int lineNo, final String filterString) { + // null for unfiltered + if ( filterString.equals(VCFConstants.UNFILTERED) ) + return null; + + if ( filterString.equals(VCFConstants.PASSES_FILTERS_v4) ) + return Collections.emptySet(); + if ( filterString.equals(VCFConstants.PASSES_FILTERS_v3) ) + generateException(VCFConstants.PASSES_FILTERS_v3 + " is an invalid filter name in vcf4", lineNo); + if ( filterString.length() == 0 ) + generateException("The VCF specification requires a valid filter status: filter was " + filterString, lineNo); + + // do we have the filter string cached? + if ( cache != null && cache.containsKey(filterString) ) + return Collections.unmodifiableSet(cache.get(filterString)); + + // empty set for passes filters + LinkedHashSet fFields = new LinkedHashSet(); + // otherwise we have to parse and cache the value + if ( filterString.indexOf(VCFConstants.FILTER_CODE_SEPARATOR) == -1 ) + fFields.add(filterString); + else + fFields.addAll(Arrays.asList(filterString.split(VCFConstants.FILTER_CODE_SEPARATOR))); + + fFields = fFields; + if ( cache != null ) cache.put(filterString, fFields); + + return Collections.unmodifiableSet(fFields); + } + + + /** + * create a genotype map + * @param str the string + * @param alleles the list of alleles + * @return a mapping of sample name to genotype object + */ + public Map createGenotypeMap(String str, List alleles, String chr, int pos) { + if (genotypeParts == null) + genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; + + int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); + + Map genotypes = new LinkedHashMap(nParts); + + // get the format keys + int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); + + // cycle through the sample names + Iterator sampleNameIterator = header.getGenotypeSamples().iterator(); + + // clear out our allele mapping + alleleMap.clear(); + + // cycle through the genotype strings + for (int genotypeOffset = 1; genotypeOffset < nParts; genotypeOffset++) { + int GTValueSplitSize = ParsingUtils.split(genotypeParts[genotypeOffset], GTValueArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); + + double GTQual = VariantContext.NO_NEG_LOG_10PERROR; + Set genotypeFilters = null; + Map gtAttributes = null; + String sampleName = sampleNameIterator.next(); + + // check to see if the value list is longer than the key list, which is a problem + if (nGTKeys < GTValueSplitSize) + generateException("There are too many keys for the sample " + sampleName + ", keys = " + parts[8] + ", values = " + parts[genotypeOffset]); + + int genotypeAlleleLocation = -1; + if (nGTKeys >= 1) { + gtAttributes = new HashMap(nGTKeys - 1); + + for (int i = 0; i < nGTKeys; i++) { + final String gtKey = new String(genotypeKeyArray[i]); + boolean missing = i >= GTValueSplitSize; + + // todo -- all of these on the fly parsing of the missing value should be static constants + if (gtKey.equals(VCFConstants.GENOTYPE_KEY)) { + genotypeAlleleLocation = i; + } else if (gtKey.equals(VCFConstants.GENOTYPE_QUALITY_KEY)) { + GTQual = missing ? parseQual(VCFConstants.MISSING_VALUE_v4) : parseQual(GTValueArray[i]); + } else if (gtKey.equals(VCFConstants.GENOTYPE_FILTER_KEY)) { + genotypeFilters = missing ? parseFilters(VCFConstants.MISSING_VALUE_v4) : parseFilters(getCachedString(GTValueArray[i])); + } else if ( missing ) { + gtAttributes.put(gtKey, VCFConstants.MISSING_VALUE_v4); + } else { + gtAttributes.put(gtKey, new String(GTValueArray[i])); + } + } + } + + // check to make sure we found a genotype field if we are a VCF4.0 file + if ( version == VCFHeaderVersion.VCF4_0 && genotypeAlleleLocation == -1 ) + generateException("Unable to find the GT field for the record; the GT field is required in VCF4.0"); + if ( genotypeAlleleLocation > 0 ) + generateException("Saw GT field at position " + genotypeAlleleLocation + ", but it must be at the first position for genotypes when present"); + + List GTalleles = (genotypeAlleleLocation == -1 ? null : parseGenotypeAlleles(GTValueArray[genotypeAlleleLocation], alleles, alleleMap)); + boolean phased = genotypeAlleleLocation != -1 && GTValueArray[genotypeAlleleLocation].indexOf(VCFConstants.PHASED) != -1; + + // add it to the list + try { + genotypes.put(sampleName, + new Genotype(sampleName, + GTalleles, + GTQual, + genotypeFilters, + gtAttributes, + phased)); + } catch (TribbleException e) { + throw new TribbleException.InternalCodecException(e.getMessage() + ", at position " + chr+":"+pos); + } + } + + return genotypes; + } + + @Override + public boolean canDecode(final File potentialInput) { + return canDecodeFile(potentialInput, VCF4_MAGIC_HEADER); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java new file mode 100755 index 000000000..74d6cf62f --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * a base class for compound header lines, which include info lines and format lines (so far) + */ +abstract class VCFCompoundHeaderLine extends VCFHeaderLine implements VCFNamedHeaderLine { + public enum SupportedHeaderLineType { + INFO(true), FORMAT(false); + + public final boolean allowFlagValues; + SupportedHeaderLineType(boolean flagValues) { + allowFlagValues = flagValues; + } + } + + // the field types + private String name; + private int count = -1; + private VCFHeaderLineCount countType; + private String description; + private VCFHeaderLineType type; + + // access methods + public String getName() { return name; } + public String getDescription() { return description; } + public VCFHeaderLineType getType() { return type; } + public VCFHeaderLineCount getCountType() { return countType; } + public int getCount() { + if ( countType != VCFHeaderLineCount.INTEGER ) + throw new ReviewedStingException("Asking for header line count when type is not an integer"); + return count; + } + + // utility method + public int getCount(int numAltAlleles) { + int myCount; + switch ( countType ) { + case INTEGER: myCount = count; break; + case UNBOUNDED: myCount = -1; break; + case A: myCount = numAltAlleles; break; + case G: myCount = ((numAltAlleles + 1) * (numAltAlleles + 2) / 2); break; + default: throw new ReviewedStingException("Unknown count type: " + countType); + } + return myCount; + } + + public void setNumberToUnbounded() { + countType = VCFHeaderLineCount.UNBOUNDED; + count = -1; + } + + // our type of line, i.e. format, info, etc + private final SupportedHeaderLineType lineType; + + /** + * create a VCF format header line + * + * @param name the name for this header line + * @param count the count for this header line + * @param type the type for this header line + * @param description the description for this header line + * @param lineType the header line type + */ + protected VCFCompoundHeaderLine(String name, int count, VCFHeaderLineType type, String description, SupportedHeaderLineType lineType) { + super(lineType.toString(), ""); + this.name = name; + this.countType = VCFHeaderLineCount.INTEGER; + this.count = count; + this.type = type; + this.description = description; + this.lineType = lineType; + validate(); + } + + /** + * create a VCF format header line + * + * @param name the name for this header line + * @param count the count type for this header line + * @param type the type for this header line + * @param description the description for this header line + * @param lineType the header line type + */ + protected VCFCompoundHeaderLine(String name, VCFHeaderLineCount count, VCFHeaderLineType type, String description, SupportedHeaderLineType lineType) { + super(lineType.toString(), ""); + this.name = name; + this.countType = count; + this.type = type; + this.description = description; + this.lineType = lineType; + validate(); + } + + /** + * create a VCF format header line + * + * @param line the header line + * @param version the VCF header version + * @param lineType the header line type + * + */ + protected VCFCompoundHeaderLine(String line, VCFHeaderVersion version, SupportedHeaderLineType lineType) { + super(lineType.toString(), ""); + Map mapping = VCFHeaderLineTranslator.parseLine(version,line, Arrays.asList("ID","Number","Type","Description")); + name = mapping.get("ID"); + count = -1; + final String numberStr = mapping.get("Number"); + if ( numberStr.equals(VCFConstants.PER_ALLELE_COUNT) ) { + countType = VCFHeaderLineCount.A; + } else if ( numberStr.equals(VCFConstants.PER_GENOTYPE_COUNT) ) { + countType = VCFHeaderLineCount.G; + } else if ( ((version == VCFHeaderVersion.VCF4_0 || version == VCFHeaderVersion.VCF4_1) && + numberStr.equals(VCFConstants.UNBOUNDED_ENCODING_v4)) || + ((version == VCFHeaderVersion.VCF3_2 || version == VCFHeaderVersion.VCF3_3) && + numberStr.equals(VCFConstants.UNBOUNDED_ENCODING_v3)) ) { + countType = VCFHeaderLineCount.UNBOUNDED; + } else { + countType = VCFHeaderLineCount.INTEGER; + count = Integer.valueOf(numberStr); + + } + type = VCFHeaderLineType.valueOf(mapping.get("Type")); + if (type == VCFHeaderLineType.Flag && !allowFlagValues()) + throw new IllegalArgumentException("Flag is an unsupported type for this kind of field"); + + description = mapping.get("Description"); + if ( description == null && ALLOW_UNBOUND_DESCRIPTIONS ) // handle the case where there's no description provided + description = UNBOUND_DESCRIPTION; + + this.lineType = lineType; + + validate(); + } + + private void validate() { + if ( name == null || type == null || description == null || lineType == null ) + throw new IllegalArgumentException(String.format("Invalid VCFCompoundHeaderLine: key=%s name=%s type=%s desc=%s lineType=%s", + super.getKey(), name, type, description, lineType )); + } + + /** + * make a string representation of this header line + * @return a string representation + */ + protected String toStringEncoding() { + Map map = new LinkedHashMap(); + map.put("ID", name); + Object number; + switch ( countType ) { + case A: number = VCFConstants.PER_ALLELE_COUNT; break; + case G: number = VCFConstants.PER_GENOTYPE_COUNT; break; + case UNBOUNDED: number = VCFConstants.UNBOUNDED_ENCODING_v4; break; + case INTEGER: + default: number = count; + } + map.put("Number", number); + map.put("Type", type); + map.put("Description", description); + return lineType.toString() + "=" + toStringEncoding(map); + } + + /** + * returns true if we're equal to another compounder header line + * @param o a compound header line + * @return true if equal + */ + public boolean equals(Object o) { + if ( !(o instanceof VCFCompoundHeaderLine) ) + return false; + VCFCompoundHeaderLine other = (VCFCompoundHeaderLine)o; + return equalsExcludingDescription(other) && + description.equals(other.description); + } + + public boolean equalsExcludingDescription(VCFCompoundHeaderLine other) { + return count == other.count && + countType == other.countType && + type == other.type && + lineType == other.lineType && + name.equals(other.name); + } + + public boolean sameLineTypeAndName(VCFCompoundHeaderLine other) { + return lineType == other.lineType && + name.equals(other.name); + } + + /** + * do we allow flag (boolean) values? (i.e. booleans where you don't have specify the value, AQ means AQ=true) + * @return true if we do, false otherwise + */ + abstract boolean allowFlagValues(); + +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java new file mode 100755 index 000000000..91f6b1ba9 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR + * THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import java.util.Locale; + +final class VCFConstants { + public static final Locale VCF_LOCALE = Locale.US; + + // reserved INFO/FORMAT field keys + public static final String ANCESTRAL_ALLELE_KEY = "AA"; + public static final String ALLELE_COUNT_KEY = "AC"; + public static final String ALLELE_FREQUENCY_KEY = "AF"; + public static final String ALLELE_NUMBER_KEY = "AN"; + public static final String RMS_BASE_QUALITY_KEY = "BQ"; + public static final String CIGAR_KEY = "CIGAR"; + public static final String DBSNP_KEY = "DB"; + public static final String DEPTH_KEY = "DP"; + public static final String DOWNSAMPLED_KEY = "DS"; + public static final String EXPECTED_ALLELE_COUNT_KEY = "EC"; + public static final String END_KEY = "END"; + public static final String GENOTYPE_FILTER_KEY = "FT"; + public static final String GENOTYPE_KEY = "GT"; + @Deprecated + public static final String GENOTYPE_LIKELIHOODS_KEY = "GL"; // log10 scaled genotype likelihoods + public static final String GENOTYPE_POSTERIORS_KEY = "GP"; + public static final String GENOTYPE_QUALITY_KEY = "GQ"; + public static final String HAPMAP2_KEY = "H2"; + public static final String HAPMAP3_KEY = "H3"; + public static final String HAPLOTYPE_QUALITY_KEY = "HQ"; + public static final String RMS_MAPPING_QUALITY_KEY = "MQ"; + public static final String MAPPING_QUALITY_ZERO_KEY = "MQ0"; + public static final String SAMPLE_NUMBER_KEY = "NS"; + public static final String PHRED_GENOTYPE_LIKELIHOODS_KEY = "PL"; // phred-scaled genotype likelihoods + public static final String PHASE_QUALITY_KEY = "PQ"; + public static final String PHASE_SET_KEY = "PS"; + public static final String OLD_DEPTH_KEY = "RD"; + public static final String STRAND_BIAS_KEY = "SB"; + public static final String SOMATIC_KEY = "SOMATIC"; + public static final String VALIDATED_KEY = "VALIDATED"; + public static final String THOUSAND_GENOMES_KEY = "1000G"; + + // separators + public static final String FORMAT_FIELD_SEPARATOR = ":"; + public static final String GENOTYPE_FIELD_SEPARATOR = ":"; + public static final char GENOTYPE_FIELD_SEPARATOR_CHAR = ':'; + public static final String FIELD_SEPARATOR = "\t"; + public static final char FIELD_SEPARATOR_CHAR = '\t'; + public static final String FILTER_CODE_SEPARATOR = ";"; + public static final String INFO_FIELD_ARRAY_SEPARATOR = ","; + public static final char INFO_FIELD_ARRAY_SEPARATOR_CHAR = ','; + public static final String ID_FIELD_SEPARATOR = ";"; + public static final String INFO_FIELD_SEPARATOR = ";"; + public static final char INFO_FIELD_SEPARATOR_CHAR = ';'; + public static final String UNPHASED = "/"; + public static final String PHASED = "|"; + public static final String PHASED_SWITCH_PROB_v3 = "\\"; + public static final String PHASING_TOKENS = "/|\\"; + + // old indel alleles + public static final char DELETION_ALLELE_v3 = 'D'; + public static final char INSERTION_ALLELE_v3 = 'I'; + + // missing/default values + public static final String UNFILTERED = "."; + public static final String PASSES_FILTERS_v3 = "0"; + public static final String PASSES_FILTERS_v4 = "PASS"; + public static final String EMPTY_ID_FIELD = "."; + public static final String EMPTY_INFO_FIELD = "."; + public static final String EMPTY_ALTERNATE_ALLELE_FIELD = "."; + public static final String MISSING_VALUE_v4 = "."; + public static final String MISSING_QUALITY_v3 = "-1"; + public static final Double MISSING_QUALITY_v3_DOUBLE = Double.valueOf(MISSING_QUALITY_v3); + + public static final String MISSING_GENOTYPE_QUALITY_v3 = "-1"; + public static final String MISSING_HAPLOTYPE_QUALITY_v3 = "-1"; + public static final String MISSING_DEPTH_v3 = "-1"; + public static final String UNBOUNDED_ENCODING_v4 = "."; + public static final String UNBOUNDED_ENCODING_v3 = "-1"; + public static final String PER_ALLELE_COUNT = "A"; + public static final String PER_GENOTYPE_COUNT = "G"; + public static final String EMPTY_ALLELE = "."; + public static final String EMPTY_GENOTYPE = "./."; + public static final double MAX_GENOTYPE_QUAL = 99.0; + + public static final String DOUBLE_PRECISION_FORMAT_STRING = "%.2f"; + public static final String DOUBLE_PRECISION_INT_SUFFIX = ".00"; + public static final Double VCF_ENCODING_EPSILON = 0.00005; // when we consider fields equal(), used in the Qual compare +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java new file mode 100755 index 000000000..5e16fbed0 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java @@ -0,0 +1,28 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +/** + * @author ebanks + * A class representing a key=value entry for FILTER fields in the VCF header + */ +class VCFFilterHeaderLine extends VCFSimpleHeaderLine { + + /** + * create a VCF filter header line + * + * @param name the name for this header line + * @param description the description for this header line + */ + public VCFFilterHeaderLine(String name, String description) { + super(name, description, SupportedHeaderLineType.FILTER); + } + + /** + * create a VCF info header line + * + * @param line the header line + * @param version the vcf header version + */ + protected VCFFilterHeaderLine(String line, VCFHeaderVersion version) { + super(line, version, SupportedHeaderLineType.FILTER); + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java new file mode 100755 index 000000000..f73c032cc --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java @@ -0,0 +1,32 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + + +/** + * @author ebanks + *

+ * Class VCFFormatHeaderLine + *

+ * A class representing a key=value entry for genotype FORMAT fields in the VCF header + */ +class VCFFormatHeaderLine extends VCFCompoundHeaderLine { + + public VCFFormatHeaderLine(String name, int count, VCFHeaderLineType type, String description) { + super(name, count, type, description, SupportedHeaderLineType.FORMAT); + if (type == VCFHeaderLineType.Flag) + throw new IllegalArgumentException("Flag is an unsupported type for format fields"); + } + + public VCFFormatHeaderLine(String name, VCFHeaderLineCount count, VCFHeaderLineType type, String description) { + super(name, count, type, description, SupportedHeaderLineType.FORMAT); + } + + protected VCFFormatHeaderLine(String line, VCFHeaderVersion version) { + super(line, version, SupportedHeaderLineType.FORMAT); + } + + // format fields do not allow flag values (that wouldn't make much sense, how would you encode this in the genotype). + @Override + boolean allowFlagValues() { + return false; + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java new file mode 100755 index 000000000..be1b49ec1 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java @@ -0,0 +1,198 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + + +import org.broad.tribble.util.ParsingUtils; + +import java.util.*; + + +/** + * @author aaron + *

+ * Class VCFHeader + *

+ * A class representing the VCF header + */ +class VCFHeader { + + // the mandatory header fields + public enum HEADER_FIELDS { + CHROM, POS, ID, REF, ALT, QUAL, FILTER, INFO + } + + // the associated meta data + private final Set mMetaData; + private final Map mInfoMetaData = new HashMap(); + private final Map mFormatMetaData = new HashMap(); + private final Map mOtherMetaData = new HashMap(); + + // the list of auxillary tags + private final Set mGenotypeSampleNames = new LinkedHashSet(); + + // the character string that indicates meta data + public static final String METADATA_INDICATOR = "##"; + + // the header string indicator + public static final String HEADER_INDICATOR = "#"; + + // were the input samples sorted originally (or are we sorting them)? + private boolean samplesWereAlreadySorted = true; + + + /** + * create a VCF header, given a list of meta data and auxillary tags + * + * @param metaData the meta data associated with this header + */ + public VCFHeader(Set metaData) { + mMetaData = new TreeSet(metaData); + loadVCFVersion(); + loadMetaDataMaps(); + } + + /** + * create a VCF header, given a list of meta data and auxillary tags + * + * @param metaData the meta data associated with this header + * @param genotypeSampleNames the sample names + */ + public VCFHeader(Set metaData, Set genotypeSampleNames) { + mMetaData = new TreeSet(); + if ( metaData != null ) + mMetaData.addAll(metaData); + + mGenotypeSampleNames.addAll(genotypeSampleNames); + + loadVCFVersion(); + loadMetaDataMaps(); + + samplesWereAlreadySorted = ParsingUtils.isSorted(genotypeSampleNames); + } + + /** + * Adds a header line to the header metadata. + * + * @param headerLine Line to add to the existing metadata component. + */ + public void addMetaDataLine(VCFHeaderLine headerLine) { + mMetaData.add(headerLine); + } + + /** + * check our metadata for a VCF version tag, and throw an exception if the version is out of date + * or the version is not present + */ + public void loadVCFVersion() { + List toRemove = new ArrayList(); + for ( VCFHeaderLine line : mMetaData ) + if ( VCFHeaderVersion.isFormatString(line.getKey())) { + toRemove.add(line); + } + // remove old header lines for now, + mMetaData.removeAll(toRemove); + + } + + /** + * load the format/info meta data maps (these are used for quick lookup by key name) + */ + private void loadMetaDataMaps() { + for ( VCFHeaderLine line : mMetaData ) { + if ( line instanceof VCFInfoHeaderLine ) { + VCFInfoHeaderLine infoLine = (VCFInfoHeaderLine)line; + mInfoMetaData.put(infoLine.getName(), infoLine); + } + else if ( line instanceof VCFFormatHeaderLine ) { + VCFFormatHeaderLine formatLine = (VCFFormatHeaderLine)line; + mFormatMetaData.put(formatLine.getName(), formatLine); + } + else { + mOtherMetaData.put(line.getKey(), line); + } + } + } + + /** + * get the header fields in order they're presented in the input file (which is now required to be + * the order presented in the spec). + * + * @return a set of the header fields, in order + */ + public Set getHeaderFields() { + Set fields = new LinkedHashSet(); + for (HEADER_FIELDS field : HEADER_FIELDS.values()) + fields.add(field); + return fields; + } + + /** + * get the meta data, associated with this header + * + * @return a set of the meta data + */ + public Set getMetaData() { + Set lines = new LinkedHashSet(); + lines.add(new VCFHeaderLine(VCFHeaderVersion.VCF4_0.getFormatString(), VCFHeaderVersion.VCF4_0.getVersionString())); + lines.addAll(mMetaData); + return Collections.unmodifiableSet(lines); + } + + /** + * get the genotyping sample names + * + * @return a list of the genotype column names, which may be empty if hasGenotypingData() returns false + */ + public Set getGenotypeSamples() { + return mGenotypeSampleNames; + } + + /** + * do we have genotyping data? + * + * @return true if we have genotyping columns, false otherwise + */ + public boolean hasGenotypingData() { + return mGenotypeSampleNames.size() > 0; + } + + /** + * were the input samples sorted originally? + * + * @return true if the input samples were sorted originally, false otherwise + */ + public boolean samplesWereAlreadySorted() { + return samplesWereAlreadySorted; + } + + /** @return the column count */ + public int getColumnCount() { + return HEADER_FIELDS.values().length + (hasGenotypingData() ? mGenotypeSampleNames.size() + 1 : 0); + } + + /** + * @param key the header key name + * @return the meta data line, or null if there is none + */ + public VCFInfoHeaderLine getInfoHeaderLine(String key) { + return mInfoMetaData.get(key); + } + + /** + * @param key the header key name + * @return the meta data line, or null if there is none + */ + public VCFFormatHeaderLine getFormatHeaderLine(String key) { + return mFormatMetaData.get(key); + } + + /** + * @param key the header key name + * @return the meta data line, or null if there is none + */ + public VCFHeaderLine getOtherHeaderLine(String key) { + return mOtherMetaData.get(key); + } +} + + + diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java new file mode 100755 index 000000000..61b0722bd --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2010. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR + * THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broad.tribble.TribbleException; + +import java.util.Map; + + +/** + * @author ebanks + *

+ * Class VCFHeaderLine + *

+ * A class representing a key=value entry in the VCF header + */ +class VCFHeaderLine implements Comparable { + protected static boolean ALLOW_UNBOUND_DESCRIPTIONS = true; + protected static String UNBOUND_DESCRIPTION = "Not provided in original VCF header"; + + private String mKey = null; + private String mValue = null; + + + /** + * create a VCF header line + * + * @param key the key for this header line + * @param value the value for this header line + */ + public VCFHeaderLine(String key, String value) { + if ( key == null ) + throw new IllegalArgumentException("VCFHeaderLine: key cannot be null: key = " + key); + mKey = key; + mValue = value; + } + + /** + * Get the key + * + * @return the key + */ + public String getKey() { + return mKey; + } + + /** + * Get the value + * + * @return the value + */ + public String getValue() { + return mValue; + } + + public String toString() { + return toStringEncoding(); + } + + /** + * Should be overloaded in sub classes to do subclass specific + * + * @return the string encoding + */ + protected String toStringEncoding() { + return mKey + "=" + mValue; + } + + public boolean equals(Object o) { + if ( !(o instanceof VCFHeaderLine) ) + return false; + return mKey.equals(((VCFHeaderLine)o).getKey()) && mValue.equals(((VCFHeaderLine)o).getValue()); + } + + public int compareTo(Object other) { + return toString().compareTo(other.toString()); + } + + /** + * @param line the line + * @return true if the line is a VCF meta data line, or false if it is not + */ + public static boolean isHeaderLine(String line) { + return line != null && line.length() > 0 && VCFHeader.HEADER_INDICATOR.equals(line.substring(0,1)); + } + + /** + * create a string of a mapping pair for the target VCF version + * @param keyValues a mapping of the key->value pairs to output + * @return a string, correctly formatted + */ + public static String toStringEncoding(Map keyValues) { + StringBuilder builder = new StringBuilder(); + builder.append("<"); + boolean start = true; + for (Map.Entry entry : keyValues.entrySet()) { + if (start) start = false; + else builder.append(","); + + if ( entry.getValue() == null ) throw new TribbleException.InternalCodecException("Header problem: unbound value at " + entry + " from " + keyValues); + + builder.append(entry.getKey()); + builder.append("="); + builder.append(entry.getValue().toString().contains(",") || + entry.getValue().toString().contains(" ") || + entry.getKey().equals("Description") ? "\""+ entry.getValue() + "\"" : entry.getValue()); + } + builder.append(">"); + return builder.toString(); + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java new file mode 100644 index 000000000..8fd29d188 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java @@ -0,0 +1,8 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +/** + * the count encodings we use for fields in VCF header lines + */ +public enum VCFHeaderLineCount { + INTEGER, A, G, UNBOUNDED; +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java new file mode 100755 index 000000000..538b4ff8e --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java @@ -0,0 +1,124 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import java.util.*; + +/** + * A class for translating between vcf header versions + */ +public class VCFHeaderLineTranslator { + private static Map mapping; + + static { + mapping = new HashMap(); + mapping.put(VCFHeaderVersion.VCF4_0,new VCF4Parser()); + mapping.put(VCFHeaderVersion.VCF4_1,new VCF4Parser()); + mapping.put(VCFHeaderVersion.VCF3_3,new VCF3Parser()); + mapping.put(VCFHeaderVersion.VCF3_2,new VCF3Parser()); + } + + public static Map parseLine(VCFHeaderVersion version, String valueLine, List expectedTagOrder) { + return mapping.get(version).parseLine(valueLine,expectedTagOrder); + } +} + + +interface VCFLineParser { + public Map parseLine(String valueLine, List expectedTagOrder); +} + + +/** + * a class that handles the to and from disk for VCF 4 lines + */ +class VCF4Parser implements VCFLineParser { + Set bracketed = new HashSet(); + + /** + * parse a VCF4 line + * @param valueLine the line + * @return a mapping of the tags parsed out + */ + public Map parseLine(String valueLine, List expectedTagOrder) { + // our return map + Map ret = new LinkedHashMap(); + + // a builder to store up characters as we go + StringBuilder builder = new StringBuilder(); + + // store the key when we're parsing out the values + String key = ""; + + // where are we in the stream of characters? + int index = 0; + + // are we inside a quotation? we don't special case ',' then + boolean inQuote = false; + + // a little switch machine to parse out the tags. Regex ended up being really complicated and ugly [yes, but this machine is getting ugly now... MAD] + for (char c: valueLine.toCharArray()) { + if ( c == '\"' ) { + inQuote = ! inQuote; + } else if ( inQuote ) { + builder.append(c); + } else { + switch (c) { + case ('<') : if (index == 0) break; // if we see a open bracket at the beginning, ignore it + case ('>') : if (index == valueLine.length()-1) ret.put(key,builder.toString().trim()); break; // if we see a close bracket, and we're at the end, add an entry to our list + case ('=') : key = builder.toString().trim(); builder = new StringBuilder(); break; // at an equals, copy the key and reset the builder + case (',') : ret.put(key,builder.toString().trim()); builder = new StringBuilder(); break; // drop the current key value to the return map + default: builder.append(c); // otherwise simply append to the current string + } + } + + index++; + } + + // validate the tags against the expected list + index = 0; + if (ret.size() > expectedTagOrder.size()) throw new IllegalArgumentException("Unexpected tag count " + ret.size() + " in string " + expectedTagOrder.size()); + for (String str : ret.keySet()) { + if (!expectedTagOrder.get(index).equals(str)) throw new IllegalArgumentException("Unexpected tag " + str + " in string " + valueLine); + index++; + } + return ret; + } +} + +class VCF3Parser implements VCFLineParser { + + public Map parseLine(String valueLine, List expectedTagOrder) { + // our return map + Map ret = new LinkedHashMap(); + + // a builder to store up characters as we go + StringBuilder builder = new StringBuilder(); + + // where are we in the stream of characters? + int index = 0; + // where in the expected tag order are we? + int tagIndex = 0; + + // are we inside a quotation? we don't special case ',' then + boolean inQuote = false; + + // a little switch machine to parse out the tags. Regex ended up being really complicated and ugly + for (char c: valueLine.toCharArray()) { + switch (c) { + case ('\"') : inQuote = !inQuote; break; // a quote means we ignore ',' in our strings, keep track of it + case (',') : if (!inQuote) { ret.put(expectedTagOrder.get(tagIndex++),builder.toString()); builder = new StringBuilder(); break; } // drop the current key value to the return map + default: builder.append(c); // otherwise simply append to the current string + } + index++; + } + ret.put(expectedTagOrder.get(tagIndex++),builder.toString()); + + // validate the tags against the expected list + index = 0; + if (tagIndex != expectedTagOrder.size()) throw new IllegalArgumentException("Unexpected tag count " + tagIndex + ", we expected " + expectedTagOrder.size()); + for (String str : ret.keySet()){ + if (!expectedTagOrder.get(index).equals(str)) throw new IllegalArgumentException("Unexpected tag " + str + " in string " + valueLine); + index++; + } + return ret; + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java new file mode 100755 index 000000000..65cf1a327 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java @@ -0,0 +1,8 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +/** + * the type encodings we use for fields in VCF header lines + */ +enum VCFHeaderLineType { + Integer, Float, String, Character, Flag; +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java new file mode 100755 index 000000000..21e737abe --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java @@ -0,0 +1,91 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broad.tribble.TribbleException; + +/** + * information that identifies each header version + */ +enum VCFHeaderVersion { + VCF3_2("VCRv3.2","format"), + VCF3_3("VCFv3.3","fileformat"), + VCF4_0("VCFv4.0","fileformat"), + VCF4_1("VCFv4.1","fileformat"); + + private final String versionString; + private final String formatString; + + /** + * create the enum, privately, using: + * @param vString the version string + * @param fString the format string + */ + VCFHeaderVersion(String vString, String fString) { + this.versionString = vString; + this.formatString = fString; + } + + /** + * get the header version + * @param version the version string + * @return a VCFHeaderVersion object + */ + public static VCFHeaderVersion toHeaderVersion(String version) { + version = clean(version); + for (VCFHeaderVersion hv : VCFHeaderVersion.values()) + if (hv.versionString.equals(version)) + return hv; + return null; + } + + /** + * are we a valid version string of some type + * @param version the version string + * @return true if we're valid of some type, false otherwise + */ + public static boolean isVersionString(String version){ + return toHeaderVersion(version) != null; + } + + /** + * are we a valid format string for some type + * @param format the format string + * @return true if we're valid of some type, false otherwise + */ + public static boolean isFormatString(String format){ + format = clean(format); + for (VCFHeaderVersion hv : VCFHeaderVersion.values()) + if (hv.formatString.equals(format)) + return true; + return false; + } + + public static VCFHeaderVersion getHeaderVersion(String versionLine) { + String[] lineFields = versionLine.split("="); + if ( lineFields.length != 2 || !isFormatString(lineFields[0].substring(2)) ) + throw new TribbleException.InvalidHeader(versionLine + " is not a valid VCF version line"); + + if ( !isVersionString(lineFields[1]) ) + throw new TribbleException.InvalidHeader(lineFields[1] + " is not a supported version"); + + return toHeaderVersion(lineFields[1]); + } + + /** + * Utility function to clean up a VCF header string + * + * @param s string + * @return trimmed version of s + */ + private static String clean(String s) { + return s.trim(); + } + + + public String getVersionString() { + return versionString; + } + + public String getFormatString() { + return formatString; + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java new file mode 100755 index 000000000..642a78b76 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java @@ -0,0 +1,29 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + + +/** + * @author ebanks + *

+ * Class VCFInfoHeaderLine + *

+ * A class representing a key=value entry for INFO fields in the VCF header + */ +class VCFInfoHeaderLine extends VCFCompoundHeaderLine { + public VCFInfoHeaderLine(String name, int count, VCFHeaderLineType type, String description) { + super(name, count, type, description, SupportedHeaderLineType.INFO); + } + + public VCFInfoHeaderLine(String name, VCFHeaderLineCount count, VCFHeaderLineType type, String description) { + super(name, count, type, description, SupportedHeaderLineType.INFO); + } + + protected VCFInfoHeaderLine(String line, VCFHeaderVersion version) { + super(line, version, SupportedHeaderLineType.INFO); + } + + // info fields allow flag values + @Override + boolean allowFlagValues() { + return true; + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java new file mode 100755 index 000000000..b3ce5d841 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +/** an interface for named header lines **/ +interface VCFNamedHeaderLine { + String getName(); +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java new file mode 100755 index 000000000..95a3f7bf1 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java @@ -0,0 +1,22 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import java.util.List; +import java.util.Map; + + +/** + * All VCF codecs need to implement this interface so that we can perform lazy loading. + */ +interface VCFParser { + + /** + * create a genotype map + * @param str the string + * @param alleles the list of alleles + * @param chr chrom + * @param pos position + * @return a mapping of sample name to genotype object + */ + public Map createGenotypeMap(String str, List alleles, String chr, int pos); + +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java new file mode 100644 index 000000000..17706c705 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java @@ -0,0 +1,81 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * @author ebanks + * A class representing a key=value entry for simple VCF header types + */ +abstract class VCFSimpleHeaderLine extends VCFHeaderLine implements VCFNamedHeaderLine { + + public enum SupportedHeaderLineType { + FILTER, ALT; + } + + private String name; + private String description; + + // our type of line, i.e. filter, alt, etc + private final SupportedHeaderLineType lineType; + + + /** + * create a VCF filter header line + * + * @param name the name for this header line + * @param description the description for this header line + * @param lineType the header line type + */ + public VCFSimpleHeaderLine(String name, String description, SupportedHeaderLineType lineType) { + super(lineType.toString(), ""); + this.lineType = lineType; + this.name = name; + this.description = description; + + if ( name == null || description == null ) + throw new IllegalArgumentException(String.format("Invalid VCFSimpleHeaderLine: key=%s name=%s desc=%s", super.getKey(), name, description )); + } + + /** + * create a VCF info header line + * + * @param line the header line + * @param version the vcf header version + * @param lineType the header line type + */ + protected VCFSimpleHeaderLine(String line, VCFHeaderVersion version, SupportedHeaderLineType lineType) { + super(lineType.toString(), ""); + this.lineType = lineType; + Map mapping = VCFHeaderLineTranslator.parseLine(version,line, Arrays.asList("ID","Description")); + name = mapping.get("ID"); + description = mapping.get("Description"); + if ( description == null && ALLOW_UNBOUND_DESCRIPTIONS ) // handle the case where there's no description provided + description = UNBOUND_DESCRIPTION; + } + + protected String toStringEncoding() { + Map map = new LinkedHashMap(); + map.put("ID", name); + map.put("Description", description); + return lineType.toString() + "=" + toStringEncoding(map); + } + + public boolean equals(Object o) { + if ( !(o instanceof VCFSimpleHeaderLine) ) + return false; + VCFSimpleHeaderLine other = (VCFSimpleHeaderLine)o; + return name.equals(other.name) && + description.equals(other.description); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java new file mode 100755 index 000000000..dc78d40ac --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2010 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR + * THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.apache.log4j.Logger; +import org.broad.tribble.Feature; +import org.broadinstitute.sting.commandline.RodBinding; +import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; +import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; + +import java.util.*; + +/** + * A set of static utility methods for common operations on VCF files/records. + */ +class VCFUtils { + /** + * Constructor access disallowed...static utility methods only! + */ + private VCFUtils() { } + + public static Map getVCFHeadersFromRods(GenomeAnalysisEngine toolkit, List> rodBindings) { + // Collect the eval rod names + final Set names = new TreeSet(); + for ( final RodBinding evalRod : rodBindings ) + names.add(evalRod.getName()); + return getVCFHeadersFromRods(toolkit, names); + } + + public static Map getVCFHeadersFromRods(GenomeAnalysisEngine toolkit) { + return getVCFHeadersFromRods(toolkit, (Collection)null); + } + + public static Map getVCFHeadersFromRods(GenomeAnalysisEngine toolkit, Collection rodNames) { + Map data = new HashMap(); + + // iterate to get all of the sample names + List dataSources = toolkit.getRodDataSources(); + for ( ReferenceOrderedDataSource source : dataSources ) { + // ignore the rod if it's not in our list + if ( rodNames != null && !rodNames.contains(source.getName()) ) + continue; + + if ( source.getHeader() != null && source.getHeader() instanceof VCFHeader ) + data.put(source.getName(), (VCFHeader)source.getHeader()); + } + + return data; + } + + public static Map getVCFHeadersFromRodPrefix(GenomeAnalysisEngine toolkit,String prefix) { + Map data = new HashMap(); + + // iterate to get all of the sample names + List dataSources = toolkit.getRodDataSources(); + for ( ReferenceOrderedDataSource source : dataSources ) { + // ignore the rod if lacks the prefix + if ( ! source.getName().startsWith(prefix) ) + continue; + + if ( source.getHeader() != null && source.getHeader() instanceof VCFHeader ) + data.put(source.getName(), (VCFHeader)source.getHeader()); + } + + return data; + } + + /** + * Gets the header fields from all VCF rods input by the user + * + * @param toolkit GATK engine + * + * @return a set of all fields + */ + public static Set getHeaderFields(GenomeAnalysisEngine toolkit) { + return getHeaderFields(toolkit, null); + } + + /** + * Gets the header fields from all VCF rods input by the user + * + * @param toolkit GATK engine + * @param rodNames names of rods to use, or null if we should use all possible ones + * + * @return a set of all fields + */ + public static Set getHeaderFields(GenomeAnalysisEngine toolkit, Collection rodNames) { + + // keep a map of sample name to occurrences encountered + TreeSet fields = new TreeSet(); + + // iterate to get all of the sample names + List dataSources = toolkit.getRodDataSources(); + for ( ReferenceOrderedDataSource source : dataSources ) { + // ignore the rod if it's not in our list + if ( rodNames != null && !rodNames.contains(source.getName()) ) + continue; + + if ( source.getRecordType().equals(VariantContext.class)) { + VCFHeader header = (VCFHeader)source.getHeader(); + if ( header != null ) + fields.addAll(header.getMetaData()); + } + } + + return fields; + } + + /** Only displays a warning if a logger is provided and an identical warning hasn't been already issued */ + private static final class HeaderConflictWarner { + Logger logger; + Set alreadyIssued = new HashSet(); + + private HeaderConflictWarner(final Logger logger) { + this.logger = logger; + } + + public void warn(final VCFHeaderLine line, final String msg) { + if ( logger != null && ! alreadyIssued.contains(line.getKey()) ) { + alreadyIssued.add(line.getKey()); + logger.warn(msg); + } + } + } + + public static Set smartMergeHeaders(Collection headers, Logger logger) throws IllegalStateException { + HashMap map = new HashMap(); // from KEY.NAME -> line + HeaderConflictWarner conflictWarner = new HeaderConflictWarner(logger); + + // todo -- needs to remove all version headers from sources and add its own VCF version line + for ( VCFHeader source : headers ) { + //System.out.printf("Merging in header %s%n", source); + for ( VCFHeaderLine line : source.getMetaData()) { + String key = line.getKey(); + + if ( line instanceof VCFNamedHeaderLine) + key = key + "" + ((VCFNamedHeaderLine) line).getName(); + + if ( map.containsKey(key) ) { + VCFHeaderLine other = map.get(key); + if ( line.equals(other) ) + continue; + else if ( ! line.getClass().equals(other.getClass()) ) + throw new IllegalStateException("Incompatible header types: " + line + " " + other ); + else if ( line instanceof VCFFilterHeaderLine) { + String lineName = ((VCFFilterHeaderLine) line).getName(); String otherName = ((VCFFilterHeaderLine) other).getName(); + if ( ! lineName.equals(otherName) ) + throw new IllegalStateException("Incompatible header types: " + line + " " + other ); + } else if ( line instanceof VCFCompoundHeaderLine ) { + VCFCompoundHeaderLine compLine = (VCFCompoundHeaderLine)line; + VCFCompoundHeaderLine compOther = (VCFCompoundHeaderLine)other; + + // if the names are the same, but the values are different, we need to quit + if (! (compLine).equalsExcludingDescription(compOther) ) { + if ( compLine.getType().equals(compOther.getType()) ) { + // The Number entry is an Integer that describes the number of values that can be + // included with the INFO field. For example, if the INFO field contains a single + // number, then this value should be 1. However, if the INFO field describes a pair + // of numbers, then this value should be 2 and so on. If the number of possible + // values varies, is unknown, or is unbounded, then this value should be '.'. + conflictWarner.warn(line, "Promoting header field Number to . due to number differences in header lines: " + line + " " + other); + compOther.setNumberToUnbounded(); + } else if ( compLine.getType() == VCFHeaderLineType.Integer && compOther.getType() == VCFHeaderLineType.Float ) { + // promote key to Float + conflictWarner.warn(line, "Promoting Integer to Float in header: " + compOther); + map.put(key, compOther); + } else if ( compLine.getType() == VCFHeaderLineType.Float && compOther.getType() == VCFHeaderLineType.Integer ) { + // promote key to Float + conflictWarner.warn(line, "Promoting Integer to Float in header: " + compOther); + } else { + throw new IllegalStateException("Incompatible header types, collision between these two types: " + line + " " + other ); + } + } + if ( ! compLine.getDescription().equals(compOther) ) + conflictWarner.warn(line, "Allowing unequal description fields through: keeping " + compOther + " excluding " + compLine); + } else { + // we are not equal, but we're not anything special either + conflictWarner.warn(line, "Ignoring header line already in map: this header line = " + line + " already present header = " + other); + } + } else { + map.put(key, line); + //System.out.printf("Adding header line %s%n", line); + } + } + } + + return new HashSet(map.values()); + } + + public static String rsIDOfFirstRealVariant(List VCs, VariantContext.Type type) { + if ( VCs == null ) + return null; + + String rsID = null; + for ( VariantContext vc : VCs ) { + if ( vc.getType() == type ) { + rsID = vc.getID(); + break; + } + } + + return rsID; + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java new file mode 100755 index 000000000..15bdb5046 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java @@ -0,0 +1,16 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +/** + * this class writes VCF files + */ +public interface VCFWriter { + + public void writeHeader(VCFHeader header); + + /** + * attempt to close the VCF file + */ + public void close(); + + public void add(VariantContext vc); +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java new file mode 100755 index 000000000..3a193a00a --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java @@ -0,0 +1,1615 @@ +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.broad.tribble.Feature; +import org.broad.tribble.TribbleException; +import org.broad.tribble.util.ParsingUtils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.*; + +/** + * Class VariantContext + * + * == High-level overview == + * + * The VariantContext object is a single general class system for representing genetic variation data composed of: + * + * * Allele: representing single genetic haplotypes (A, T, ATC, -) + * * Genotype: an assignment of alleles for each chromosome of a single named sample at a particular locus + * * VariantContext: an abstract class holding all segregating alleles at a locus as well as genotypes + * for multiple individuals containing alleles at that locus + * + * The class system works by defining segregating alleles, creating a variant context representing the segregating + * information at a locus, and potentially creating and associating genotypes with individuals in the context. + * + * All of the classes are highly validating -- call validate() if you modify them -- so you can rely on the + * self-consistency of the data once you have a VariantContext in hand. The system has a rich set of assessor + * and manipulator routines, as well as more complex static support routines in VariantContextUtils. + * + * The VariantContext (and Genotype) objects are attributed (supporting addition of arbitrary key/value pairs) and + * filtered (can represent a variation that is viewed as suspect). + * + * VariantContexts are dynamically typed, so whether a VariantContext is a SNP, Indel, or NoVariant depends + * on the properties of the alleles in the context. See the detailed documentation on the Type parameter below. + * + * It's also easy to create subcontexts based on selected genotypes. + * + * == Working with Variant Contexts == + * By default, VariantContexts are immutable. In order to access (in the rare circumstances where you need them) + * setter routines, you need to create MutableVariantContexts and MutableGenotypes. + * + * === Some example data === + * + * Allele A, Aref, T, Tref; + * Allele del, delRef, ATC, ATCref; + * + * A [ref] / T at 10 + * GenomeLoc snpLoc = GenomeLocParser.createGenomeLoc("chr1", 10, 10); + * + * - / ATC [ref] from 20-23 + * GenomeLoc delLoc = GenomeLocParser.createGenomeLoc("chr1", 20, 22); + * + * // - [ref] / ATC immediately after 20 + * GenomeLoc insLoc = GenomeLocParser.createGenomeLoc("chr1", 20, 20); + * + * === Alleles === + * + * See the documentation in the Allele class itself + * + * What are they? + * + * Alleles can be either reference or non-reference + * + * Example alleles used here: + * + * del = new Allele("-"); + * A = new Allele("A"); + * Aref = new Allele("A", true); + * T = new Allele("T"); + * ATC = new Allele("ATC"); + * + * === Creating variant contexts === + * + * ==== By hand ==== + * + * Here's an example of a A/T polymorphism with the A being reference: + * + *

+ * VariantContext vc = new VariantContext(name, snpLoc, Arrays.asList(Aref, T));
+ * 
+ * + * If you want to create a non-variant site, just put in a single reference allele + * + *
+ * VariantContext vc = new VariantContext(name, snpLoc, Arrays.asList(Aref));
+ * 
+ * + * A deletion is just as easy: + * + *
+ * VariantContext vc = new VariantContext(name, delLoc, Arrays.asList(ATCref, del));
+ * 
+ * + * The only 2 things that distinguishes between a insertion and deletion are the reference allele + * and the location of the variation. An insertion has a Null reference allele and at least + * one non-reference Non-Null allele. Additionally, the location of the insertion is immediately after + * a 1-bp GenomeLoc (at say 20). + * + *
+ * VariantContext vc = new VariantContext("name", insLoc, Arrays.asList(delRef, ATC));
+ * 
+ * + * ==== Converting rods and other data structures to VCs ==== + * + * You can convert many common types into VariantContexts using the general function: + * + *
+ * VariantContextAdaptors.convertToVariantContext(name, myObject)
+ * 
+ * + * dbSNP and VCFs, for example, can be passed in as myObject and a VariantContext corresponding to that + * object will be returned. A null return type indicates that the type isn't yet supported. This is the best + * and easiest way to create contexts using RODs. + * + * + * === Working with genotypes === + * + *
+ * List alleles = Arrays.asList(Aref, T);
+ * Genotype g1 = new Genotype(Arrays.asList(Aref, Aref), "g1", 10);
+ * Genotype g2 = new Genotype(Arrays.asList(Aref, T), "g2", 10);
+ * Genotype g3 = new Genotype(Arrays.asList(T, T), "g3", 10);
+ * VariantContext vc = new VariantContext(snpLoc, alleles, Arrays.asList(g1, g2, g3));
+ * 
+ * + * At this point we have 3 genotypes in our context, g1-g3. + * + * You can assess a good deal of information about the genotypes through the VariantContext: + * + *
+ * vc.hasGenotypes()
+ * vc.isMonomorphic()
+ * vc.isPolymorphic()
+ * vc.getSamples().size()
+ *
+ * vc.getGenotypes()
+ * vc.getGenotypes().get("g1")
+ * vc.hasGenotype("g1")
+ *
+ * vc.getChromosomeCount()
+ * vc.getChromosomeCount(Aref)
+ * vc.getChromosomeCount(T)
+ * 
+ * + * === NO_CALL alleles === + * + * The system allows one to create Genotypes carrying special NO_CALL alleles that aren't present in the + * set of context alleles and that represent undetermined alleles in a genotype: + * + * Genotype g4 = new Genotype(Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), "NO_DATA_FOR_SAMPLE", 10); + * + * + * === subcontexts === + * It's also very easy get subcontext based only the data in a subset of the genotypes: + * + *
+ * VariantContext vc12 = vc.subContextFromGenotypes(Arrays.asList(g1,g2));
+ * VariantContext vc1 = vc.subContextFromGenotypes(Arrays.asList(g1));
+ * 
+ * + * @author depristo + */ +public class VariantContext implements Feature { // to enable tribble intergration + protected InferredGeneticContext commonInfo = null; + public final static double NO_NEG_LOG_10PERROR = InferredGeneticContext.NO_NEG_LOG_10PERROR; + public final static String UNPARSED_GENOTYPE_MAP_KEY = "_UNPARSED_GENOTYPE_MAP_"; + public final static String UNPARSED_GENOTYPE_PARSER_KEY = "_UNPARSED_GENOTYPE_PARSER_"; + public final static String ID_KEY = "ID"; + + private final Byte REFERENCE_BASE_FOR_INDEL; + + public final static Set PASSES_FILTERS = Collections.unmodifiableSet(new LinkedHashSet()); + + /** The location of this VariantContext */ + protected String contig; + protected long start; + protected long stop; + + /** The type (cached for performance reasons) of this context */ + protected Type type = null; + + /** A set of the alleles segregating in this context */ + final protected List alleles; + + /** A mapping from sampleName -> genotype objects for all genotypes associated with this context */ + protected Map genotypes = null; + + /** Counts for each of the possible Genotype types in this context */ + protected int[] genotypeCounts = null; + + public final static Map NO_GENOTYPES = Collections.unmodifiableMap(new HashMap()); + + // a fast cached access point to the ref / alt alleles for biallelic case + private Allele REF = null; + + // set to the alt allele when biallelic, otherwise == null + private Allele ALT = null; + + // were filters applied? + private boolean filtersWereAppliedToContext; + + // --------------------------------------------------------------------------------------------------------- + // + // constructors + // + // --------------------------------------------------------------------------------------------------------- + + + /** + * the complete constructor. Makes a complete VariantContext from its arguments + * This is the only constructor that is able to create indels! DO NOT USE THE OTHER ONES. + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + * @param genotypes genotypes map + * @param negLog10PError qual + * @param filters filters: use null for unfiltered and empty set for passes filters + * @param attributes attributes + * @param referenceBaseForIndel padded reference base + */ + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); + } + + /** + * the complete constructor. Makes a complete VariantContext from its arguments + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + * @param genotypes genotypes map + * @param negLog10PError qual + * @param filters filters: use null for unfiltered and empty set for passes filters + * @param attributes attributes + */ + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { + this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); + } + + /** + * Makes a VariantContext from its arguments without parsing the genotypes. + * Note that this constructor assumes that if there is genotype data, then it's been put into + * the attributes with the UNPARSED_GENOTYPE_MAP_KEY and that the codec has been added with the + * UNPARSED_GENOTYPE_PARSER_KEY. It doesn't validate that this is the case because it's possible + * that there is no genotype data. + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + * @param negLog10PError qual + * @param filters filters: use null for unfiltered and empty set for passes filters + * @param attributes attributes + * @param referenceBaseForIndel padded reference base + */ + public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true, true); + } + + /** + * Create a new VariantContext + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + * @param genotypes genotypes set + * @param negLog10PError qual + * @param filters filters: use null for unfiltered and empty set for passes filters + * @param attributes attributes + */ + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + this(source, contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes, null, false, true); + } + + /** + * Create a new variant context without genotypes and no Perror, no filters, and no attributes + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + */ + public VariantContext(String source, String contig, long start, long stop, Collection alleles) { + this(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, null, false, true); + } + + /** + * Create a new variant context with genotypes but without Perror, filters, and attributes + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + * @param genotypes genotypes + */ + public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { + this(source, contig, start, stop, alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + } + + /** + * Copy constructor + * + * @param other the VariantContext to copy + */ + public VariantContext(VariantContext other) { + this(other.getSource(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, true); + } + + /** + * the actual constructor. Private access only + * + * @param source source + * @param contig the contig + * @param start the start base (one based) + * @param stop the stop reference base (one based) + * @param alleles alleles + * @param genotypes genotypes map + * @param negLog10PError qual + * @param filters filters: use null for unfiltered and empty set for passes filters + * @param attributes attributes + * @param referenceBaseForIndel padded reference base + * @param genotypesAreUnparsed true if the genotypes have not yet been parsed + * @param performValidation if true, call validate() as the final step in construction + */ + private VariantContext(String source, String contig, long start, long stop, + Collection alleles, Map genotypes, + double negLog10PError, Set filters, Map attributes, + Byte referenceBaseForIndel, boolean genotypesAreUnparsed, + boolean performValidation ) { + if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } + this.contig = contig; + this.start = start; + this.stop = stop; + + if ( !genotypesAreUnparsed && attributes != null ) { + if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) + attributes.remove(UNPARSED_GENOTYPE_MAP_KEY); + if ( attributes.containsKey(UNPARSED_GENOTYPE_PARSER_KEY) ) + attributes.remove(UNPARSED_GENOTYPE_PARSER_KEY); + } + + this.commonInfo = new InferredGeneticContext(source, negLog10PError, filters, attributes); + filtersWereAppliedToContext = filters != null; + REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; + + if ( alleles == null ) { throw new IllegalArgumentException("Alleles cannot be null"); } + + // we need to make this a LinkedHashSet in case the user prefers a given ordering of alleles + this.alleles = makeAlleles(alleles); + + + if ( genotypes == null ) { genotypes = NO_GENOTYPES; } + this.genotypes = Collections.unmodifiableMap(genotypes); + + // cache the REF and ALT alleles + int nAlleles = alleles.size(); + for ( Allele a : alleles ) { + if ( a.isReference() ) { + REF = a; + } else if ( nAlleles == 2 ) { // only cache ALT when biallelic + ALT = a; + } + } + + if ( performValidation ) { + validate(); + } + } + + // --------------------------------------------------------------------------------------------------------- + // + // Partial-cloning routines (because Variant Context is immutable). + // + // IMPORTANT: These routines assume that the VariantContext on which they're called is already valid. + // Due to this assumption, they explicitly tell the constructor NOT to perform validation by + // calling validate(), and instead perform validation only on the data that's changed. + // + // Note that we don't call vc.getGenotypes() because that triggers the lazy loading. + // Also note that we need to create a new attributes map because it's unmodifiable and the constructor may try to modify it. + // + // --------------------------------------------------------------------------------------------------------- + + public static VariantContext modifyGenotypes(VariantContext vc, Map genotypes) { + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), false, false); + modifiedVC.validateGenotypes(); + return modifiedVC; + } + + public static VariantContext modifyLocation(VariantContext vc, String chr, int start, int end) { + VariantContext modifiedVC = new VariantContext(vc.getSource(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); + + // Since start and end have changed, we need to call both validateAlleles() and validateReferencePadding(), + // since those validation routines rely on the values of start and end: + modifiedVC.validateAlleles(); + modifiedVC.validateReferencePadding(); + + return modifiedVC; + } + + public static VariantContext modifyFilters(VariantContext vc, Set filters) { + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); + } + + public static VariantContext modifyAttributes(VariantContext vc, Map attributes) { + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); + } + + public static VariantContext modifyReferencePadding(VariantContext vc, Byte b) { + VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true, false); + modifiedVC.validateReferencePadding(); + return modifiedVC; + } + + public static VariantContext modifyPErrorFiltersAndAttributes(VariantContext vc, double negLog10PError, Set filters, Map attributes) { + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true, false); + } + + // --------------------------------------------------------------------------------------------------------- + // + // Selectors + // + // --------------------------------------------------------------------------------------------------------- + + /** + * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotype + * genotype and alleles in genotype. This is the right way to test if a single genotype is actually + * variant or not. + * + * @param genotype genotype + * @return vc subcontext + */ + public VariantContext subContextFromGenotypes(Genotype genotype) { + return subContextFromGenotypes(Arrays.asList(genotype)); + } + + + /** + * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes + * genotypes and alleles in these genotypes. This is the right way to test if a single genotype is actually + * variant or not. + * + * @param genotypes genotypes + * @return vc subcontext + */ + public VariantContext subContextFromGenotypes(Collection genotypes) { + return subContextFromGenotypes(genotypes, allelesOfGenotypes(genotypes)) ; + } + + /** + * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes + * genotypes. Also, the resulting variant context will contain the alleles provided, not only those found in genotypes + * + * @param genotypes genotypes + * @param alleles the set of allele segregating alleles at this site. Must include those in genotypes, but may be more + * @return vc subcontext + */ + public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { + return new VariantContext(getSource(), contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), getReferenceBaseForIndel()); + } + + + /** + * helper routine for subcontext + * @param genotypes genotypes + * @return allele set + */ + private Set allelesOfGenotypes(Collection genotypes) { + Set alleles = new HashSet(); + + boolean addedref = false; + for ( Genotype g : genotypes ) { + for ( Allele a : g.getAlleles() ) { + addedref = addedref || a.isReference(); + if ( a.isCalled() ) + alleles.add(a); + } + } + if ( ! addedref ) alleles.add(getReference()); + + return alleles; + } + + // --------------------------------------------------------------------------------------------------------- + // + // type operations + // + // --------------------------------------------------------------------------------------------------------- + + /** + * see: http://www.ncbi.nlm.nih.gov/bookshelf/br.fcgi?book=handbook&part=ch5&rendertype=table&id=ch5.ch5_t3 + * + * Format: + * dbSNP variation class + * Rules for assigning allele classes + * Sample allele definition + * + * Single Nucleotide Polymorphisms (SNPs)a + * Strictly defined as single base substitutions involving A, T, C, or G. + * A/T + * + * Deletion/Insertion Polymorphisms (DIPs) + * Designated using the full sequence of the insertion as one allele, and either a fully + * defined string for the variant allele or a '-' character to specify the deleted allele. + * This class will be assigned to a variation if the variation alleles are of different lengths or + * if one of the alleles is deleted ('-'). + * T/-/CCTA/G + * + * No-variation + * Reports may be submitted for segments of sequence that are assayed and determined to be invariant + * in the sample. + * (NoVariation) + * + * Mixed + * Mix of other classes + * + * Also supports NO_VARIATION type, used to indicate that the site isn't polymorphic in the population + * + * + * Not currently supported: + * + * Heterozygous sequencea + * The term heterozygous is used to specify a region detected by certain methods that do not + * resolve the polymorphism into a specific sequence motif. In these cases, a unique flanking + * sequence must be provided to define a sequence context for the variation. + * (heterozygous) + * + * Microsatellite or short tandem repeat (STR) + * Alleles are designated by providing the repeat motif and the copy number for each allele. + * Expansion of the allele repeat motif designated in dbSNP into full-length sequence will + * be only an approximation of the true genomic sequence because many microsatellite markers are + * not fully sequenced and are resolved as size variants only. + * (CAC)8/9/10/11 + * + * Named variant + * Applies to insertion/deletion polymorphisms of longer sequence features, such as retroposon + * dimorphism for Alu or line elements. These variations frequently include a deletion '-' indicator + * for the absent allele. + * (alu) / - + * + * Multi-Nucleotide Polymorphism (MNP) + * Assigned to variations that are multi-base variations of a single, common length + * GGA/AGT + */ + public enum Type { + NO_VARIATION, + SNP, + MNP, // a multi-nucleotide polymorphism + INDEL, + SYMBOLIC, + MIXED, + } + + /** + * Determines (if necessary) and returns the type of this variation by examining the alleles it contains. + * + * @return the type of this VariantContext + **/ + public Type getType() { + if ( type == null ) + determineType(); + + return type; + } + + /** + * convenience method for SNPs + * + * @return true if this is a SNP, false otherwise + */ + public boolean isSNP() { return getType() == Type.SNP; } + + + /** + * convenience method for variants + * + * @return true if this is a variant allele, false if it's reference + */ + public boolean isVariant() { return getType() != Type.NO_VARIATION; } + + /** + * convenience method for point events + * + * @return true if this is a SNP or ref site, false if it's an indel or mixed event + */ + public boolean isPointEvent() { return isSNP() || !isVariant(); } + + /** + * convenience method for indels + * + * @return true if this is an indel, false otherwise + */ + public boolean isIndel() { return getType() == Type.INDEL; } + + /** + * @return true if the alleles indicate a simple insertion (i.e., the reference allele is Null) + */ + public boolean isSimpleInsertion() { + // can't just call !isSimpleDeletion() because of complex indels + return getType() == Type.INDEL && getReference().isNull() && isBiallelic(); + } + + /** + * @return true if the alleles indicate a simple deletion (i.e., a single alt allele that is Null) + */ + public boolean isSimpleDeletion() { + // can't just call !isSimpleInsertion() because of complex indels + return getType() == Type.INDEL && getAlternateAllele(0).isNull() && isBiallelic(); + } + + /** + * @return true if the alleles indicate neither a simple deletion nor a simple insertion + */ + public boolean isComplexIndel() { + return isIndel() && !isSimpleDeletion() && !isSimpleInsertion(); + } + + public boolean isSymbolic() { + return getType() == Type.SYMBOLIC; + } + + public boolean isMNP() { + return getType() == Type.MNP; + } + + /** + * convenience method for indels + * + * @return true if this is an mixed variation, false otherwise + */ + public boolean isMixed() { return getType() == Type.MIXED; } + + + // --------------------------------------------------------------------------------------------------------- + // + // Generic accessors + // + // --------------------------------------------------------------------------------------------------------- + + public boolean hasID() { + return commonInfo.hasAttribute(ID_KEY); + } + + public String getID() { + return (String)commonInfo.getAttribute(ID_KEY); + } + + public boolean hasReferenceBaseForIndel() { + return REFERENCE_BASE_FOR_INDEL != null; + } + + // the indel base that gets stripped off for indels + public Byte getReferenceBaseForIndel() { + return REFERENCE_BASE_FOR_INDEL; + } + + // --------------------------------------------------------------------------------------------------------- + // + // get routines to access context info fields + // + // --------------------------------------------------------------------------------------------------------- + public String getSource() { return commonInfo.getName(); } + public Set getFilters() { return commonInfo.getFilters(); } + public boolean isFiltered() { return commonInfo.isFiltered(); } + public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } + public boolean filtersWereApplied() { return filtersWereAppliedToContext; } + public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } + public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } + public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } + + public Map getAttributes() { return commonInfo.getAttributes(); } + public boolean hasAttribute(String key) { return commonInfo.hasAttribute(key); } + public Object getAttribute(String key) { return commonInfo.getAttribute(key); } + + public Object getAttribute(String key, Object defaultValue) { + return commonInfo.getAttribute(key, defaultValue); + } + + public String getAttributeAsString(String key, String defaultValue) { return commonInfo.getAttributeAsString(key, defaultValue); } + public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } + public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } + public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } + + // --------------------------------------------------------------------------------------------------------- + // + // Working with alleles + // + // --------------------------------------------------------------------------------------------------------- + + /** + * @return the reference allele for this context + */ + public Allele getReference() { + Allele ref = REF; + if ( ref == null ) + throw new IllegalStateException("BUG: no reference allele found at " + this); + return ref; + } + + + /** + * @return true if the context is strictly bi-allelic + */ + public boolean isBiallelic() { + return getNAlleles() == 2; + } + + /** + * @return The number of segregating alleles in this context + */ + public int getNAlleles() { + return alleles.size(); + } + + /** + * @return The allele sharing the same bases as this String. A convenience method; better to use byte[] + */ + public Allele getAllele(String allele) { + return getAllele(allele.getBytes()); + } + + /** + * @return The allele sharing the same bases as this byte[], or null if no such allele is present. + */ + public Allele getAllele(byte[] allele) { + return Allele.getMatchingAllele(getAlleles(), allele); + } + + /** + * @return True if this context contains Allele allele, or false otherwise + */ + public boolean hasAllele(Allele allele) { + return hasAllele(allele, false); + } + + public boolean hasAllele(Allele allele, boolean ignoreRefState) { + if ( allele == REF || allele == ALT ) // optimization for cached cases + return true; + + for ( Allele a : getAlleles() ) { + if ( a.equals(allele, ignoreRefState) ) + return true; + } + + return false; + } + + + /** + * Gets the alleles. This method should return all of the alleles present at the location, + * including the reference allele. There are no constraints imposed on the ordering of alleles + * in the set. If the reference is not an allele in this context it will not be included. + * + * @return the set of alleles + */ + public List getAlleles() { return alleles; } + + /** + * Gets the alternate alleles. This method should return all the alleles present at the location, + * NOT including the reference allele. There are no constraints imposed on the ordering of alleles + * in the set. + * + * @return the set of alternate alleles + */ + public List getAlternateAlleles() { + return alleles.subList(1, alleles.size()); + } + + /** + * Gets the sizes of the alternate alleles if they are insertion/deletion events, and returns a list of their sizes + * + * @return a list of indel lengths ( null if not of type indel or mixed ) + */ + public List getIndelLengths() { + if ( getType() != Type.INDEL && getType() != Type.MIXED ) { + return null; + } + + List lengths = new ArrayList(); + for ( Allele a : getAlternateAlleles() ) { + lengths.add(a.length() - getReference().length()); + } + + return lengths; + } + + /** + * @param i -- the ith allele (from 0 to n - 2 for a context with n alleles including a reference allele) + * @return the ith non-reference allele in this context + * @throws IllegalArgumentException if i is invalid + */ + public Allele getAlternateAllele(int i) { + return alleles.get(i+1); + } + + /** + * @param other VariantContext whose alternate alleles to compare against + * @return true if this VariantContext has the same alternate alleles as other, + * regardless of ordering. Otherwise returns false. + */ + public boolean hasSameAlternateAllelesAs ( VariantContext other ) { + List thisAlternateAlleles = getAlternateAlleles(); + List otherAlternateAlleles = other.getAlternateAlleles(); + + if ( thisAlternateAlleles.size() != otherAlternateAlleles.size() ) { + return false; + } + + for ( Allele allele : thisAlternateAlleles ) { + if ( ! otherAlternateAlleles.contains(allele) ) { + return false; + } + } + + return true; + } + + // --------------------------------------------------------------------------------------------------------- + // + // Working with genotypes + // + // --------------------------------------------------------------------------------------------------------- + + private void loadGenotypes() { + if ( !hasAttribute(UNPARSED_GENOTYPE_MAP_KEY) ) { + if ( genotypes == null ) + genotypes = NO_GENOTYPES; + return; + } + + Object parserObj = getAttribute(UNPARSED_GENOTYPE_PARSER_KEY); + if ( parserObj == null || !(parserObj instanceof VCFParser) ) + throw new IllegalStateException("There is no VCF parser stored to unparse the genotype data"); + VCFParser parser = (VCFParser)parserObj; + + Object mapObj = getAttribute(UNPARSED_GENOTYPE_MAP_KEY); + if ( mapObj == null ) + throw new IllegalStateException("There is no mapping string stored to unparse the genotype data"); + + genotypes = parser.createGenotypeMap(mapObj.toString(), new ArrayList(alleles), getChr(), getStart()); + + commonInfo.removeAttribute(UNPARSED_GENOTYPE_MAP_KEY); + commonInfo.removeAttribute(UNPARSED_GENOTYPE_PARSER_KEY); + + validateGenotypes(); + } + + /** + * @return the number of samples in the context + */ + public int getNSamples() { + loadGenotypes(); + return genotypes.size(); + } + + /** + * @return true if the context has associated genotypes + */ + public boolean hasGenotypes() { + loadGenotypes(); + return genotypes.size() > 0; + } + + public boolean hasGenotypes(Collection sampleNames) { + loadGenotypes(); + for ( String name : sampleNames ) { + if ( ! genotypes.containsKey(name) ) + return false; + } + return true; + } + + /** + * @return set of all Genotypes associated with this context + */ + public Map getGenotypes() { + loadGenotypes(); + return genotypes; + } + + public List getGenotypesSortedByName() { + loadGenotypes(); + Collection types = new TreeMap(genotypes).values(); + return new ArrayList(types); + } + + /** + * Returns a map from sampleName -> Genotype for the genotype associated with sampleName. Returns a map + * for consistency with the multi-get function. + * + * @param sampleName + * @return + * @throws IllegalArgumentException if sampleName isn't bound to a genotype + */ + public Map getGenotypes(String sampleName) { + return getGenotypes(Arrays.asList(sampleName)); + } + + /** + * Returns a map from sampleName -> Genotype for each sampleName in sampleNames. Returns a map + * for consistency with the multi-get function. + * + * @param sampleNames a unique list of sample names + * @return + * @throws IllegalArgumentException if sampleName isn't bound to a genotype + */ + public Map getGenotypes(Collection sampleNames) { + HashMap map = new HashMap(); + + for ( String name : sampleNames ) { + if ( map.containsKey(name) ) throw new IllegalArgumentException("Duplicate names detected in requested samples " + sampleNames); + final Genotype g = getGenotype(name); + if ( g != null ) { + map.put(name, g); + } + } + + return map; + } + + /** + * @return the set of all sample names in this context + */ + public Set getSampleNames() { + return getGenotypes().keySet(); + } + + /** + * @param sample the sample name + * + * @return the Genotype associated with the given sample in this context or null if the sample is not in this context + */ + public Genotype getGenotype(String sample) { + return getGenotypes().get(sample); + } + + public boolean hasGenotype(String sample) { + return getGenotypes().containsKey(sample); + } + + public Genotype getGenotype(int ith) { + return getGenotypesSortedByName().get(ith); + } + + + /** + * Returns the number of chromosomes carrying any allele in the genotypes (i.e., excluding NO_CALLS + * + * @return chromosome count + */ + public int getChromosomeCount() { + int n = 0; + + for ( Genotype g : getGenotypes().values() ) { + n += g.isNoCall() ? 0 : g.getPloidy(); + } + + return n; + } + + /** + * Returns the number of chromosomes carrying allele A in the genotypes + * + * @param a allele + * @return chromosome count + */ + public int getChromosomeCount(Allele a) { + int n = 0; + + for ( Genotype g : getGenotypes().values() ) { + n += g.getAlleles(a).size(); + } + + return n; + } + + /** + * Genotype-specific functions -- are the genotypes monomorphic w.r.t. to the alleles segregating at this + * site? That is, is the number of alternate alleles among all fo the genotype == 0? + * + * @return true if it's monomorphic + */ + public boolean isMonomorphic() { + return ! isVariant() || (hasGenotypes() && getHomRefCount() + getNoCallCount() == getNSamples()); + } + + /** + * Genotype-specific functions -- are the genotypes polymorphic w.r.t. to the alleles segregating at this + * site? That is, is the number of alternate alleles among all fo the genotype > 0? + * + * @return true if it's polymorphic + */ + public boolean isPolymorphic() { + return ! isMonomorphic(); + } + + private void calculateGenotypeCounts() { + if ( genotypeCounts == null ) { + genotypeCounts = new int[Genotype.Type.values().length]; + + for ( Genotype g : getGenotypes().values() ) { + if ( g.isNoCall() ) + genotypeCounts[Genotype.Type.NO_CALL.ordinal()]++; + else if ( g.isHomRef() ) + genotypeCounts[Genotype.Type.HOM_REF.ordinal()]++; + else if ( g.isHet() ) + genotypeCounts[Genotype.Type.HET.ordinal()]++; + else if ( g.isHomVar() ) + genotypeCounts[Genotype.Type.HOM_VAR.ordinal()]++; + else + genotypeCounts[Genotype.Type.MIXED.ordinal()]++; + } + } + } + + /** + * Genotype-specific functions -- how many no-calls are there in the genotypes? + * + * @return number of no calls + */ + public int getNoCallCount() { + calculateGenotypeCounts(); + return genotypeCounts[Genotype.Type.NO_CALL.ordinal()]; + } + + /** + * Genotype-specific functions -- how many hom ref calls are there in the genotypes? + * + * @return number of hom ref calls + */ + public int getHomRefCount() { + calculateGenotypeCounts(); + return genotypeCounts[Genotype.Type.HOM_REF.ordinal()]; + } + + /** + * Genotype-specific functions -- how many het calls are there in the genotypes? + * + * @return number of het calls + */ + public int getHetCount() { + calculateGenotypeCounts(); + return genotypeCounts[Genotype.Type.HET.ordinal()]; + } + + /** + * Genotype-specific functions -- how many hom var calls are there in the genotypes? + * + * @return number of hom var calls + */ + public int getHomVarCount() { + return genotypeCounts[Genotype.Type.HOM_VAR.ordinal()]; + } + + /** + * Genotype-specific functions -- how many mixed calls are there in the genotypes? + * + * @return number of mixed calls + */ + public int getMixedCount() { + return genotypeCounts[Genotype.Type.MIXED.ordinal()]; + } + + // --------------------------------------------------------------------------------------------------------- + // + // validation: extra-strict validation routines for paranoid users + // + // --------------------------------------------------------------------------------------------------------- + + /** + * Run all extra-strict validation tests on a Variant Context object + * + * @param reference the true reference allele + * @param paddedRefBase the reference base used for padding indels + * @param rsIDs the true dbSNP IDs + */ + public void extraStrictValidation(Allele reference, Byte paddedRefBase, Set rsIDs) { + // validate the reference + validateReferenceBases(reference, paddedRefBase); + + // validate the RS IDs + validateRSIDs(rsIDs); + + // validate the altenate alleles + validateAlternateAlleles(); + + // validate the AN and AC fields + validateChromosomeCounts(); + + // TODO: implement me + //checkReferenceTrack(); + } + + public void validateReferenceBases(Allele reference, Byte paddedRefBase) { + // don't validate if we're a complex event + if ( !isComplexIndel() && !reference.isNull() && !reference.basesMatch(getReference()) ) { + throw new TribbleException.InternalCodecException(String.format("the REF allele is incorrect for the record at position %s:%d, fasta says %s vs. VCF says %s", getChr(), getStart(), reference.getBaseString(), getReference().getBaseString())); + } + + // we also need to validate the padding base for simple indels + if ( hasReferenceBaseForIndel() && !getReferenceBaseForIndel().equals(paddedRefBase) ) { + throw new TribbleException.InternalCodecException(String.format("the padded REF base is incorrect for the record at position %s:%d, fasta says %s vs. VCF says %s", getChr(), getStart(), (char)paddedRefBase.byteValue(), (char)getReferenceBaseForIndel().byteValue())); + } + } + + public void validateRSIDs(Set rsIDs) { + if ( rsIDs != null && hasAttribute(VariantContext.ID_KEY) ) { + for ( String id : getID().split(VCFConstants.ID_FIELD_SEPARATOR) ) { + if ( id.startsWith("rs") && !rsIDs.contains(id) ) + throw new TribbleException.InternalCodecException(String.format("the rsID %s for the record at position %s:%d is not in dbSNP", id, getChr(), getStart())); + } + } + } + + public void validateAlternateAlleles() { + if ( !hasGenotypes() ) + return; + + List reportedAlleles = getAlleles(); + Set observedAlleles = new HashSet(); + observedAlleles.add(getReference()); + for ( Genotype g : getGenotypes().values() ) { + if ( g.isCalled() ) + observedAlleles.addAll(g.getAlleles()); + } + + if ( reportedAlleles.size() != observedAlleles.size() ) + throw new TribbleException.InternalCodecException(String.format("the ALT allele(s) for the record at position %s:%d do not match what is observed in the per-sample genotypes", getChr(), getStart())); + + int originalSize = reportedAlleles.size(); + // take the intersection and see if things change + observedAlleles.retainAll(reportedAlleles); + if ( observedAlleles.size() != originalSize ) + throw new TribbleException.InternalCodecException(String.format("the ALT allele(s) for the record at position %s:%d do not match what is observed in the per-sample genotypes", getChr(), getStart())); + } + + public void validateChromosomeCounts() { + Map observedAttrs = calculateChromosomeCounts(); + + // AN + if ( hasAttribute(VCFConstants.ALLELE_NUMBER_KEY) ) { + int reportedAN = Integer.valueOf(getAttribute(VCFConstants.ALLELE_NUMBER_KEY).toString()); + int observedAN = (Integer)observedAttrs.get(VCFConstants.ALLELE_NUMBER_KEY); + if ( reportedAN != observedAN ) + throw new TribbleException.InternalCodecException(String.format("the Allele Number (AN) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedAN, observedAN)); + } + + // AC + if ( hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) { + List observedACs = (List)observedAttrs.get(VCFConstants.ALLELE_COUNT_KEY); + if ( getAttribute(VCFConstants.ALLELE_COUNT_KEY) instanceof List ) { + Collections.sort(observedACs); + List reportedACs = (List)getAttribute(VCFConstants.ALLELE_COUNT_KEY); + Collections.sort(reportedACs); + if ( observedACs.size() != reportedACs.size() ) + throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag doesn't have the correct number of values for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedACs.size(), observedACs.size())); + for (int i = 0; i < observedACs.size(); i++) { + if ( Integer.valueOf(reportedACs.get(i).toString()) != observedACs.get(i) ) + throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedACs.get(i), observedACs.get(i))); + } + } else { + if ( observedACs.size() != 1 ) + throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag doesn't have enough values for the record at position %s:%d", getChr(), getStart())); + int reportedAC = Integer.valueOf(getAttribute(VCFConstants.ALLELE_COUNT_KEY).toString()); + if ( reportedAC != observedACs.get(0) ) + throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedAC, observedACs.get(0))); + } + } + } + + private Map calculateChromosomeCounts() { + Map attributes = new HashMap(); + + attributes.put(VCFConstants.ALLELE_NUMBER_KEY, getChromosomeCount()); + ArrayList alleleFreqs = new ArrayList(); + ArrayList alleleCounts = new ArrayList(); + + // if there are alternate alleles, record the relevant tags + if ( getAlternateAlleles().size() > 0 ) { + for ( Allele allele : getAlternateAlleles() ) { + alleleCounts.add(getChromosomeCount(allele)); + alleleFreqs.add((double)getChromosomeCount(allele) / (double)getChromosomeCount()); + } + } + // otherwise, set them to 0 + else { + alleleCounts.add(0); + alleleFreqs.add(0.0); + } + + attributes.put(VCFConstants.ALLELE_COUNT_KEY, alleleCounts); + attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, alleleFreqs); + return attributes; + } + + // --------------------------------------------------------------------------------------------------------- + // + // validation: the normal validation routines are called automatically upon creation of the VC + // + // --------------------------------------------------------------------------------------------------------- + + /** + * To be called by any modifying routines + */ + private boolean validate() { + return validate(true); + } + + private boolean validate(boolean throwException) { + try { + validateReferencePadding(); + validateAlleles(); + validateGenotypes(); + } catch ( IllegalArgumentException e ) { + if ( throwException ) + throw e; + else + return false; + } + + return true; + } + + private void validateReferencePadding() { + if (hasSymbolicAlleles()) // symbolic alleles don't need padding... + return; + + boolean needsPadding = (getReference().length() == getEnd() - getStart()); // off by one because padded base was removed + + if ( needsPadding && !hasReferenceBaseForIndel() ) + throw new ReviewedStingException("Badly formed variant context at location " + getChr() + ":" + getStart() + "; no padded reference base was provided."); + } + + private void validateAlleles() { + // check alleles + boolean alreadySeenRef = false, alreadySeenNull = false; + for ( Allele allele : alleles ) { + // make sure there's only one reference allele + if ( allele.isReference() ) { + if ( alreadySeenRef ) throw new IllegalArgumentException("BUG: Received two reference tagged alleles in VariantContext " + alleles + " this=" + this); + alreadySeenRef = true; + } + + if ( allele.isNoCall() ) { + throw new IllegalArgumentException("BUG: Cannot add a no call allele to a variant context " + alleles + " this=" + this); + } + + // make sure there's only one null allele + if ( allele.isNull() ) { + if ( alreadySeenNull ) throw new IllegalArgumentException("BUG: Received two null alleles in VariantContext " + alleles + " this=" + this); + alreadySeenNull = true; + } + } + + // make sure there's one reference allele + if ( ! alreadySeenRef ) + throw new IllegalArgumentException("No reference allele found in VariantContext"); + +// if ( getType() == Type.INDEL ) { +// if ( getReference().length() != (getLocation().size()-1) ) { + long length = (stop - start) + 1; + if ( (getReference().isNull() && length != 1 ) || + (getReference().isNonNull() && (length - getReference().length() > 1))) { + throw new IllegalStateException("BUG: GenomeLoc " + contig + ":" + start + "-" + stop + " has a size == " + length + " but the variation reference allele has length " + getReference().length() + " this = " + this); + } + } + + private void validateGenotypes() { + if ( this.genotypes == null ) throw new IllegalStateException("Genotypes is null"); + + for ( Map.Entry elt : this.genotypes.entrySet() ) { + String name = elt.getKey(); + Genotype g = elt.getValue(); + + if ( ! name.equals(g.getSampleName()) ) throw new IllegalStateException("Bound sample name " + name + " does not equal the name of the genotype " + g.getSampleName()); + + if ( g.isAvailable() ) { + for ( Allele gAllele : g.getAlleles() ) { + if ( ! hasAllele(gAllele) && gAllele.isCalled() ) + throw new IllegalStateException("Allele in genotype " + gAllele + " not in the variant context " + alleles); + } + } + } + } + + // --------------------------------------------------------------------------------------------------------- + // + // utility routines + // + // --------------------------------------------------------------------------------------------------------- + + private void determineType() { + if ( type == null ) { + switch ( getNAlleles() ) { + case 0: + throw new IllegalStateException("Unexpected error: requested type of VariantContext with no alleles!" + this); + case 1: + // note that this doesn't require a reference allele. You can be monomorphic independent of having a + // reference allele + type = Type.NO_VARIATION; + break; + default: + determinePolymorphicType(); + } + } + } + + private void determinePolymorphicType() { + type = null; + + // do a pairwise comparison of all alleles against the reference allele + for ( Allele allele : alleles ) { + if ( allele == REF ) + continue; + + // find the type of this allele relative to the reference + Type biallelicType = typeOfBiallelicVariant(REF, allele); + + // for the first alternate allele, set the type to be that one + if ( type == null ) { + type = biallelicType; + } + // if the type of this allele is different from that of a previous one, assign it the MIXED type and quit + else if ( biallelicType != type ) { + type = Type.MIXED; + return; + } + } + } + + private static Type typeOfBiallelicVariant(Allele ref, Allele allele) { + if ( ref.isSymbolic() ) + throw new IllegalStateException("Unexpected error: encountered a record with a symbolic reference allele"); + + if ( allele.isSymbolic() ) + return Type.SYMBOLIC; + + if ( ref.length() == allele.length() ) { + if ( allele.length() == 1 ) + return Type.SNP; + else + return Type.MNP; + } + + // Important note: previously we were checking that one allele is the prefix of the other. However, that's not an + // appropriate check as can be seen from the following example: + // REF = CTTA and ALT = C,CT,CA + // This should be assigned the INDEL type but was being marked as a MIXED type because of the prefix check. + // In truth, it should be absolutely impossible to return a MIXED type from this method because it simply + // performs a pairwise comparison of a single alternate allele against the reference allele (whereas the MIXED type + // is reserved for cases of multiple alternate alleles of different types). Therefore, if we've reached this point + // in the code (so we're not a SNP, MNP, or symbolic allele), we absolutely must be an INDEL. + return Type.INDEL; + + // old incorrect logic: + // if (oneIsPrefixOfOther(ref, allele)) + // return Type.INDEL; + // else + // return Type.MIXED; + } + + public String toString() { + return String.format("[VC %s @ %s of type=%s alleles=%s attr=%s GT=%s", + getSource(), contig + ":" + (start - stop == 0 ? start : start + "-" + stop), this.getType(), + ParsingUtils.sortList(this.getAlleles()), ParsingUtils.sortedString(this.getAttributes()), this.getGenotypesSortedByName()); + } + + // protected basic manipulation routines + private static List makeAlleles(Collection alleles) { + final List alleleList = new ArrayList(alleles.size()); + + boolean sawRef = false; + for ( final Allele a : alleles ) { + for ( final Allele b : alleleList ) { + if ( a.equals(b, true) ) + throw new IllegalArgumentException("Duplicate allele added to VariantContext: " + a); + } + + // deal with the case where the first allele isn't the reference + if ( a.isReference() ) { + if ( sawRef ) + throw new IllegalArgumentException("Alleles for a VariantContext must contain at most one reference allele: " + alleles); + alleleList.add(0, a); + sawRef = true; + } + else + alleleList.add(a); + } + + if ( alleleList.isEmpty() ) + throw new IllegalArgumentException("Cannot create a VariantContext with an empty allele list"); + + if ( alleleList.get(0).isNonReference() ) + throw new IllegalArgumentException("Alleles for a VariantContext must contain at least one reference allele: " + alleles); + + return alleleList; + } + + public static Map genotypeCollectionToMap(Map dest, Collection genotypes) { + for ( Genotype g : genotypes ) { + if ( dest.containsKey(g.getSampleName() ) ) + throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); + dest.put(g.getSampleName(), g); + } + + return dest; + } + + // --------------------------------------------------------------------------------------------------------- + // + // tribble integration routines -- not for public consumption + // + // --------------------------------------------------------------------------------------------------------- + public String getChr() { + return contig; + } + + public int getStart() { + return (int)start; + } + + public int getEnd() { + return (int)stop; + } + + private boolean hasSymbolicAlleles() { + for (Allele a: getAlleles()) { + if (a.isSymbolic()) { + return true; + } + } + return false; + } + + public static VariantContext createVariantContextWithPaddedAlleles(VariantContext inputVC, boolean refBaseShouldBeAppliedToEndOfAlleles) { + + // see if we need to pad common reference base from all alleles + boolean padVC; + + // We need to pad a VC with a common base if the length of the reference allele is less than the length of the VariantContext. + // This happens because the position of e.g. an indel is always one before the actual event (as per VCF convention). + long locLength = (inputVC.getEnd() - inputVC.getStart()) + 1; + if (inputVC.hasSymbolicAlleles()) + padVC = true; + else if (inputVC.getReference().length() == locLength) + padVC = false; + else if (inputVC.getReference().length() == locLength-1) + padVC = true; + else throw new IllegalArgumentException("Badly formed variant context at location " + String.valueOf(inputVC.getStart()) + + " in contig " + inputVC.getChr() + ". Reference length must be at most one base shorter than location size"); + + // nothing to do if we don't need to pad bases + if (padVC) { + + if ( !inputVC.hasReferenceBaseForIndel() ) + throw new ReviewedStingException("Badly formed variant context at location " + inputVC.getChr() + ":" + inputVC.getStart() + "; no padded reference base is available."); + + Byte refByte = inputVC.getReferenceBaseForIndel(); + + List alleles = new ArrayList(); + Map genotypes = new TreeMap(); + + Map inputGenotypes = inputVC.getGenotypes(); + + for (Allele a : inputVC.getAlleles()) { + // get bases for current allele and create a new one with trimmed bases + if (a.isSymbolic()) { + alleles.add(a); + } else { + String newBases; + if ( refBaseShouldBeAppliedToEndOfAlleles ) + newBases = a.getBaseString() + new String(new byte[]{refByte}); + else + newBases = new String(new byte[]{refByte}) + a.getBaseString(); + alleles.add(Allele.create(newBases,a.isReference())); + } + } + + // now we can recreate new genotypes with trimmed alleles + for (String sample : inputVC.getSampleNames()) { + Genotype g = inputGenotypes.get(sample); + + List inAlleles = g.getAlleles(); + List newGenotypeAlleles = new ArrayList(); + for (Allele a : inAlleles) { + if (a.isCalled()) { + if (a.isSymbolic()) { + newGenotypeAlleles.add(a); + } else { + String newBases; + if ( refBaseShouldBeAppliedToEndOfAlleles ) + newBases = a.getBaseString() + new String(new byte[]{refByte}); + else + newBases = new String(new byte[]{refByte}) + a.getBaseString(); + newGenotypeAlleles.add(Allele.create(newBases,a.isReference())); + } + } + else { + // add no-call allele + newGenotypeAlleles.add(Allele.NO_CALL); + } + } + genotypes.put(sample, new Genotype(sample, newGenotypeAlleles, g.getNegLog10PError(), + g.getFilters(),g.getAttributes(),g.isPhased())); + + } + + // Do not change the filter state if filters were not applied to this context + Set inputVCFilters = inputVC.filtersWereAppliedToContext ? inputVC.getFilters() : null; + return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); + } + else + return inputVC; + + } + + public ArrayList getTwoAllelesWithHighestAlleleCounts() { + // first idea: get two alleles with highest AC + int maxAC1 = 0, maxAC2=0,maxAC1ind =0, maxAC2ind = 0; + int i=0; + int[] alleleCounts = new int[this.getAlleles().size()]; + ArrayList alleleArray = new ArrayList(); + for (Allele a:this.getAlleles()) { + int ac = this.getChromosomeCount(a); + if (ac >=maxAC1) { + maxAC1 = ac; + maxAC1ind = i; + } + alleleArray.add(a); + alleleCounts[i++] = ac; + } + // now get second best allele + for (i=0; i < alleleCounts.length; i++) { + if (i == maxAC1ind) + continue; + if (alleleCounts[i] >= maxAC2) { + maxAC2 = alleleCounts[i]; + maxAC2ind = i; + } + } + + Allele alleleA, alleleB; + if (alleleArray.get(maxAC1ind).isReference()) { + alleleA = alleleArray.get(maxAC1ind); + alleleB = alleleArray.get(maxAC2ind); + } + else if (alleleArray.get(maxAC2ind).isReference()) { + alleleA = alleleArray.get(maxAC2ind); + alleleB = alleleArray.get(maxAC1ind); + } else { + alleleA = alleleArray.get(maxAC1ind); + alleleB = alleleArray.get(maxAC2ind); + } + ArrayList a = new ArrayList(); + a.add(alleleA); + a.add(alleleB); + return a; + } + public Allele getAltAlleleWithHighestAlleleCount() { + // first idea: get two alleles with highest AC + Allele best = null; + int maxAC1 = 0; + for (Allele a:this.getAlternateAlleles()) { + int ac = this.getChromosomeCount(a); + if (ac >=maxAC1) { + maxAC1 = ac; + best = a; + } + + } + return best; + } + + public int[] getGLIndecesOfAllele(Allele inputAllele) { + int[] idxVector = new int[3]; + int numAlleles = this.getAlleles().size(); + + int idxDiag = numAlleles; + int incr = numAlleles - 1; + int k=1; + for (Allele a: getAlternateAlleles()) { + // multi-allelic approximation, part 1: Ideally + // for each alt allele compute marginal (suboptimal) posteriors - + // compute indices for AA,AB,BB for current allele - genotype likelihoods are a linear vector that can be thought of + // as a row-wise upper triangular matrix of likelihoods. + // So, for example, with 2 alt alleles, likelihoods have AA,AB,AC,BB,BC,CC. + // 3 alt alleles: AA,AB,AC,AD BB BC BD CC CD DD + + int idxAA = 0; + int idxAB = k++; + // yy is always element on the diagonal. + // 2 alleles: BBelement 2 + // 3 alleles: BB element 3. CC element 5 + // 4 alleles: + int idxBB = idxDiag; + + if (a.equals(inputAllele)) { + idxVector[0] = idxAA; + idxVector[1] = idxAB; + idxVector[2] = idxBB; + break; + } + idxDiag += incr--; + } + return idxVector; + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java new file mode 100755 index 000000000..e2cf2ecf0 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java @@ -0,0 +1,1407 @@ +/* + * Copyright (c) 2010. The Broad Institute + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import com.google.java.contract.Ensures; +import com.google.java.contract.Requires; +import net.sf.picard.reference.ReferenceSequenceFile; +import net.sf.samtools.util.StringUtil; +import org.apache.commons.jexl2.Expression; +import org.apache.commons.jexl2.JexlEngine; +import org.apache.log4j.Logger; +import org.broad.tribble.util.popgen.HardyWeinbergCalculation; +import org.broadinstitute.sting.gatk.walkers.phasing.ReadBackedPhasingWalker; +import org.broadinstitute.sting.utils.BaseUtils; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; + +import java.io.Serializable; +import java.util.*; + +public class VariantContextUtils { + private static Logger logger = Logger.getLogger(VariantContextUtils.class); + public final static String MERGE_INTERSECTION = "Intersection"; + public final static String MERGE_FILTER_IN_ALL = "FilteredInAll"; + public final static String MERGE_REF_IN_ALL = "ReferenceInAll"; + public final static String MERGE_FILTER_PREFIX = "filterIn"; + + final public static JexlEngine engine = new JexlEngine(); + static { + engine.setSilent(false); // will throw errors now for selects that don't evaluate properly + engine.setLenient(false); + } + + /** + * Create a new VariantContext + * + * @param name name + * @param loc location + * @param alleles alleles + * @param genotypes genotypes set + * @param negLog10PError qual + * @param filters filters: use null for unfiltered and empty set for passes filters + * @param attributes attributes + * @return VariantContext object + */ + public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { + return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes != null ? VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes); + } + + /** + * Create a new variant context without genotypes and no Perror, no filters, and no attributes + * @param name name + * @param loc location + * @param alleles alleles + * @return VariantContext object + */ + public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles) { + return new VariantContext (name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, VariantContext.NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + } + + /** + * Create a new variant context without genotypes and no Perror, no filters, and no attributes + * @param name name + * @param loc location + * @param alleles alleles + * @param genotypes genotypes + * @return VariantContext object + */ + public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes) { + return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); + } + + /** + * Copy constructor + * + * @param other the VariantContext to copy + * @return VariantContext object + */ + public static VariantContext toVC(VariantContext other) { + return new VariantContext(other.getSource(), other.getChr(), other.getStart(), other.getEnd(), other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.getFilters(), other.getAttributes()); + } + + /** + * Update the attributes of the attributes map given the VariantContext to reflect the proper chromosome-based VCF tags + * + * @param vc the VariantContext + * @param attributes the attributes map to populate; must not be null; may contain old values + * @param removeStaleValues should we remove stale values from the mapping? + */ + public static void calculateChromosomeCounts(VariantContext vc, Map attributes, boolean removeStaleValues) { + // if everyone is a no-call, remove the old attributes if requested + if ( vc.getChromosomeCount() == 0 && removeStaleValues ) { + if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) + attributes.remove(VCFConstants.ALLELE_COUNT_KEY); + if ( attributes.containsKey(VCFConstants.ALLELE_FREQUENCY_KEY) ) + attributes.remove(VCFConstants.ALLELE_FREQUENCY_KEY); + if ( attributes.containsKey(VCFConstants.ALLELE_NUMBER_KEY) ) + attributes.remove(VCFConstants.ALLELE_NUMBER_KEY); + return; + } + + if ( vc.hasGenotypes() ) { + attributes.put(VCFConstants.ALLELE_NUMBER_KEY, vc.getChromosomeCount()); + + // if there are alternate alleles, record the relevant tags + if ( vc.getAlternateAlleles().size() > 0 ) { + ArrayList alleleFreqs = new ArrayList(); + ArrayList alleleCounts = new ArrayList(); + double totalChromosomes = (double)vc.getChromosomeCount(); + for ( Allele allele : vc.getAlternateAlleles() ) { + int altChromosomes = vc.getChromosomeCount(allele); + alleleCounts.add(altChromosomes); + String freq = String.format(makePrecisionFormatStringFromDenominatorValue(totalChromosomes), ((double)altChromosomes / totalChromosomes)); + alleleFreqs.add(freq); + } + + attributes.put(VCFConstants.ALLELE_COUNT_KEY, alleleCounts.size() == 1 ? alleleCounts.get(0) : alleleCounts); + attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, alleleFreqs.size() == 1 ? alleleFreqs.get(0) : alleleFreqs); + } + else { + attributes.put(VCFConstants.ALLELE_COUNT_KEY, 0); + attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, 0.0); + } + } + } + + private static String makePrecisionFormatStringFromDenominatorValue(double maxValue) { + int precision = 1; + + while ( maxValue > 1 ) { + precision++; + maxValue /= 10.0; + } + + return "%." + precision + "f"; + } + + public static Genotype removePLs(Genotype g) { + Map attrs = new HashMap(g.getAttributes()); + attrs.remove(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY); + attrs.remove(VCFConstants.GENOTYPE_LIKELIHOODS_KEY); + return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attrs, g.isPhased()); + } + + /** + * A simple but common wrapper for matching VariantContext objects using JEXL expressions + */ + public static class JexlVCMatchExp { + public String name; + public Expression exp; + + /** + * Create a new matcher expression with name and JEXL expression exp + * @param name name + * @param exp expression + */ + public JexlVCMatchExp(String name, Expression exp) { + this.name = name; + this.exp = exp; + } + } + + /** + * Method for creating JexlVCMatchExp from input walker arguments names and exps. These two arrays contain + * the name associated with each JEXL expression. initializeMatchExps will parse each expression and return + * a list of JexlVCMatchExp, in order, that correspond to the names and exps. These are suitable input to + * match() below. + * + * @param names names + * @param exps expressions + * @return list of matches + */ + public static List initializeMatchExps(String[] names, String[] exps) { + if ( names == null || exps == null ) + throw new ReviewedStingException("BUG: neither names nor exps can be null: names " + Arrays.toString(names) + " exps=" + Arrays.toString(exps) ); + + if ( names.length != exps.length ) + throw new UserException("Inconsistent number of provided filter names and expressions: names=" + Arrays.toString(names) + " exps=" + Arrays.toString(exps)); + + Map map = new HashMap(); + for ( int i = 0; i < names.length; i++ ) { map.put(names[i], exps[i]); } + + return VariantContextUtils.initializeMatchExps(map); + } + + public static List initializeMatchExps(ArrayList names, ArrayList exps) { + String[] nameArray = new String[names.size()]; + String[] expArray = new String[exps.size()]; + return initializeMatchExps(names.toArray(nameArray), exps.toArray(expArray)); + } + + + /** + * Method for creating JexlVCMatchExp from input walker arguments mapping from names to exps. These two arrays contain + * the name associated with each JEXL expression. initializeMatchExps will parse each expression and return + * a list of JexlVCMatchExp, in order, that correspond to the names and exps. These are suitable input to + * match() below. + * + * @param names_and_exps mapping of names to expressions + * @return list of matches + */ + public static List initializeMatchExps(Map names_and_exps) { + List exps = new ArrayList(); + + for ( Map.Entry elt : names_and_exps.entrySet() ) { + String name = elt.getKey(); + String expStr = elt.getValue(); + + if ( name == null || expStr == null ) throw new IllegalArgumentException("Cannot create null expressions : " + name + " " + expStr); + try { + Expression exp = engine.createExpression(expStr); + exps.add(new JexlVCMatchExp(name, exp)); + } catch (Exception e) { + throw new UserException.BadArgumentValue(name, "Invalid expression used (" + expStr + "). Please see the JEXL docs for correct syntax.") ; + } + } + + return exps; + } + + /** + * Returns true if exp match VC. See collection<> version for full docs. + * @param vc variant context + * @param exp expression + * @return true if there is a match + */ + public static boolean match(VariantContext vc, JexlVCMatchExp exp) { + return match(vc,Arrays.asList(exp)).get(exp); + } + + /** + * Matches each JexlVCMatchExp exp against the data contained in vc, and returns a map from these + * expressions to true (if they matched) or false (if they didn't). This the best way to apply JEXL + * expressions to VariantContext records. Use initializeMatchExps() to create the list of JexlVCMatchExp + * expressions. + * + * @param vc variant context + * @param exps expressions + * @return true if there is a match + */ + public static Map match(VariantContext vc, Collection exps) { + return new JEXLMap(exps,vc); + + } + + /** + * Returns true if exp match VC/g. See collection<> version for full docs. + * @param vc variant context + * @param g genotype + * @param exp expression + * @return true if there is a match + */ + public static boolean match(VariantContext vc, Genotype g, JexlVCMatchExp exp) { + return match(vc,g,Arrays.asList(exp)).get(exp); + } + + /** + * Matches each JexlVCMatchExp exp against the data contained in vc/g, and returns a map from these + * expressions to true (if they matched) or false (if they didn't). This the best way to apply JEXL + * expressions to VariantContext records/genotypes. Use initializeMatchExps() to create the list of JexlVCMatchExp + * expressions. + * + * @param vc variant context + * @param g genotype + * @param exps expressions + * @return true if there is a match + */ + public static Map match(VariantContext vc, Genotype g, Collection exps) { + return new JEXLMap(exps,vc,g); + } + + public static double computeHardyWeinbergPvalue(VariantContext vc) { + if ( vc.getChromosomeCount() == 0 ) + return 0.0; + return HardyWeinbergCalculation.hwCalculate(vc.getHomRefCount(), vc.getHetCount(), vc.getHomVarCount()); + } + + /** + * Returns a newly allocated VC that is the same as VC, but without genotypes + * @param vc variant context + * @return new VC without genotypes + */ + @Requires("vc != null") + @Ensures("result != null") + public static VariantContext sitesOnlyVariantContext(VariantContext vc) { + return VariantContext.modifyGenotypes(vc, null); + } + + /** + * Returns a newly allocated list of VC, where each VC is the same as the input VCs, but without genotypes + * @param vcs collection of VCs + * @return new VCs without genotypes + */ + @Requires("vcs != null") + @Ensures("result != null") + public static Collection sitesOnlyVariantContexts(Collection vcs) { + List r = new ArrayList(); + for ( VariantContext vc : vcs ) + r.add(sitesOnlyVariantContext(vc)); + return r; + } + + public static VariantContext pruneVariantContext(VariantContext vc) { + return pruneVariantContext(vc, null); + } + + public static VariantContext pruneVariantContext(final VariantContext vc, final Collection keysToPreserve ) { + final MutableVariantContext mvc = new MutableVariantContext(vc); + + if ( keysToPreserve == null || keysToPreserve.size() == 0 ) + mvc.clearAttributes(); + else { + final Map d = mvc.getAttributes(); + mvc.clearAttributes(); + for ( String key : keysToPreserve ) + if ( d.containsKey(key) ) + mvc.putAttribute(key, d.get(key)); + } + + // this must be done as the ID is stored in the attributes field + if ( vc.hasID() ) mvc.setID(vc.getID()); + + Collection gs = mvc.getGenotypes().values(); + mvc.clearGenotypes(); + for ( Genotype g : gs ) { + MutableGenotype mg = new MutableGenotype(g); + mg.clearAttributes(); + if ( keysToPreserve != null ) + for ( String key : keysToPreserve ) + if ( g.hasAttribute(key) ) + mg.putAttribute(key, g.getAttribute(key)); + mvc.addGenotype(mg); + } + + return mvc; + } + + public enum GenotypeMergeType { + /** + * Make all sample genotypes unique by file. Each sample shared across RODs gets named sample.ROD. + */ + UNIQUIFY, + /** + * Take genotypes in priority order (see the priority argument). + */ + PRIORITIZE, + /** + * Take the genotypes in any order. + */ + UNSORTED, + /** + * Require that all samples/genotypes be unique between all inputs. + */ + REQUIRE_UNIQUE + } + + public enum FilteredRecordMergeType { + /** + * Union - leaves the record if any record is unfiltered. + */ + KEEP_IF_ANY_UNFILTERED, + /** + * Requires all records present at site to be unfiltered. VCF files that don't contain the record don't influence this. + */ + KEEP_IF_ALL_UNFILTERED + } + + /** + * Performs a master merge on the VCs. Here there is a master input [contains all of the information] and many + * VCs containing partial, extra genotype information which should be added to the master. For example, + * we scatter out the phasing algorithm over some samples in the master, producing a minimal VCF with phasing + * information per genotype. The master merge will add the PQ information from each genotype record, where + * appropriate, to the master VC. + * + * @param unsortedVCs collection of VCs + * @param masterName name of master VC + * @return master-merged VC + */ + public static VariantContext masterMerge(Collection unsortedVCs, String masterName) { + VariantContext master = findMaster(unsortedVCs, masterName); + Map genotypes = master.getGenotypes(); + for (Genotype g : genotypes.values()) { + genotypes.put(g.getSampleName(), new MutableGenotype(g)); + } + + Map masterAttributes = new HashMap(master.getAttributes()); + + for (VariantContext vc : unsortedVCs) { + if (!vc.getSource().equals(masterName)) { + for (Genotype g : vc.getGenotypes().values()) { + MutableGenotype masterG = (MutableGenotype) genotypes.get(g.getSampleName()); + for (Map.Entry attr : g.getAttributes().entrySet()) { + if (!masterG.hasAttribute(attr.getKey())) { + //System.out.printf("Adding GT attribute %s to masterG %s, new %s%n", attr, masterG, g); + masterG.putAttribute(attr.getKey(), attr.getValue()); + } + } + + if (masterG.isPhased() != g.isPhased()) { + if (masterG.sameGenotype(g)) { + // System.out.printf("Updating phasing %s to masterG %s, new %s%n", g.isPhased(), masterG, g); + masterG.setAlleles(g.getAlleles()); + masterG.setPhase(g.isPhased()); + } + //else System.out.println("WARNING: Not updating phase, since genotypes differ between master file and auxiliary info file!"); + } + +// if ( MathUtils.compareDoubles(masterG.getNegLog10PError(), g.getNegLog10PError()) != 0 ) { +// System.out.printf("Updating GQ %s to masterG %s, new %s%n", g.getNegLog10PError(), masterG, g); +// masterG.setNegLog10PError(g.getNegLog10PError()); +// } + + } + + for (Map.Entry attr : vc.getAttributes().entrySet()) { + if (!masterAttributes.containsKey(attr.getKey())) { + //System.out.printf("Adding VC attribute %s to master %s, new %s%n", attr, master, vc); + masterAttributes.put(attr.getKey(), attr.getValue()); + } + } + } + } + + return new VariantContext(master.getSource(), master.getChr(), master.getStart(), master.getEnd(), master.getAlleles(), genotypes, master.getNegLog10PError(), master.getFilters(), masterAttributes); + } + + private static VariantContext findMaster(Collection unsortedVCs, String masterName) { + for (VariantContext vc : unsortedVCs) { + if (vc.getSource().equals(masterName)) { + return vc; + } + } + + throw new ReviewedStingException(String.format("Couldn't find master VCF %s at %s", masterName, unsortedVCs.iterator().next())); + } + + /** + * Merges VariantContexts into a single hybrid. Takes genotypes for common samples in priority order, if provided. + * If uniqifySamples is true, the priority order is ignored and names are created by concatenating the VC name with + * the sample name + * + * @param genomeLocParser loc parser + * @param unsortedVCs collection of unsorted VCs + * @param priorityListOfVCs priority list detailing the order in which we should grab the VCs + * @param filteredRecordMergeType merge type for filtered records + * @param genotypeMergeOptions merge option for genotypes + * @param annotateOrigin should we annotate the set it came from? + * @param printMessages should we print messages? + * @param setKey the key name of the set + * @param filteredAreUncalled are filtered records uncalled? + * @param mergeInfoWithMaxAC should we merge in info from the VC with maximum allele count? + * @return new VariantContext representing the merge of unsortedVCs + */ + public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, + final Collection unsortedVCs, + final List priorityListOfVCs, + final FilteredRecordMergeType filteredRecordMergeType, + final GenotypeMergeType genotypeMergeOptions, + final boolean annotateOrigin, + final boolean printMessages, + final String setKey, + final boolean filteredAreUncalled, + final boolean mergeInfoWithMaxAC ) { + if ( unsortedVCs == null || unsortedVCs.size() == 0 ) + return null; + + if ( annotateOrigin && priorityListOfVCs == null ) + throw new IllegalArgumentException("Cannot merge calls and annotate their origins without a complete priority list of VariantContexts"); + + if ( genotypeMergeOptions == GenotypeMergeType.REQUIRE_UNIQUE ) + verifyUniqueSampleNames(unsortedVCs); + + List prepaddedVCs = sortVariantContextsByPriority(unsortedVCs, priorityListOfVCs, genotypeMergeOptions); + // Make sure all variant contexts are padded with reference base in case of indels if necessary + List VCs = new ArrayList(); + + for (VariantContext vc : prepaddedVCs) { + // also a reasonable place to remove filtered calls, if needed + if ( ! filteredAreUncalled || vc.isNotFiltered() ) + VCs.add(VariantContext.createVariantContextWithPaddedAlleles(vc, false)); + } + if ( VCs.size() == 0 ) // everything is filtered out and we're filteredAreUncalled + return null; + + // establish the baseline info from the first VC + final VariantContext first = VCs.get(0); + final String name = first.getSource(); + final Allele refAllele = determineReferenceAllele(VCs); + + final Set alleles = new LinkedHashSet(); + final Set filters = new TreeSet(); + final Map attributes = new TreeMap(); + final Set inconsistentAttributes = new HashSet(); + final Set variantSources = new HashSet(); // contains the set of sources we found in our set of VCs that are variant + final Set rsIDs = new LinkedHashSet(1); // most of the time there's one id + + GenomeLoc loc = getLocation(genomeLocParser,first); + int depth = 0; + int maxAC = -1; + final Map attributesWithMaxAC = new TreeMap(); + double negLog10PError = -1; + VariantContext vcWithMaxAC = null; + Map genotypes = new TreeMap(); + + // counting the number of filtered and variant VCs + int nFiltered = 0; + + boolean remapped = false; + + // cycle through and add info from the other VCs, making sure the loc/reference matches + + for ( VariantContext vc : VCs ) { + if ( loc.getStart() != vc.getStart() ) // || !first.getReference().equals(vc.getReference()) ) + throw new ReviewedStingException("BUG: attempting to merge VariantContexts with different start sites: first="+ first.toString() + " second=" + vc.toString()); + + if ( getLocation(genomeLocParser,vc).size() > loc.size() ) + loc = getLocation(genomeLocParser,vc); // get the longest location + + nFiltered += vc.isFiltered() ? 1 : 0; + if ( vc.isVariant() ) variantSources.add(vc.getSource()); + + AlleleMapper alleleMapping = resolveIncompatibleAlleles(refAllele, vc, alleles); + remapped = remapped || alleleMapping.needsRemapping(); + + alleles.addAll(alleleMapping.values()); + + mergeGenotypes(genotypes, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); + + negLog10PError = Math.max(negLog10PError, vc.isVariant() ? vc.getNegLog10PError() : -1); + + filters.addAll(vc.getFilters()); + + // + // add attributes + // + // special case DP (add it up) and ID (just preserve it) + // + if (vc.hasAttribute(VCFConstants.DEPTH_KEY)) + depth += vc.getAttributeAsInt(VCFConstants.DEPTH_KEY, 0); + if ( vc.hasID() && ! vc.getID().equals(VCFConstants.EMPTY_ID_FIELD) ) rsIDs.add(vc.getID()); + if (mergeInfoWithMaxAC && vc.hasAttribute(VCFConstants.ALLELE_COUNT_KEY)) { + String rawAlleleCounts = vc.getAttributeAsString(VCFConstants.ALLELE_COUNT_KEY, null); + // lets see if the string contains a , separator + if (rawAlleleCounts.contains(VCFConstants.INFO_FIELD_ARRAY_SEPARATOR)) { + List alleleCountArray = Arrays.asList(rawAlleleCounts.substring(1, rawAlleleCounts.length() - 1).split(VCFConstants.INFO_FIELD_ARRAY_SEPARATOR)); + for (String alleleCount : alleleCountArray) { + final int ac = Integer.valueOf(alleleCount.trim()); + if (ac > maxAC) { + maxAC = ac; + vcWithMaxAC = vc; + } + } + } else { + final int ac = Integer.valueOf(rawAlleleCounts); + if (ac > maxAC) { + maxAC = ac; + vcWithMaxAC = vc; + } + } + } + + for (Map.Entry p : vc.getAttributes().entrySet()) { + String key = p.getKey(); + // if we don't like the key already, don't go anywhere + if ( ! inconsistentAttributes.contains(key) ) { + boolean alreadyFound = attributes.containsKey(key); + Object boundValue = attributes.get(key); + boolean boundIsMissingValue = alreadyFound && boundValue.equals(VCFConstants.MISSING_VALUE_v4); + + if ( alreadyFound && ! boundValue.equals(p.getValue()) && ! boundIsMissingValue ) { + // we found the value but we're inconsistent, put it in the exclude list + //System.out.printf("Inconsistent INFO values: %s => %s and %s%n", key, boundValue, p.getValue()); + inconsistentAttributes.add(key); + attributes.remove(key); + } else if ( ! alreadyFound || boundIsMissingValue ) { // no value + //if ( vc != first ) System.out.printf("Adding key %s => %s%n", p.getKey(), p.getValue()); + attributes.put(key, p.getValue()); + } + } + } + } + + // if we have more alternate alleles in the merged VC than in one or more of the + // original VCs, we need to strip out the GL/PLs (because they are no longer accurate), as well as allele-dependent attributes like AC,AF + for ( VariantContext vc : VCs ) { + if (vc.alleles.size() == 1) + continue; + if ( hasPLIncompatibleAlleles(alleles, vc.alleles)) { + logger.warn(String.format("Stripping PLs at %s due incompatible alleles merged=%s vs. single=%s", + genomeLocParser.createGenomeLoc(vc), alleles, vc.alleles)); + genotypes = stripPLs(genotypes); + // this will remove stale AC,AF attributed from vc + calculateChromosomeCounts(vc, attributes, true); + break; + } + } + + // take the VC with the maxAC and pull the attributes into a modifiable map + if ( mergeInfoWithMaxAC && vcWithMaxAC != null ) { + attributesWithMaxAC.putAll(vcWithMaxAC.getAttributes()); + } + + // if at least one record was unfiltered and we want a union, clear all of the filters + if ( filteredRecordMergeType == FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED && nFiltered != VCs.size() ) + filters.clear(); + + + if ( annotateOrigin ) { // we care about where the call came from + String setValue; + if ( nFiltered == 0 && variantSources.size() == priorityListOfVCs.size() ) // nothing was unfiltered + setValue = MERGE_INTERSECTION; + else if ( nFiltered == VCs.size() ) // everything was filtered out + setValue = MERGE_FILTER_IN_ALL; + else if ( variantSources.isEmpty() ) // everyone was reference + setValue = MERGE_REF_IN_ALL; + else { + LinkedHashSet s = new LinkedHashSet(); + for ( VariantContext vc : VCs ) + if ( vc.isVariant() ) + s.add( vc.isFiltered() ? MERGE_FILTER_PREFIX + vc.getSource() : vc.getSource() ); + setValue = Utils.join("-", s); + } + + if ( setKey != null ) { + attributes.put(setKey, setValue); + if( mergeInfoWithMaxAC && vcWithMaxAC != null ) { attributesWithMaxAC.put(setKey, vcWithMaxAC.getSource()); } + } + } + + if ( depth > 0 ) + attributes.put(VCFConstants.DEPTH_KEY, String.valueOf(depth)); + + if ( ! rsIDs.isEmpty() ) { + attributes.put(VariantContext.ID_KEY, Utils.join(",", rsIDs)); + } + + VariantContext merged = new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, negLog10PError, filters, (mergeInfoWithMaxAC ? attributesWithMaxAC : attributes) ); + // Trim the padded bases of all alleles if necessary + merged = createVariantContextWithTrimmedAlleles(merged); + + if ( printMessages && remapped ) System.out.printf("Remapped => %s%n", merged); + return merged; + } + + private static final boolean hasPLIncompatibleAlleles(final Collection alleleSet1, final Collection alleleSet2) { + final Iterator it1 = alleleSet1.iterator(); + final Iterator it2 = alleleSet2.iterator(); + + while ( it1.hasNext() && it2.hasNext() ) { + final Allele a1 = it1.next(); + final Allele a2 = it2.next(); + if ( ! a1.equals(a2) ) + return true; + } + + // by this point, at least one of the iterators is empty. All of the elements + // we've compared are equal up until this point. But it's possible that the + // sets aren't the same size, which is indicated by the test below. If they + // are of the same size, though, the sets are compatible + return it1.hasNext() || it2.hasNext(); + } + + public static boolean allelesAreSubset(VariantContext vc1, VariantContext vc2) { + // if all alleles of vc1 are a contained in alleles of vc2, return true + if (!vc1.getReference().equals(vc2.getReference())) + return false; + + for (Allele a :vc1.getAlternateAlleles()) { + if (!vc2.getAlternateAlleles().contains(a)) + return false; + } + + return true; + } + public static VariantContext createVariantContextWithTrimmedAlleles(VariantContext inputVC) { + // see if we need to trim common reference base from all alleles + boolean trimVC; + + // We need to trim common reference base from all alleles in all genotypes if a ref base is common to all alleles + Allele refAllele = inputVC.getReference(); + if (!inputVC.isVariant()) + trimVC = false; + else if (refAllele.isNull()) + trimVC = false; + else { + trimVC = (AbstractVCFCodec.computeForwardClipping(new ArrayList(inputVC.getAlternateAlleles()), + inputVC.getReference().getDisplayString()) > 0); + } + + // nothing to do if we don't need to trim bases + if (trimVC) { + List alleles = new ArrayList(); + Map genotypes = new TreeMap(); + + // set the reference base for indels in the attributes + Map attributes = new TreeMap(inputVC.getAttributes()); + + Map originalToTrimmedAlleleMap = new HashMap(); + + for (Allele a : inputVC.getAlleles()) { + if (a.isSymbolic()) { + alleles.add(a); + originalToTrimmedAlleleMap.put(a, a); + } else { + // get bases for current allele and create a new one with trimmed bases + byte[] newBases = Arrays.copyOfRange(a.getBases(), 1, a.length()); + Allele trimmedAllele = Allele.create(newBases, a.isReference()); + alleles.add(trimmedAllele); + originalToTrimmedAlleleMap.put(a, trimmedAllele); + } + } + + // detect case where we're trimming bases but resulting vc doesn't have any null allele. In that case, we keep original representation + // example: mixed records such as {TA*,TGA,TG} + boolean hasNullAlleles = false; + + for (Allele a: originalToTrimmedAlleleMap.values()) { + if (a.isNull()) + hasNullAlleles = true; + if (a.isReference()) + refAllele = a; + } + + if (!hasNullAlleles) + return inputVC; + // now we can recreate new genotypes with trimmed alleles + for ( Map.Entry sample : inputVC.getGenotypes().entrySet() ) { + + List originalAlleles = sample.getValue().getAlleles(); + List trimmedAlleles = new ArrayList(); + for ( Allele a : originalAlleles ) { + if ( a.isCalled() ) + trimmedAlleles.add(originalToTrimmedAlleleMap.get(a)); + else + trimmedAlleles.add(Allele.NO_CALL); + } + genotypes.put(sample.getKey(), Genotype.modifyAlleles(sample.getValue(), trimmedAlleles)); + + } + return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVC.filtersWereApplied() ? inputVC.getFilters() : null, attributes, new Byte(inputVC.getReference().getBases()[0])); + + } + + return inputVC; + } + + public static Map stripPLs(Map genotypes) { + Map newGs = new HashMap(genotypes.size()); + + for ( Map.Entry g : genotypes.entrySet() ) { + newGs.put(g.getKey(), g.getValue().hasLikelihoods() ? removePLs(g.getValue()) : g.getValue()); + } + + return newGs; + } + + public static Map> separateVariantContextsByType(Collection VCs) { + HashMap> mappedVCs = new HashMap>(); + for ( VariantContext vc : VCs ) { + + // look at previous variant contexts of different type. If: + // a) otherVC has alleles which are subset of vc, remove otherVC from its list and add otherVC to vc's list + // b) vc has alleles which are subset of otherVC. Then, add vc to otherVC's type list (rather, do nothing since vc will be added automatically to its list) + // c) neither: do nothing, just add vc to its own list + boolean addtoOwnList = true; + for (VariantContext.Type type : VariantContext.Type.values()) { + if (type.equals(vc.getType())) + continue; + + if (!mappedVCs.containsKey(type)) + continue; + + List vcList = mappedVCs.get(type); + for (int k=0; k < vcList.size(); k++) { + VariantContext otherVC = vcList.get(k); + if (allelesAreSubset(otherVC,vc)) { + // otherVC has a type different than vc and its alleles are a subset of vc: remove otherVC from its list and add it to vc's type list + vcList.remove(k); + // avoid having empty lists + if (vcList.size() == 0) + mappedVCs.remove(vcList); + if ( !mappedVCs.containsKey(vc.getType()) ) + mappedVCs.put(vc.getType(), new ArrayList()); + mappedVCs.get(vc.getType()).add(otherVC); + break; + } + else if (allelesAreSubset(vc,otherVC)) { + // vc has a type different than otherVC and its alleles are a subset of VC: add vc to otherVC's type list and don't add to its own + mappedVCs.get(type).add(vc); + addtoOwnList = false; + break; + } + } + } + if (addtoOwnList) { + if ( !mappedVCs.containsKey(vc.getType()) ) + mappedVCs.put(vc.getType(), new ArrayList()); + mappedVCs.get(vc.getType()).add(vc); + } + } + + return mappedVCs; + } + + private static class AlleleMapper { + private VariantContext vc = null; + private Map map = null; + public AlleleMapper(VariantContext vc) { this.vc = vc; } + public AlleleMapper(Map map) { this.map = map; } + public boolean needsRemapping() { return this.map != null; } + public Collection values() { return map != null ? map.values() : vc.getAlleles(); } + + public Allele remap(Allele a) { return map != null && map.containsKey(a) ? map.get(a) : a; } + + public List remap(List as) { + List newAs = new ArrayList(); + for ( Allele a : as ) { + //System.out.printf(" Remapping %s => %s%n", a, remap(a)); + newAs.add(remap(a)); + } + return newAs; + } + } + + static private void verifyUniqueSampleNames(Collection unsortedVCs) { + Set names = new HashSet(); + for ( VariantContext vc : unsortedVCs ) { + for ( String name : vc.getSampleNames() ) { + //System.out.printf("Checking %s %b%n", name, names.contains(name)); + if ( names.contains(name) ) + throw new UserException("REQUIRE_UNIQUE sample names is true but duplicate names were discovered " + name); + } + + names.addAll(vc.getSampleNames()); + } + } + + + static private Allele determineReferenceAllele(List VCs) { + Allele ref = null; + + for ( VariantContext vc : VCs ) { + Allele myRef = vc.getReference(); + if ( ref == null || ref.length() < myRef.length() ) + ref = myRef; + else if ( ref.length() == myRef.length() && ! ref.equals(myRef) ) + throw new UserException.BadInput(String.format("The provided variant file(s) have inconsistent references for the same position(s) at %s:%d, %s vs. %s", vc.getChr(), vc.getStart(), ref, myRef)); + } + + return ref; + } + + static private AlleleMapper resolveIncompatibleAlleles(Allele refAllele, VariantContext vc, Set allAlleles) { + if ( refAllele.equals(vc.getReference()) ) + return new AlleleMapper(vc); + else { + // we really need to do some work. The refAllele is the longest reference allele seen at this + // start site. So imagine it is: + // + // refAllele: ACGTGA + // myRef: ACGT + // myAlt: - + // + // We need to remap all of the alleles in vc to include the extra GA so that + // myRef => refAllele and myAlt => GA + // + + Allele myRef = vc.getReference(); + if ( refAllele.length() <= myRef.length() ) throw new ReviewedStingException("BUG: myRef="+myRef+" is longer than refAllele="+refAllele); + byte[] extraBases = Arrays.copyOfRange(refAllele.getBases(), myRef.length(), refAllele.length()); + +// System.out.printf("Remapping allele at %s%n", vc); +// System.out.printf("ref %s%n", refAllele); +// System.out.printf("myref %s%n", myRef ); +// System.out.printf("extrabases %s%n", new String(extraBases)); + + Map map = new HashMap(); + for ( Allele a : vc.getAlleles() ) { + if ( a.isReference() ) + map.put(a, refAllele); + else { + Allele extended = Allele.extend(a, extraBases); + for ( Allele b : allAlleles ) + if ( extended.equals(b) ) + extended = b; +// System.out.printf(" Extending %s => %s%n", a, extended); + map.put(a, extended); + } + } + + // debugging +// System.out.printf("mapping %s%n", map); + + return new AlleleMapper(map); + } + } + + static class CompareByPriority implements Comparator, Serializable { + List priorityListOfVCs; + public CompareByPriority(List priorityListOfVCs) { + this.priorityListOfVCs = priorityListOfVCs; + } + + private int getIndex(VariantContext vc) { + int i = priorityListOfVCs.indexOf(vc.getSource()); + if ( i == -1 ) throw new UserException.BadArgumentValue(Utils.join(",", priorityListOfVCs), "Priority list " + priorityListOfVCs + " doesn't contain variant context " + vc.getSource()); + return i; + } + + public int compare(VariantContext vc1, VariantContext vc2) { + return Integer.valueOf(getIndex(vc1)).compareTo(getIndex(vc2)); + } + } + + public static List sortVariantContextsByPriority(Collection unsortedVCs, List priorityListOfVCs, GenotypeMergeType mergeOption ) { + if ( mergeOption == GenotypeMergeType.PRIORITIZE && priorityListOfVCs == null ) + throw new IllegalArgumentException("Cannot merge calls by priority with a null priority list"); + + if ( priorityListOfVCs == null || mergeOption == GenotypeMergeType.UNSORTED ) + return new ArrayList(unsortedVCs); + else { + ArrayList sorted = new ArrayList(unsortedVCs); + Collections.sort(sorted, new CompareByPriority(priorityListOfVCs)); + return sorted; + } + } + + private static void mergeGenotypes(Map mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { + for ( Genotype g : oneVC.getGenotypes().values() ) { + String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); + if ( ! mergedGenotypes.containsKey(name) ) { + // only add if the name is new + Genotype newG = g; + + if ( uniqifySamples || alleleMapping.needsRemapping() ) { + MutableGenotype mutG = new MutableGenotype(name, g); + if ( alleleMapping.needsRemapping() ) mutG.setAlleles(alleleMapping.remap(g.getAlleles())); + newG = mutG; + } + + mergedGenotypes.put(name, newG); + } + } + } + + public static String mergedSampleName(String trackName, String sampleName, boolean uniqify ) { + return uniqify ? sampleName + "." + trackName : sampleName; + } + + /** + * Returns a context identical to this with the REF and ALT alleles reverse complemented. + * + * @param vc variant context + * @return new vc + */ + public static VariantContext reverseComplement(VariantContext vc) { + // create a mapping from original allele to reverse complemented allele + HashMap alleleMap = new HashMap(vc.getAlleles().size()); + for ( Allele originalAllele : vc.getAlleles() ) { + Allele newAllele; + if ( originalAllele.isNoCall() || originalAllele.isNull() ) + newAllele = originalAllele; + else + newAllele = Allele.create(BaseUtils.simpleReverseComplement(originalAllele.getBases()), originalAllele.isReference()); + alleleMap.put(originalAllele, newAllele); + } + + // create new Genotype objects + Map newGenotypes = new HashMap(vc.getNSamples()); + for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { + List newAlleles = new ArrayList(); + for ( Allele allele : genotype.getValue().getAlleles() ) { + Allele newAllele = alleleMap.get(allele); + if ( newAllele == null ) + newAllele = Allele.NO_CALL; + newAlleles.add(newAllele); + } + newGenotypes.put(genotype.getKey(), Genotype.modifyAlleles(genotype.getValue(), newAlleles)); + } + + return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes()); + + } + + public static VariantContext purgeUnallowedGenotypeAttributes(VariantContext vc, Set allowedAttributes) { + if ( allowedAttributes == null ) + return vc; + + Map newGenotypes = new HashMap(vc.getNSamples()); + for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { + Map attrs = new HashMap(); + for ( Map.Entry attr : genotype.getValue().getAttributes().entrySet() ) { + if ( allowedAttributes.contains(attr.getKey()) ) + attrs.put(attr.getKey(), attr.getValue()); + } + newGenotypes.put(genotype.getKey(), Genotype.modifyAttributes(genotype.getValue(), attrs)); + } + + return VariantContext.modifyGenotypes(vc, newGenotypes); + } + + public static BaseUtils.BaseSubstitutionType getSNPSubstitutionType(VariantContext context) { + if (!context.isSNP() || !context.isBiallelic()) + throw new IllegalStateException("Requested SNP substitution type for bialleic non-SNP " + context); + return BaseUtils.SNPSubstitutionType(context.getReference().getBases()[0], context.getAlternateAllele(0).getBases()[0]); + } + + /** + * If this is a BiAlleic SNP, is it a transition? + */ + public static boolean isTransition(VariantContext context) { + return getSNPSubstitutionType(context) == BaseUtils.BaseSubstitutionType.TRANSITION; + } + + /** + * If this is a BiAlleic SNP, is it a transversion? + */ + public static boolean isTransversion(VariantContext context) { + return getSNPSubstitutionType(context) == BaseUtils.BaseSubstitutionType.TRANSVERSION; + } + + /** + * create a genome location, given a variant context + * @param genomeLocParser parser + * @param vc the variant context + * @return the genomeLoc + */ + public static final GenomeLoc getLocation(GenomeLocParser genomeLocParser,VariantContext vc) { + return genomeLocParser.createGenomeLoc(vc.getChr(), vc.getStart(), vc.getEnd(), true); + } + + public abstract static class AlleleMergeRule { + // vc1, vc2 are ONLY passed to allelesShouldBeMerged() if mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2) AND allSamplesAreMergeable(vc1, vc2): + abstract public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2); + + public String toString() { + return "all samples are mergeable"; + } + } + + // NOTE: returns null if vc1 and vc2 are not merged into a single MNP record + + public static VariantContext mergeIntoMNP(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile, AlleleMergeRule alleleMergeRule) { + if (!mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2)) + return null; + + // Check that it's logically possible to merge the VCs: + if (!allSamplesAreMergeable(vc1, vc2)) + return null; + + // Check if there's a "point" in merging the VCs (e.g., annotations could be changed) + if (!alleleMergeRule.allelesShouldBeMerged(vc1, vc2)) + return null; + + return reallyMergeIntoMNP(vc1, vc2, referenceFile); + } + + private static VariantContext reallyMergeIntoMNP(VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile) { + int startInter = vc1.getEnd() + 1; + int endInter = vc2.getStart() - 1; + byte[] intermediateBases = null; + if (startInter <= endInter) { + intermediateBases = referenceFile.getSubsequenceAt(vc1.getChr(), startInter, endInter).getBases(); + StringUtil.toUpperCase(intermediateBases); + } + MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added + + Map mergedGenotypes = new HashMap(); + for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { + String sample = gt1Entry.getKey(); + Genotype gt1 = gt1Entry.getValue(); + Genotype gt2 = vc2.getGenotype(sample); + + List site1Alleles = gt1.getAlleles(); + List site2Alleles = gt2.getAlleles(); + + List mergedAllelesForSample = new LinkedList(); + + /* NOTE: Since merged alleles are added to mergedAllelesForSample in the SAME order as in the input VC records, + we preserve phase information (if any) relative to whatever precedes vc1: + */ + Iterator all2It = site2Alleles.iterator(); + for (Allele all1 : site1Alleles) { + Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() + + Allele mergedAllele = mergeData.ensureMergedAllele(all1, all2); + mergedAllelesForSample.add(mergedAllele); + } + + double mergedGQ = Math.max(gt1.getNegLog10PError(), gt2.getNegLog10PError()); + Set mergedGtFilters = new HashSet(); // Since gt1 and gt2 were unfiltered, the Genotype remains unfiltered + + Map mergedGtAttribs = new HashMap(); + PhaseAndQuality phaseQual = calcPhaseForMergedGenotypes(gt1, gt2); + if (phaseQual.PQ != null) + mergedGtAttribs.put(ReadBackedPhasingWalker.PQ_KEY, phaseQual.PQ); + + Genotype mergedGt = new Genotype(sample, mergedAllelesForSample, mergedGQ, mergedGtFilters, mergedGtAttribs, phaseQual.isPhased); + mergedGenotypes.put(sample, mergedGt); + } + + String mergedName = VariantContextUtils.mergeVariantContextNames(vc1.getSource(), vc2.getSource()); + double mergedNegLog10PError = Math.max(vc1.getNegLog10PError(), vc2.getNegLog10PError()); + Set mergedFilters = new HashSet(); // Since vc1 and vc2 were unfiltered, the merged record remains unfiltered + Map mergedAttribs = VariantContextUtils.mergeVariantContextAttributes(vc1, vc2); + + VariantContext mergedVc = new VariantContext(mergedName, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); + + mergedAttribs = new HashMap(mergedVc.getAttributes()); + VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); + mergedVc = VariantContext.modifyAttributes(mergedVc, mergedAttribs); + + return mergedVc; + } + + private static class AlleleOneAndTwo { + private Allele all1; + private Allele all2; + + public AlleleOneAndTwo(Allele all1, Allele all2) { + this.all1 = all1; + this.all2 = all2; + } + + public int hashCode() { + return all1.hashCode() + all2.hashCode(); + } + + public boolean equals(Object other) { + if (!(other instanceof AlleleOneAndTwo)) + return false; + + AlleleOneAndTwo otherAot = (AlleleOneAndTwo) other; + return (this.all1.equals(otherAot.all1) && this.all2.equals(otherAot.all2)); + } + } + + private static class MergedAllelesData { + private Map mergedAlleles; + private byte[] intermediateBases; + private int intermediateLength; + + public MergedAllelesData(byte[] intermediateBases, VariantContext vc1, VariantContext vc2) { + this.mergedAlleles = new HashMap(); // implemented equals() and hashCode() for AlleleOneAndTwo + this.intermediateBases = intermediateBases; + this.intermediateLength = this.intermediateBases != null ? this.intermediateBases.length : 0; + + this.ensureMergedAllele(vc1.getReference(), vc2.getReference(), true); + } + + public Allele ensureMergedAllele(Allele all1, Allele all2) { + return ensureMergedAllele(all1, all2, false); // false <-> since even if all1+all2 = reference, it was already created in the constructor + } + + private Allele ensureMergedAllele(Allele all1, Allele all2, boolean creatingReferenceForFirstTime) { + AlleleOneAndTwo all12 = new AlleleOneAndTwo(all1, all2); + Allele mergedAllele = mergedAlleles.get(all12); + + if (mergedAllele == null) { + byte[] bases1 = all1.getBases(); + byte[] bases2 = all2.getBases(); + + byte[] mergedBases = new byte[bases1.length + intermediateLength + bases2.length]; + System.arraycopy(bases1, 0, mergedBases, 0, bases1.length); + if (intermediateBases != null) + System.arraycopy(intermediateBases, 0, mergedBases, bases1.length, intermediateLength); + System.arraycopy(bases2, 0, mergedBases, bases1.length + intermediateLength, bases2.length); + + mergedAllele = Allele.create(mergedBases, creatingReferenceForFirstTime); + mergedAlleles.put(all12, mergedAllele); + } + + return mergedAllele; + } + + public Set getAllMergedAlleles() { + return new HashSet(mergedAlleles.values()); + } + } + + private static String mergeVariantContextNames(String name1, String name2) { + return name1 + "_" + name2; + } + + private static Map mergeVariantContextAttributes(VariantContext vc1, VariantContext vc2) { + Map mergedAttribs = new HashMap(); + + List vcList = new LinkedList(); + vcList.add(vc1); + vcList.add(vc2); + + String[] MERGE_OR_ATTRIBS = {VCFConstants.DBSNP_KEY}; + for (String orAttrib : MERGE_OR_ATTRIBS) { + boolean attribVal = false; + for (VariantContext vc : vcList) { + attribVal = vc.getAttributeAsBoolean(orAttrib, false); + if (attribVal) // already true, so no reason to continue: + break; + } + mergedAttribs.put(orAttrib, attribVal); + } + + // Merge ID fields: + String iDVal = null; + for (VariantContext vc : vcList) { + String val = vc.getAttributeAsString(VariantContext.ID_KEY, null); + if (val != null && !val.equals(VCFConstants.EMPTY_ID_FIELD)) { + if (iDVal == null) + iDVal = val; + else + iDVal += VCFConstants.ID_FIELD_SEPARATOR + val; + } + } + if (iDVal != null) + mergedAttribs.put(VariantContext.ID_KEY, iDVal); + + return mergedAttribs; + } + + private static boolean mergeIntoMNPvalidationCheck(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2) { + GenomeLoc loc1 = VariantContextUtils.getLocation(genomeLocParser, vc1); + GenomeLoc loc2 = VariantContextUtils.getLocation(genomeLocParser, vc2); + + if (!loc1.onSameContig(loc2)) + throw new ReviewedStingException("Can only merge vc1, vc2 if on the same chromosome"); + + if (!loc1.isBefore(loc2)) + throw new ReviewedStingException("Can only merge if vc1 is BEFORE vc2"); + + if (vc1.isFiltered() || vc2.isFiltered()) + return false; + + if (!vc1.getSampleNames().equals(vc2.getSampleNames())) // vc1, vc2 refer to different sample sets + return false; + + if (!allGenotypesAreUnfilteredAndCalled(vc1) || !allGenotypesAreUnfilteredAndCalled(vc2)) + return false; + + return true; + } + + private static boolean allGenotypesAreUnfilteredAndCalled(VariantContext vc) { + for (Map.Entry gtEntry : vc.getGenotypes().entrySet()) { + Genotype gt = gtEntry.getValue(); + if (gt.isNoCall() || gt.isFiltered()) + return false; + } + + return true; + } + + // Assumes that vc1 and vc2 were already checked to have the same sample names: + + private static boolean allSamplesAreMergeable(VariantContext vc1, VariantContext vc2) { + // Check that each sample's genotype in vc2 is uniquely appendable onto its genotype in vc1: + for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { + String sample = gt1Entry.getKey(); + Genotype gt1 = gt1Entry.getValue(); + Genotype gt2 = vc2.getGenotype(sample); + + if (!alleleSegregationIsKnown(gt1, gt2)) // can merge if: phased, or if either is a hom + return false; + } + + return true; + } + + public static boolean alleleSegregationIsKnown(Genotype gt1, Genotype gt2) { + if (gt1.getPloidy() != gt2.getPloidy()) + return false; + + /* If gt2 is phased or hom, then could even be MERGED with gt1 [This is standard]. + + HOWEVER, EVEN if this is not the case, but gt1.isHom(), + it is trivially known that each of gt2's alleles segregate with the single allele type present in gt1. + */ + return (gt2.isPhased() || gt2.isHom() || gt1.isHom()); + } + + private static class PhaseAndQuality { + public boolean isPhased; + public Double PQ = null; + + public PhaseAndQuality(Genotype gt) { + this.isPhased = gt.isPhased(); + if (this.isPhased) { + this.PQ = gt.getAttributeAsDouble(ReadBackedPhasingWalker.PQ_KEY, -1); + if ( this.PQ == -1 ) this.PQ = null; + } + } + } + + // Assumes that alleleSegregationIsKnown(gt1, gt2): + + private static PhaseAndQuality calcPhaseForMergedGenotypes(Genotype gt1, Genotype gt2) { + if (gt2.isPhased() || gt2.isHom()) + return new PhaseAndQuality(gt1); // maintain the phase of gt1 + + if (!gt1.isHom()) + throw new ReviewedStingException("alleleSegregationIsKnown(gt1, gt2) implies: gt2.genotypesArePhased() || gt2.isHom() || gt1.isHom()"); + + /* We're dealing with: gt1.isHom(), gt2.isHet(), !gt2.genotypesArePhased(); so, the merged (het) Genotype is not phased relative to the previous Genotype + + For example, if we're merging the third Genotype with the second one: + 0/1 + 1|1 + 0/1 + + Then, we want to output: + 0/1 + 1/2 + */ + return new PhaseAndQuality(gt2); // maintain the phase of gt2 [since !gt2.genotypesArePhased()] + } + + /* Checks if any sample has a MNP of ALT alleles (segregating together): + [Assumes that vc1 and vc2 were already checked to have the same sample names && allSamplesAreMergeable(vc1, vc2)] + */ + + public static boolean someSampleHasDoubleNonReferenceAllele(VariantContext vc1, VariantContext vc2) { + for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { + String sample = gt1Entry.getKey(); + Genotype gt1 = gt1Entry.getValue(); + Genotype gt2 = vc2.getGenotype(sample); + + List site1Alleles = gt1.getAlleles(); + List site2Alleles = gt2.getAlleles(); + + Iterator all2It = site2Alleles.iterator(); + for (Allele all1 : site1Alleles) { + Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() + + if (all1.isNonReference() && all2.isNonReference()) // corresponding alleles are alternate + return true; + } + } + + return false; + } + + /* Checks if all samples are consistent in their haplotypes: + [Assumes that vc1 and vc2 were already checked to have the same sample names && allSamplesAreMergeable(vc1, vc2)] + */ + + public static boolean doubleAllelesSegregatePerfectlyAmongSamples(VariantContext vc1, VariantContext vc2) { + // Check that Alleles at vc1 and at vc2 always segregate together in all samples (including reference): + Map allele1ToAllele2 = new HashMap(); + Map allele2ToAllele1 = new HashMap(); + + // Note the segregation of the alleles for the reference genome: + allele1ToAllele2.put(vc1.getReference(), vc2.getReference()); + allele2ToAllele1.put(vc2.getReference(), vc1.getReference()); + + // Note the segregation of the alleles for each sample (and check that it is consistent with the reference and all previous samples). + for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { + String sample = gt1Entry.getKey(); + Genotype gt1 = gt1Entry.getValue(); + Genotype gt2 = vc2.getGenotype(sample); + + List site1Alleles = gt1.getAlleles(); + List site2Alleles = gt2.getAlleles(); + + Iterator all2It = site2Alleles.iterator(); + for (Allele all1 : site1Alleles) { + Allele all2 = all2It.next(); + + Allele all1To2 = allele1ToAllele2.get(all1); + if (all1To2 == null) + allele1ToAllele2.put(all1, all2); + else if (!all1To2.equals(all2)) // all1 segregates with two different alleles at site 2 + return false; + + Allele all2To1 = allele2ToAllele1.get(all2); + if (all2To1 == null) + allele2ToAllele1.put(all2, all1); + else if (!all2To1.equals(all1)) // all2 segregates with two different alleles at site 1 + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java new file mode 100644 index 000000000..f16861f30 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2010. The Broad Institute + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext.v13; + +import org.apache.commons.jexl2.JexlContext; +import org.apache.commons.jexl2.MapContext; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.exceptions.UserException; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * + * @author aaron + * @author depristo + * + * Class VariantJEXLContext + * + * implements the JEXML context for VariantContext; this saves us from + * having to generate a JEXML context lookup map everytime we want to evaluate an expression. + * + * This is package protected, only classes in variantcontext should have access to it. + * + * // todo -- clean up to remove or better support genotype filtering + */ + +class VariantJEXLContext implements JexlContext { + // our stored variant context + private VariantContext vc; + + private interface AttributeGetter { + public Object get(VariantContext vc); + } + + private static Map x = new HashMap(); + + static { + x.put("vc", new AttributeGetter() { public Object get(VariantContext vc) { return vc; }}); + x.put("CHROM", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getChr(); }}); + x.put("POS", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getStart(); }}); + x.put("TYPE", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getType().toString(); }}); + x.put("QUAL", new AttributeGetter() { public Object get(VariantContext vc) { return 10 * vc.getNegLog10PError(); }}); + x.put("ALLELES", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getAlleles(); }}); + x.put("N_ALLELES", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getNAlleles(); }}); + x.put("FILTER", new AttributeGetter() { public Object get(VariantContext vc) { return vc.isFiltered() ? "1" : "0"; }}); + +// x.put("GT", new AttributeGetter() { public Object get(VariantContext vc) { return g.getGenotypeString(); }}); + x.put("homRefCount", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getHomRefCount(); }}); + x.put("hetCount", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getHetCount(); }}); + x.put("homVarCount", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getHomVarCount(); }}); + } + + public VariantJEXLContext(VariantContext vc) { + this.vc = vc; + } + + public Object get(String name) { + Object result = null; + if ( x.containsKey(name) ) { // dynamic resolution of name -> value via map + result = x.get(name).get(vc); + } else if ( vc.hasAttribute(name)) { + result = vc.getAttribute(name); + } else if ( vc.getFilters().contains(name) ) { + result = "1"; + } + + //System.out.printf("dynamic lookup %s => %s%n", name, result); + + return result; + } + + public boolean has(String name) { + return get(name) != null; + } + + public void set(String name, Object value) { + throw new UnsupportedOperationException("remove() not supported on a VariantJEXLContext"); + } +} + + + + +/** + * this is an implementation of a Map of JexlVCMatchExp to true or false values. It lazy initializes each value + * as requested to save as much processing time as possible. + * + * Compatible with JEXL 1.1 (this code will be easier if we move to 2.0, all of the functionality can go into the + * JexlContext's get() + * + */ + +class JEXLMap implements Map { + // our variant context and/or Genotype + private final VariantContext vc; + private final Genotype g; + + // our context + private JexlContext jContext = null; + + // our mapping from JEXLVCMatchExp to Booleans, which will be set to NULL for previously uncached JexlVCMatchExp + private Map jexl; + + + public JEXLMap(Collection jexlCollection, VariantContext vc, Genotype g) { + this.vc = vc; + this.g = g; + initialize(jexlCollection); + } + + public JEXLMap(Collection jexlCollection, VariantContext vc) { + this(jexlCollection, vc, null); + } + + private void initialize(Collection jexlCollection) { + jexl = new HashMap(); + for (VariantContextUtils.JexlVCMatchExp exp: jexlCollection) { + jexl.put(exp, null); + } + } + + /** + * create the internal JexlContext, only when required. This code is where new JEXL context variables + * should get added. + * + */ + private void createContext() { + if ( g == null ) { + // todo -- remove dependancy on g to the entire system + jContext = new VariantJEXLContext(vc); + } else { + // + // this whole branch is here just to support G jexl operations + // + Map infoMap = new HashMap(); + + if ( vc != null ) { + // create a mapping of what we know about the variant context, its Chromosome, positions, etc. + infoMap.put("CHROM", vc.getChr()); + infoMap.put("POS", vc.getStart()); + infoMap.put("TYPE", vc.getType().toString()); + infoMap.put("QUAL", String.valueOf(vc.getPhredScaledQual())); + + // add alleles + infoMap.put("ALLELES", Utils.join(";", vc.getAlleles())); + infoMap.put("N_ALLELES", String.valueOf(vc.getNAlleles())); + + // add attributes + addAttributesToMap(infoMap, vc.getAttributes()); + + // add filter fields + infoMap.put("FILTER", vc.isFiltered() ? "1" : "0"); + for ( Object filterCode : vc.getFilters() ) { + infoMap.put(String.valueOf(filterCode), "1"); + } + + // add genotype-specific fields + // TODO -- implement me when we figure out a good way to represent this + // for ( Genotype g : vc.getGenotypes().values() ) { + // String prefix = g.getSampleName() + "."; + // addAttributesToMap(infoMap, g.getAttributes(), prefix); + // infoMap.put(prefix + "GT", g.getGenotypeString()); + // } + + // add specific genotype if one is provided + infoMap.put(VCFConstants.GENOTYPE_KEY, g.getGenotypeString()); + infoMap.put("isHomRef", g.isHomRef() ? "1" : "0"); + infoMap.put("isHet", g.isHet() ? "1" : "0"); + infoMap.put("isHomVar", g.isHomVar() ? "1" : "0"); + infoMap.put(VCFConstants.GENOTYPE_QUALITY_KEY, new Double(g.getPhredScaledQual())); + for ( Map.Entry e : g.getAttributes().entrySet() ) { + if ( e.getValue() != null && !e.getValue().equals(VCFConstants.MISSING_VALUE_v4) ) + infoMap.put(e.getKey(), e.getValue()); + } + } + + // create the internal context that we can evaluate expressions against + + jContext = new MapContext(infoMap); + } + } + + /** + * @return the size of the internal data structure + */ + public int size() { + return jexl.size(); + } + + /** + * @return true if we're empty + */ + public boolean isEmpty() { return this.jexl.isEmpty(); } + + /** + * do we contain the specified key + * @param o the key + * @return true if we have a value for that key + */ + public boolean containsKey(Object o) { return jexl.containsKey(o); } + + public Boolean get(Object o) { + // if we've already determined the value, return it + if (jexl.containsKey(o) && jexl.get(o) != null) return jexl.get(o); + + // try and cast the expression + VariantContextUtils.JexlVCMatchExp e = (VariantContextUtils.JexlVCMatchExp) o; + evaluateExpression(e); + return jexl.get(e); + } + + /** + * get the keyset of map + * @return a set of keys of type JexlVCMatchExp + */ + public Set keySet() { + return jexl.keySet(); + } + + /** + * get all the values of the map. This is an expensive call, since it evaluates all keys that haven't + * been evaluated yet. This is fine if you truely want all the keys, but if you only want a portion, or know + * the keys you want, you would be better off using get() to get them by name. + * @return a collection of boolean values, representing the results of all the variants evaluated + */ + public Collection values() { + // this is an expensive call + for (VariantContextUtils.JexlVCMatchExp exp : jexl.keySet()) + if (jexl.get(exp) == null) + evaluateExpression(exp); + return jexl.values(); + } + + /** + * evaulate a JexlVCMatchExp's expression, given the current context (and setup the context if it's null) + * @param exp the JexlVCMatchExp to evaluate + */ + private void evaluateExpression(VariantContextUtils.JexlVCMatchExp exp) { + // if the context is null, we need to create it to evaluate the JEXL expression + if (this.jContext == null) createContext(); + try { + jexl.put (exp, (Boolean) exp.exp.evaluate(jContext)); + } catch (Exception e) { + throw new UserException.CommandLineException(String.format("Invalid JEXL expression detected for %s with message %s", exp.name, e.getMessage())); + } + } + + /** + * helper function: adds the list of attributes to the information map we're building + * @param infoMap the map + * @param attributes the attributes + */ + private static void addAttributesToMap(Map infoMap, Map attributes ) { + for (Map.Entry e : attributes.entrySet()) { + infoMap.put(e.getKey(), String.valueOf(e.getValue())); + } + } + + public Boolean put(VariantContextUtils.JexlVCMatchExp jexlVCMatchExp, Boolean aBoolean) { + return jexl.put(jexlVCMatchExp,aBoolean); + } + + public void putAll(Map map) { + jexl.putAll(map); + } + + // ////////////////////////////////////////////////////////////////////////////////////// + // The Following are unsupported at the moment + // ////////////////////////////////////////////////////////////////////////////////////// + + // this doesn't make much sense to implement, boolean doesn't offer too much variety to deal + // with evaluating every key in the internal map. + public boolean containsValue(Object o) { + throw new UnsupportedOperationException("containsValue() not supported on a JEXLMap"); + } + + // this doesn't make much sense + public Boolean remove(Object o) { + throw new UnsupportedOperationException("remove() not supported on a JEXLMap"); + } + + + public Set> entrySet() { + throw new UnsupportedOperationException("clear() not supported on a JEXLMap"); + } + + // nope + public void clear() { + throw new UnsupportedOperationException("clear() not supported on a JEXLMap"); + } +} From caf60804028bfcd17ca0234ce3a07678e3ec722c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 15:17:33 -0500 Subject: [PATCH 040/113] Better algorithm for merging genotypes in CombineVariants --- .../sting/utils/variantcontext/VariantContextUtils.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 161800009..238cf4b3b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -401,6 +401,7 @@ public class VariantContextUtils { final Map attributesWithMaxAC = new TreeMap(); double negLog10PError = -1; VariantContext vcWithMaxAC = null; + Set addedSamples = new HashSet(first.getNSamples()); GenotypeCollection genotypes = GenotypeCollection.create(); // counting the number of filtered and variant VCs @@ -425,7 +426,7 @@ public class VariantContextUtils { alleles.addAll(alleleMapping.values()); - mergeGenotypes(genotypes, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); + mergeGenotypes(genotypes, addedSamples, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); negLog10PError = Math.max(negLog10PError, vc.isVariant() ? vc.getNegLog10PError() : -1); @@ -824,10 +825,10 @@ public class VariantContextUtils { } } - private static void mergeGenotypes(GenotypeCollection mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { + private static void mergeGenotypes(GenotypeCollection mergedGenotypes, Set addedSamples, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { for ( Genotype g : oneVC.getGenotypes() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); - if ( ! mergedGenotypes.containsSample(name) ) { + if ( ! addedSamples.contains(name) ) { // only add if the name is new Genotype newG = g; @@ -837,6 +838,7 @@ public class VariantContextUtils { } mergedGenotypes.add(newG); + addedSamples.add(name); } } } From 974daaca4d9955d8ef862c7796561c08d08f0975 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 16:08:46 -0500 Subject: [PATCH 041/113] V13 version in archive. Can you pulled out wholesale for performance testing --- .../VariantContextBenchmark.java | 244 +-- .../variantcontext/v13/AbstractVCFCodec.java | 635 ------- .../utils/variantcontext/v13/Allele.java | 456 ----- .../utils/variantcontext/v13/Genotype.java | 349 ---- .../v13/GenotypeLikelihoods.java | 196 -- .../variantcontext/v13/IndexingVCFWriter.java | 143 -- .../v13/InferredGeneticContext.java | 243 --- .../variantcontext/v13/MutableGenotype.java | 68 - .../v13/MutableVariantContext.java | 213 --- .../utils/variantcontext/v13/VCF3Codec.java | 198 -- .../variantcontext/v13/VCFAltHeaderLine.java | 28 - .../utils/variantcontext/v13/VCFCodec.java | 228 --- .../v13/VCFCompoundHeaderLine.java | 224 --- .../variantcontext/v13/VCFConstants.java | 112 -- .../v13/VCFFilterHeaderLine.java | 28 - .../v13/VCFFormatHeaderLine.java | 32 - .../utils/variantcontext/v13/VCFHeader.java | 198 -- .../variantcontext/v13/VCFHeaderLine.java | 134 -- .../v13/VCFHeaderLineCount.java | 8 - .../v13/VCFHeaderLineTranslator.java | 124 -- .../variantcontext/v13/VCFHeaderLineType.java | 8 - .../variantcontext/v13/VCFHeaderVersion.java | 91 - .../variantcontext/v13/VCFInfoHeaderLine.java | 29 - .../v13/VCFNamedHeaderLine.java | 30 - .../utils/variantcontext/v13/VCFParser.java | 22 - .../v13/VCFSimpleHeaderLine.java | 81 - .../utils/variantcontext/v13/VCFUtils.java | 227 --- .../utils/variantcontext/v13/VCFWriter.java | 16 - .../variantcontext/v13/VariantContext.java | 1615 ----------------- .../v13/VariantContextUtils.java | 1407 -------------- .../v13/VariantJEXLContext.java | 315 ---- 31 files changed, 127 insertions(+), 7575 deletions(-) delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java delete mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java delete mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java delete mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java delete mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java delete mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index e16176dff..eda74a965 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -244,124 +244,134 @@ public class VariantContextBenchmark extends SimpleBenchmark { } } - public void timeV13(int rep) { - for ( int i = 0; i < rep; i++ ) { - FunctionToBenchmark func = getV13FunctionToBenchmark(); - FeatureCodec codec = new org.broadinstitute.sting.utils.variantcontext.v13.VCFCodec(); - runBenchmark(codec, func); - } - } + // -------------------------------------------------------------------------------- + // + // V13 + // + // In order to use this, you must move the v13 version from archive and uncomment + // + // git mv private/archive/java/src/org/broadinstitute/sting/utils/variantcontext/v13 public/java/test/org/broadinstitute/sting/utils/variantcontext/v13 + // + // -------------------------------------------------------------------------------- - public FunctionToBenchmark getV13FunctionToBenchmark() { - switch ( operation ) { - case READ: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - ; // empty operation - } - }; - case SUBSET_TO_SAMPLES: - return new FunctionToBenchmark() { - List samples; - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - if ( samples == null ) - samples = new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake); - org.broadinstitute.sting.utils.variantcontext.v13.VariantContext sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values()); - sub.getNSamples(); - } - }; - - case GET_TYPE: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getType(); - } - }; - case GET_ID: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getID(); - } - }; - case GET_GENOTYPES: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getGenotypes().size(); - } - }; - - case GET_GENOTYPES_FOR_SAMPLES: - return new FunctionToBenchmark() { - Set samples; - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - if ( samples == null ) - samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); - vc.getGenotypes(samples).size(); - } - }; - - case GET_ATTRIBUTE_STRING: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getAttribute("AN", null); - } - }; - - case GET_ATTRIBUTE_INT: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getAttributeAsInt("AC", 0); - } - }; - - case GET_N_SAMPLES: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getNSamples(); - } - }; - - case GET_GENOTYPES_IN_ORDER_OF_NAME: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - ; // TODO - TEST IS BROKEN - //vc.getGenotypesSortedByName(); - } - }; - - case CALC_GENOTYPE_COUNTS: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - vc.getHetCount(); - } - }; - - case MERGE: - return new FunctionToBenchmark() { - public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { - List toMerge = new ArrayList(); - - for ( int i = 0; i < dupsToMerge; i++ ) { - Map gc = new HashMap(); - for ( final org.broadinstitute.sting.utils.variantcontext.v13.Genotype g : vc.getGenotypes().values() ) { - String name = g.getSampleName()+"_"+i; - gc.put(name, new org.broadinstitute.sting.utils.variantcontext.v13.Genotype(name, - g.getAlleles(), g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased(), g.getLikelihoods().getAsVector())); - toMerge.add(org.broadinstitute.sting.utils.variantcontext.v13.VariantContext.modifyGenotypes(vc, gc)); - } - } - - org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.simpleMerge(b37GenomeLocParser, - toMerge, null, - org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, - org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.GenotypeMergeType.UNSORTED, - true, false, "set", false, true); - } - }; - - default: throw new IllegalArgumentException("Unexpected operation " + operation); - } - } +// public void timeV13(int rep) { +// for ( int i = 0; i < rep; i++ ) { +// FunctionToBenchmark func = getV13FunctionToBenchmark(); +// FeatureCodec codec = new org.broadinstitute.sting.utils.variantcontext.v13.VCFCodec(); +// runBenchmark(codec, func); +// } +// } +// +// public FunctionToBenchmark getV13FunctionToBenchmark() { +// switch ( operation ) { +// case READ: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// ; // empty operation +// } +// }; +// case SUBSET_TO_SAMPLES: +// return new FunctionToBenchmark() { +// List samples; +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// if ( samples == null ) +// samples = new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake); +// org.broadinstitute.sting.utils.variantcontext.v13.VariantContext sub = vc.subContextFromGenotypes(vc.getGenotypes(samples).values()); +// sub.getNSamples(); +// } +// }; +// +// case GET_TYPE: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getType(); +// } +// }; +// case GET_ID: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getID(); +// } +// }; +// case GET_GENOTYPES: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getGenotypes().size(); +// } +// }; +// +// case GET_GENOTYPES_FOR_SAMPLES: +// return new FunctionToBenchmark() { +// Set samples; +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// if ( samples == null ) +// samples = new HashSet(new ArrayList(vc.getSampleNames()).subList(0, nSamplesToTake)); +// vc.getGenotypes(samples).size(); +// } +// }; +// +// case GET_ATTRIBUTE_STRING: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getAttribute("AN", null); +// } +// }; +// +// case GET_ATTRIBUTE_INT: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getAttributeAsInt("AC", 0); +// } +// }; +// +// case GET_N_SAMPLES: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getNSamples(); +// } +// }; +// +// case GET_GENOTYPES_IN_ORDER_OF_NAME: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// ; // TODO - TEST IS BROKEN +// //vc.getGenotypesSortedByName(); +// } +// }; +// +// case CALC_GENOTYPE_COUNTS: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// vc.getHetCount(); +// } +// }; +// +// case MERGE: +// return new FunctionToBenchmark() { +// public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { +// List toMerge = new ArrayList(); +// +// for ( int i = 0; i < dupsToMerge; i++ ) { +// Map gc = new HashMap(); +// for ( final org.broadinstitute.sting.utils.variantcontext.v13.Genotype g : vc.getGenotypes().values() ) { +// String name = g.getSampleName()+"_"+i; +// gc.put(name, new org.broadinstitute.sting.utils.variantcontext.v13.Genotype(name, +// g.getAlleles(), g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased(), g.getLikelihoods().getAsVector())); +// toMerge.add(org.broadinstitute.sting.utils.variantcontext.v13.VariantContext.modifyGenotypes(vc, gc)); +// } +// } +// +// org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.simpleMerge(b37GenomeLocParser, +// toMerge, null, +// org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, +// org.broadinstitute.sting.utils.variantcontext.v13.VariantContextUtils.GenotypeMergeType.UNSORTED, +// true, false, "set", false, true); +// } +// }; +// +// default: throw new IllegalArgumentException("Unexpected operation " + operation); +// } +// } public static void main(String[] args) { CaliperMain.main(VariantContextBenchmark.class, args); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java deleted file mode 100755 index 5310313e0..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/AbstractVCFCodec.java +++ /dev/null @@ -1,635 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.apache.log4j.Logger; -import org.broad.tribble.Feature; -import org.broad.tribble.FeatureCodec; -import org.broad.tribble.NameAwareCodec; -import org.broad.tribble.TribbleException; -import org.broad.tribble.readers.LineReader; -import org.broad.tribble.util.BlockCompressedInputStream; -import org.broad.tribble.util.ParsingUtils; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; - -import java.io.*; -import java.util.*; -import java.util.zip.GZIPInputStream; - - -abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, VCFParser { - - protected final static Logger log = Logger.getLogger(VCFCodec.class); - protected final static int NUM_STANDARD_FIELDS = 8; // INFO is the 8th column - - protected VCFHeaderVersion version; - - // we have to store the list of strings that make up the header until they're needed - protected VCFHeader header = null; - - // a mapping of the allele - protected Map> alleleMap = new HashMap>(3); - - // for ParsingUtils.split - protected String[] GTValueArray = new String[100]; - protected String[] genotypeKeyArray = new String[100]; - protected String[] infoFieldArray = new String[1000]; - protected String[] infoValueArray = new String[1000]; - - // for performance testing purposes - public static boolean validate = true; - - // a key optimization -- we need a per thread string parts array, so we don't allocate a big array over and over - // todo: make this thread safe? - protected String[] parts = null; - protected String[] genotypeParts = null; - - // for performance we cache the hashmap of filter encodings for quick lookup - protected HashMap> filterHash = new HashMap>(); - - // a mapping of the VCF fields to their type, filter fields, and format fields, for quick lookup to validate against - TreeMap infoFields = new TreeMap(); - TreeMap formatFields = new TreeMap(); - Set filterFields = new HashSet(); - - // we store a name to give to each of the variant contexts we emit - protected String name = "Unknown"; - - protected int lineNo = 0; - - protected Map stringCache = new HashMap(); - - - /** - * @param reader the line reader to take header lines from - * @return the number of header lines - */ - public abstract Object readHeader(LineReader reader); - - /** - * create a genotype map - * @param str the string - * @param alleles the list of alleles - * @param chr chrom - * @param pos position - * @return a mapping of sample name to genotype object - */ - public abstract Map createGenotypeMap(String str, List alleles, String chr, int pos); - - - /** - * parse the filter string, first checking to see if we already have parsed it in a previous attempt - * @param filterString the string to parse - * @return a set of the filters applied - */ - protected abstract Set parseFilters(String filterString); - - /** - * create a VCF header - * @param headerStrings a list of strings that represent all the ## entries - * @param line the single # line (column names) - * @return the count of header lines - */ - protected Object createHeader(List headerStrings, String line) { - - headerStrings.add(line); - - Set metaData = new TreeSet(); - Set auxTags = new LinkedHashSet(); - // iterate over all the passed in strings - for ( String str : headerStrings ) { - if ( !str.startsWith(VCFHeader.METADATA_INDICATOR) ) { - String[] strings = str.substring(1).split(VCFConstants.FIELD_SEPARATOR); - if ( strings.length < VCFHeader.HEADER_FIELDS.values().length ) - throw new TribbleException.InvalidHeader("there are not enough columns present in the header line: " + str); - - int arrayIndex = 0; - for (VCFHeader.HEADER_FIELDS field : VCFHeader.HEADER_FIELDS.values()) { - try { - if (field != VCFHeader.HEADER_FIELDS.valueOf(strings[arrayIndex])) - throw new TribbleException.InvalidHeader("we were expecting column name '" + field + "' but we saw '" + strings[arrayIndex] + "'"); - } catch (IllegalArgumentException e) { - throw new TribbleException.InvalidHeader("unknown column name '" + strings[arrayIndex] + "'; it does not match a legal column header name."); - } - arrayIndex++; - } - - boolean sawFormatTag = false; - if ( arrayIndex < strings.length ) { - if ( !strings[arrayIndex].equals("FORMAT") ) - throw new TribbleException.InvalidHeader("we were expecting column name 'FORMAT' but we saw '" + strings[arrayIndex] + "'"); - sawFormatTag = true; - arrayIndex++; - } - - while ( arrayIndex < strings.length ) - auxTags.add(strings[arrayIndex++]); - - if ( sawFormatTag && auxTags.size() == 0 ) - throw new UserException.MalformedVCFHeader("The FORMAT field was provided but there is no genotype/sample data"); - - } else { - if ( str.startsWith("##INFO=") ) { - VCFInfoHeaderLine info = new VCFInfoHeaderLine(str.substring(7),version); - metaData.add(info); - infoFields.put(info.getName(), info.getType()); - } else if ( str.startsWith("##FILTER=") ) { - VCFFilterHeaderLine filter = new VCFFilterHeaderLine(str.substring(9),version); - metaData.add(filter); - filterFields.add(filter.getName()); - } else if ( str.startsWith("##FORMAT=") ) { - VCFFormatHeaderLine format = new VCFFormatHeaderLine(str.substring(9),version); - metaData.add(format); - formatFields.put(format.getName(), format.getType()); - } else { - int equals = str.indexOf("="); - if ( equals != -1 ) - metaData.add(new VCFHeaderLine(str.substring(2, equals), str.substring(equals+1))); - } - } - } - - header = new VCFHeader(metaData, auxTags); - return header; - } - - /** - * the fast decode function - * @param line the line of text for the record - * @return a feature, (not guaranteed complete) that has the correct start and stop - */ - public Feature decodeLoc(String line) { - lineNo++; - - // the same line reader is not used for parsing the header and parsing lines, if we see a #, we've seen a header line - if (line.startsWith(VCFHeader.HEADER_INDICATOR)) return null; - - // our header cannot be null, we need the genotype sample names and counts - if (header == null) throw new ReviewedStingException("VCF Header cannot be null when decoding a record"); - - final String[] locParts = new String[6]; - int nParts = ParsingUtils.split(line, locParts, VCFConstants.FIELD_SEPARATOR_CHAR, true); - - if ( nParts != 6 ) - throw new UserException.MalformedVCF("there aren't enough columns for line " + line, lineNo); - - // get our alleles (because the end position depends on them) - final String ref = getCachedString(locParts[3].toUpperCase()); - final String alts = getCachedString(locParts[4].toUpperCase()); - final List alleles = parseAlleles(ref, alts, lineNo); - - // find out our location - final int start = Integer.valueOf(locParts[1]); - int stop = start; - - // ref alleles don't need to be single bases for monomorphic sites - if ( alleles.size() == 1 ) { - stop = start + alleles.get(0).length() - 1; - } else if ( !isSingleNucleotideEvent(alleles) ) { - stop = clipAlleles(start, ref, alleles, null, lineNo); - } - - return new VCFLocFeature(locParts[0], start, stop); - } - - private final static class VCFLocFeature implements Feature { - - final String chr; - final int start, stop; - - private VCFLocFeature(String chr, int start, int stop) { - this.chr = chr; - this.start = start; - this.stop = stop; - } - - public String getChr() { return chr; } - public int getStart() { return start; } - public int getEnd() { return stop; } - } - - - /** - * decode the line into a feature (VariantContext) - * @param line the line - * @return a VariantContext - */ - public Feature decode(String line) { - // the same line reader is not used for parsing the header and parsing lines, if we see a #, we've seen a header line - if (line.startsWith(VCFHeader.HEADER_INDICATOR)) return null; - - // our header cannot be null, we need the genotype sample names and counts - if (header == null) throw new ReviewedStingException("VCF Header cannot be null when decoding a record"); - - if (parts == null) - parts = new String[Math.min(header.getColumnCount(), NUM_STANDARD_FIELDS+1)]; - - int nParts = ParsingUtils.split(line, parts, VCFConstants.FIELD_SEPARATOR_CHAR, true); - - // if we have don't have a header, or we have a header with no genotyping data check that we have eight columns. Otherwise check that we have nine (normal colummns + genotyping data) - if (( (header == null || !header.hasGenotypingData()) && nParts != NUM_STANDARD_FIELDS) || - (header != null && header.hasGenotypingData() && nParts != (NUM_STANDARD_FIELDS + 1)) ) - throw new UserException.MalformedVCF("there aren't enough columns for line " + line + " (we expected " + (header == null ? NUM_STANDARD_FIELDS : NUM_STANDARD_FIELDS + 1) + - " tokens, and saw " + nParts + " )", lineNo); - - return parseVCFLine(parts); - } - - protected void generateException(String message) { - throw new UserException.MalformedVCF(message, lineNo); - } - - protected static void generateException(String message, int lineNo) { - throw new UserException.MalformedVCF(message, lineNo); - } - - /** - * parse out the VCF line - * - * @param parts the parts split up - * @return a variant context object - */ - private VariantContext parseVCFLine(String[] parts) { - // increment the line count - lineNo++; - - // parse out the required fields - String contig = getCachedString(parts[0]); - int pos = Integer.valueOf(parts[1]); - String id = null; - if ( parts[2].length() == 0 ) - generateException("The VCF specification requires a valid ID field"); - else if ( parts[2].equals(VCFConstants.EMPTY_ID_FIELD) ) - id = VCFConstants.EMPTY_ID_FIELD; - else - id = new String(parts[2]); - String ref = getCachedString(parts[3].toUpperCase()); - String alts = getCachedString(parts[4].toUpperCase()); - Double qual = parseQual(parts[5]); - String filter = getCachedString(parts[6]); - String info = new String(parts[7]); - - // get our alleles, filters, and setup an attribute map - List alleles = parseAlleles(ref, alts, lineNo); - Set filters = parseFilters(filter); - Map attributes = parseInfo(info, id); - - // find out our current location, and clip the alleles down to their minimum length - int loc = pos; - // ref alleles don't need to be single bases for monomorphic sites - if ( alleles.size() == 1 ) { - loc = pos + alleles.get(0).length() - 1; - } else if ( !isSingleNucleotideEvent(alleles) ) { - ArrayList newAlleles = new ArrayList(); - loc = clipAlleles(pos, ref, alleles, newAlleles, lineNo); - alleles = newAlleles; - } - - // do we have genotyping data - if (parts.length > NUM_STANDARD_FIELDS) { - attributes.put(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, new String(parts[8])); - attributes.put(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY, this); - } - - VariantContext vc = null; - try { - vc = new VariantContext(name, contig, pos, loc, alleles, qual, filters, attributes, ref.getBytes()[0]); - } catch (Exception e) { - generateException(e.getMessage()); - } - - // did we resort the sample names? If so, we need to load the genotype data - if ( !header.samplesWereAlreadySorted() ) - vc.getGenotypes(); - - return vc; - } - - /** - * - * @return the type of record - */ - public Class getFeatureType() { - return VariantContext.class; - } - - /** - * get the name of this codec - * @return our set name - */ - public String getName() { - return name; - } - - /** - * set the name of this codec - * @param name new name - */ - public void setName(String name) { - this.name = name; - } - - /** - * Return a cached copy of the supplied string. - * - * @param str string - * @return interned string - */ - protected String getCachedString(String str) { - String internedString = stringCache.get(str); - if ( internedString == null ) { - internedString = new String(str); - stringCache.put(internedString, internedString); - } - return internedString; - } - - /** - * parse out the info fields - * @param infoField the fields - * @param id the indentifier - * @return a mapping of keys to objects - */ - private Map parseInfo(String infoField, String id) { - Map attributes = new HashMap(); - - if ( infoField.length() == 0 ) - generateException("The VCF specification requires a valid info field"); - - if ( !infoField.equals(VCFConstants.EMPTY_INFO_FIELD) ) { - if ( infoField.indexOf("\t") != -1 || infoField.indexOf(" ") != -1 ) - generateException("The VCF specification does not allow for whitespace in the INFO field"); - - int infoFieldSplitSize = ParsingUtils.split(infoField, infoFieldArray, VCFConstants.INFO_FIELD_SEPARATOR_CHAR, false); - for (int i = 0; i < infoFieldSplitSize; i++) { - String key; - Object value; - - int eqI = infoFieldArray[i].indexOf("="); - if ( eqI != -1 ) { - key = infoFieldArray[i].substring(0, eqI); - String str = infoFieldArray[i].substring(eqI+1); - - // split on the INFO field separator - int infoValueSplitSize = ParsingUtils.split(str, infoValueArray, VCFConstants.INFO_FIELD_ARRAY_SEPARATOR_CHAR, false); - if ( infoValueSplitSize == 1 ) { - value = infoValueArray[0]; - } else { - ArrayList valueList = new ArrayList(infoValueSplitSize); - for ( int j = 0; j < infoValueSplitSize; j++ ) - valueList.add(infoValueArray[j]); - value = valueList; - } - } else { - key = infoFieldArray[i]; - value = true; - } - - attributes.put(key, value); - } - } - - if ( ! id.equals(VCFConstants.EMPTY_ID_FIELD) ) - attributes.put(VariantContext.ID_KEY, id); - return attributes; - } - - /** - * create a an allele from an index and an array of alleles - * @param index the index - * @param alleles the alleles - * @return an Allele - */ - protected static Allele oneAllele(String index, List alleles) { - if ( index.equals(VCFConstants.EMPTY_ALLELE) ) - return Allele.NO_CALL; - int i = Integer.valueOf(index); - if ( i >= alleles.size() ) - throw new TribbleException.InternalCodecException("The allele with index " + index + " is not defined in the REF/ALT columns in the record"); - return alleles.get(i); - } - - - /** - * parse genotype alleles from the genotype string - * @param GT GT string - * @param alleles list of possible alleles - * @param cache cache of alleles for GT - * @return the allele list for the GT string - */ - protected static List parseGenotypeAlleles(String GT, List alleles, Map> cache) { - // cache results [since they are immutable] and return a single object for each genotype - List GTAlleles = cache.get(GT); - - if ( GTAlleles == null ) { - StringTokenizer st = new StringTokenizer(GT, VCFConstants.PHASING_TOKENS); - GTAlleles = new ArrayList(st.countTokens()); - while ( st.hasMoreTokens() ) { - String genotype = st.nextToken(); - GTAlleles.add(oneAllele(genotype, alleles)); - } - cache.put(GT, GTAlleles); - } - - return GTAlleles; - } - - /** - * parse out the qual value - * @param qualString the quality string - * @return return a double - */ - protected static Double parseQual(String qualString) { - // if we're the VCF 4 missing char, return immediately - if ( qualString.equals(VCFConstants.MISSING_VALUE_v4)) - return VariantContext.NO_NEG_LOG_10PERROR; - - Double val = Double.valueOf(qualString); - - // check to see if they encoded the missing qual score in VCF 3 style, with either the -1 or -1.0. check for val < 0 to save some CPU cycles - if ((val < 0) && (Math.abs(val - VCFConstants.MISSING_QUALITY_v3_DOUBLE) < VCFConstants.VCF_ENCODING_EPSILON)) - return VariantContext.NO_NEG_LOG_10PERROR; - - // scale and return the value - return val / 10.0; - } - - /** - * parse out the alleles - * @param ref the reference base - * @param alts a string of alternates to break into alleles - * @param lineNo the line number for this record - * @return a list of alleles, and a pair of the shortest and longest sequence - */ - protected static List parseAlleles(String ref, String alts, int lineNo) { - List alleles = new ArrayList(2); // we are almost always biallelic - // ref - checkAllele(ref, true, lineNo); - Allele refAllele = Allele.create(ref, true); - alleles.add(refAllele); - - if ( alts.indexOf(",") == -1 ) // only 1 alternatives, don't call string split - parseSingleAltAllele(alleles, alts, lineNo); - else - for ( String alt : alts.split(",") ) - parseSingleAltAllele(alleles, alt, lineNo); - - return alleles; - } - - /** - * check to make sure the allele is an acceptable allele - * @param allele the allele to check - * @param isRef are we the reference allele? - * @param lineNo the line number for this record - */ - private static void checkAllele(String allele, boolean isRef, int lineNo) { - if ( allele == null || allele.length() == 0 ) - generateException("Empty alleles are not permitted in VCF records", lineNo); - - if ( isSymbolicAllele(allele) ) { - if ( isRef ) { - generateException("Symbolic alleles not allowed as reference allele: " + allele, lineNo); - } - } else { - // check for VCF3 insertions or deletions - if ( (allele.charAt(0) == VCFConstants.DELETION_ALLELE_v3) || (allele.charAt(0) == VCFConstants.INSERTION_ALLELE_v3) ) - generateException("Insertions/Deletions are not supported when reading 3.x VCF's. Please" + - " convert your file to VCF4 using VCFTools, available at http://vcftools.sourceforge.net/index.html", lineNo); - - if (!Allele.acceptableAlleleBases(allele)) - generateException("Unparsable vcf record with allele " + allele, lineNo); - - if ( isRef && allele.equals(VCFConstants.EMPTY_ALLELE) ) - generateException("The reference allele cannot be missing", lineNo); - } - } - - /** - * return true if this is a symbolic allele (e.g. ) otherwise false - * @param allele the allele to check - * @return true if the allele is a symbolic allele, otherwise false - */ - private static boolean isSymbolicAllele(String allele) { - return (allele != null && allele.startsWith("<") && allele.endsWith(">") && allele.length() > 2); - } - - /** - * parse a single allele, given the allele list - * @param alleles the alleles available - * @param alt the allele to parse - * @param lineNo the line number for this record - */ - private static void parseSingleAltAllele(List alleles, String alt, int lineNo) { - checkAllele(alt, false, lineNo); - - Allele allele = Allele.create(alt, false); - if ( ! allele.isNoCall() ) - alleles.add(allele); - } - - protected static boolean isSingleNucleotideEvent(List alleles) { - for ( Allele a : alleles ) { - if ( a.length() != 1 ) - return false; - } - return true; - } - - public static int computeForwardClipping(List unclippedAlleles, String ref) { - boolean clipping = true; - - for ( Allele a : unclippedAlleles ) { - if ( a.isSymbolic() ) - continue; - - if ( a.length() < 1 || (a.getBases()[0] != ref.getBytes()[0]) ) { - clipping = false; - break; - } - } - - return (clipping) ? 1 : 0; - } - - protected static int computeReverseClipping(List unclippedAlleles, String ref, int forwardClipping, int lineNo) { - int clipping = 0; - boolean stillClipping = true; - - while ( stillClipping ) { - for ( Allele a : unclippedAlleles ) { - if ( a.isSymbolic() ) - continue; - - if ( a.length() - clipping <= forwardClipping || a.length() - forwardClipping == 0 ) - stillClipping = false; - else if ( ref.length() == clipping ) - generateException("bad alleles encountered", lineNo); - else if ( a.getBases()[a.length()-clipping-1] != ref.getBytes()[ref.length()-clipping-1] ) - stillClipping = false; - } - if ( stillClipping ) - clipping++; - } - - return clipping; - } - /** - * clip the alleles, based on the reference - * - * @param position the unadjusted start position (pre-clipping) - * @param ref the reference string - * @param unclippedAlleles the list of unclipped alleles - * @param clippedAlleles output list of clipped alleles - * @param lineNo the current line number in the file - * @return the new reference end position of this event - */ - protected static int clipAlleles(int position, String ref, List unclippedAlleles, List clippedAlleles, int lineNo) { - - int forwardClipping = computeForwardClipping(unclippedAlleles, ref); - int reverseClipping = computeReverseClipping(unclippedAlleles, ref, forwardClipping, lineNo); - - if ( clippedAlleles != null ) { - for ( Allele a : unclippedAlleles ) { - if ( a.isSymbolic() ) { - clippedAlleles.add(a); - } else { - clippedAlleles.add(Allele.create(Arrays.copyOfRange(a.getBases(), forwardClipping, a.getBases().length-reverseClipping), a.isReference())); - } - } - } - - // the new reference length - int refLength = ref.length() - reverseClipping; - - return position+Math.max(refLength - 1,0); - } - - public final static boolean canDecodeFile(final File potentialInput, final String MAGIC_HEADER_LINE) { - try { - return isVCFStream(new FileInputStream(potentialInput), MAGIC_HEADER_LINE) || - isVCFStream(new GZIPInputStream(new FileInputStream(potentialInput)), MAGIC_HEADER_LINE) || - isVCFStream(new BlockCompressedInputStream(new FileInputStream(potentialInput)), MAGIC_HEADER_LINE); - } catch ( FileNotFoundException e ) { - return false; - } catch ( IOException e ) { - return false; - } - } - - private final static boolean isVCFStream(final InputStream stream, final String MAGIC_HEADER_LINE) { - try { - byte[] buff = new byte[MAGIC_HEADER_LINE.length()]; - int nread = stream.read(buff, 0, MAGIC_HEADER_LINE.length()); - boolean eq = Arrays.equals(buff, MAGIC_HEADER_LINE.getBytes()); - return eq; -// String firstLine = new String(buff); -// return firstLine.startsWith(MAGIC_HEADER_LINE); - } catch ( IOException e ) { - return false; - } catch ( RuntimeException e ) { - return false; - } finally { - try { stream.close(); } catch ( IOException e ) {} - } - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java deleted file mode 100755 index f43cd7b98..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Allele.java +++ /dev/null @@ -1,456 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -/** - * Immutable representation of an allele - * - * Types of alleles: - * - * Ref: a t C g a // C is the reference base - * - * : a t G g a // C base is a G in some individuals - * - * : a t - g a // C base is deleted w.r.t. the reference - * - * : a t CAg a // A base is inserted w.r.t. the reference sequence - * - * In these cases, where are the alleles? - * - * SNP polymorphism of C/G -> { C , G } -> C is the reference allele - * 1 base deletion of C -> { C , - } -> C is the reference allele - * 1 base insertion of A -> { - ; A } -> Null is the reference allele - * - * Suppose I see a the following in the population: - * - * Ref: a t C g a // C is the reference base - * : a t G g a // C base is a G in some individuals - * : a t - g a // C base is deleted w.r.t. the reference - * - * How do I represent this? There are three segregating alleles: - * - * { C , G , - } - * - * Now suppose I have this more complex example: - * - * Ref: a t C g a // C is the reference base - * : a t - g a - * : a t - - a - * : a t CAg a - * - * There are actually four segregating alleles: - * - * { C g , - g, - -, and CAg } over bases 2-4 - * - * However, the molecular equivalence explicitly listed above is usually discarded, so the actual - * segregating alleles are: - * - * { C g, g, -, C a g } - * - * Critically, it should be possible to apply an allele to a reference sequence to create the - * correct haplotype sequence: - * - * Allele + reference => haplotype - * - * For convenience, we are going to create Alleles where the GenomeLoc of the allele is stored outside of the - * Allele object itself. So there's an idea of an A/C polymorphism independent of it's surrounding context. - * - * Given list of alleles it's possible to determine the "type" of the variation - * - * A / C @ loc => SNP with - * - / A => INDEL - * - * If you know where allele is the reference, you can determine whether the variant is an insertion or deletion. - * - * Alelle also supports is concept of a NO_CALL allele. This Allele represents a haplotype that couldn't be - * determined. This is usually represented by a '.' allele. - * - * Note that Alleles store all bases as bytes, in **UPPER CASE**. So 'atc' == 'ATC' from the perspective of an - * Allele. - - * @author ebanks, depristo - */ -class Allele implements Comparable { - private static final byte[] EMPTY_ALLELE_BASES = new byte[0]; - - private boolean isRef = false; - private boolean isNull = false; - private boolean isNoCall = false; - private boolean isSymbolic = false; - - private byte[] bases = null; - - public final static String NULL_ALLELE_STRING = "-"; - public final static String NO_CALL_STRING = "."; - /** A generic static NO_CALL allele for use */ - - // no public way to create an allele - private Allele(byte[] bases, boolean isRef) { - // standardize our representation of null allele and bases - if ( wouldBeNullAllele(bases) ) { - bases = EMPTY_ALLELE_BASES; - isNull = true; - } else if ( wouldBeNoCallAllele(bases) ) { - bases = EMPTY_ALLELE_BASES; - isNoCall = true; - if ( isRef ) throw new IllegalArgumentException("Cannot tag a NoCall allele as the reference allele"); - } else if ( wouldBeSymbolicAllele(bases) ) { - isSymbolic = true; - if ( isRef ) throw new IllegalArgumentException("Cannot tag a symbolic allele as the reference allele"); - } -// else -// bases = new String(bases).toUpperCase().getBytes(); // todo -- slow performance - - this.isRef = isRef; - this.bases = bases; - - if ( ! acceptableAlleleBases(bases) ) - throw new IllegalArgumentException("Unexpected base in allele bases \'" + new String(bases)+"\'"); - } - - private Allele(String bases, boolean isRef) { - this(bases.getBytes(), isRef); - } - - - private final static Allele REF_A = new Allele("A", true); - private final static Allele ALT_A = new Allele("A", false); - private final static Allele REF_C = new Allele("C", true); - private final static Allele ALT_C = new Allele("C", false); - private final static Allele REF_G = new Allele("G", true); - private final static Allele ALT_G = new Allele("G", false); - private final static Allele REF_T = new Allele("T", true); - private final static Allele ALT_T = new Allele("T", false); - private final static Allele REF_N = new Allele("N", true); - private final static Allele ALT_N = new Allele("N", false); - private final static Allele REF_NULL = new Allele(NULL_ALLELE_STRING, true); - private final static Allele ALT_NULL = new Allele(NULL_ALLELE_STRING, false); - public final static Allele NO_CALL = new Allele(NO_CALL_STRING, false); - - // --------------------------------------------------------------------------------------------------------- - // - // creation routines - // - // --------------------------------------------------------------------------------------------------------- - - /** - * Create a new Allele that includes bases and if tagged as the reference allele if isRef == true. If bases - * == '-', a Null allele is created. If bases == '.', a no call Allele is created. - * - * @param bases the DNA sequence of this variation, '-', of '.' - * @param isRef should we make this a reference allele? - * @throws IllegalArgumentException if bases contains illegal characters or is otherwise malformated - */ - public static Allele create(byte[] bases, boolean isRef) { - if ( bases == null ) - throw new IllegalArgumentException("create: the Allele base string cannot be null; use new Allele() or new Allele(\"\") to create a Null allele"); - - if ( bases.length == 1 ) { - // optimization to return a static constant Allele for each single base object - switch (bases[0]) { - case '.': - if ( isRef ) throw new IllegalArgumentException("Cannot tag a NoCall allele as the reference allele"); - return NO_CALL; - case '-': return isRef ? REF_NULL : ALT_NULL; - case 'A': case 'a' : return isRef ? REF_A : ALT_A; - case 'C': case 'c' : return isRef ? REF_C : ALT_C; - case 'G': case 'g' : return isRef ? REF_G : ALT_G; - case 'T': case 't' : return isRef ? REF_T : ALT_T; - case 'N': case 'n' : return isRef ? REF_N : ALT_N; - default: throw new IllegalArgumentException("Illegal base: " + (char)bases[0]); - } - } else { - return new Allele(bases, isRef); - } - } - - public static Allele create(byte base, boolean isRef) { -// public Allele(byte base, boolean isRef) { - return create( new byte[]{ base }, isRef); - } - - public static Allele create(byte base) { - return create( base, false ); - } - - public static Allele extend(Allele left, byte[] right) { - if (left.isSymbolic()) - throw new IllegalArgumentException("Cannot extend a symbolic allele"); - byte[] bases = null; - if ( left.length() == 0 ) - bases = right; - else { - bases = new byte[left.length() + right.length]; - System.arraycopy(left.getBases(), 0, bases, 0, left.length()); - System.arraycopy(right, 0, bases, left.length(), right.length); - } - - return create(bases, left.isReference()); - } - - /** - * @param bases bases representing an allele - * @return true if the bases represent the null allele - */ - public static boolean wouldBeNullAllele(byte[] bases) { - return (bases.length == 1 && bases[0] == '-') || bases.length == 0; - } - - /** - * @param bases bases representing an allele - * @return true if the bases represent the NO_CALL allele - */ - public static boolean wouldBeNoCallAllele(byte[] bases) { - return bases.length == 1 && bases[0] == '.'; - } - - /** - * @param bases bases representing an allele - * @return true if the bases represent a symbolic allele - */ - public static boolean wouldBeSymbolicAllele(byte[] bases) { - return bases.length > 2 && bases[0] == '<' && bases[bases.length-1] == '>'; - } - - /** - * @param bases bases representing an allele - * @return true if the bases represent the well formatted allele - */ - public static boolean acceptableAlleleBases(String bases) { - return acceptableAlleleBases(bases.getBytes()); - } - - /** - * @param bases bases representing an allele - * @return true if the bases represent the well formatted allele - */ - public static boolean acceptableAlleleBases(byte[] bases) { - if ( wouldBeNullAllele(bases) || wouldBeNoCallAllele(bases) || wouldBeSymbolicAllele(bases) ) - return true; - - for ( int i = 0; i < bases.length; i++ ) { - switch (bases[i]) { - case 'A': case 'C': case 'G': case 'T': case 'N' : case 'a': case 'c': case 'g': case 't': case 'n' : - break; - default: - return false; - } - } - - return true; - } - - /** - * @see Allele(byte[], boolean) - * - * @param bases bases representing an allele - * @param isRef is this the reference allele? - */ - public static Allele create(String bases, boolean isRef) { - //public Allele(String bases, boolean isRef) { - return create(bases.getBytes(), isRef); - } - - - /** - * Creates a non-Ref allele. @see Allele(byte[], boolean) for full information - * - * @param bases bases representing an allele - */ - public static Allele create(String bases) { - return create(bases, false); - } - - /** - * Creates a non-Ref allele. @see Allele(byte[], boolean) for full information - * - * @param bases bases representing an allele - */ - public static Allele create(byte[] bases) { - return create(bases, false); - //this(bases, false); - } - - // --------------------------------------------------------------------------------------------------------- - // - // accessor routines - // - // --------------------------------------------------------------------------------------------------------- - - //Returns true if this is the null allele - public boolean isNull() { return isNull; } - // Returns true if this is not the null allele - public boolean isNonNull() { return ! isNull(); } - - // Returns true if this is the NO_CALL allele - public boolean isNoCall() { return isNoCall; } - // Returns true if this is not the NO_CALL allele - public boolean isCalled() { return ! isNoCall(); } - - // Returns true if this Allele is the reference allele - public boolean isReference() { return isRef; } - // Returns true if this Allele is not the reference allele - public boolean isNonReference() { return ! isReference(); } - - // Returns true if this Allele is symbolic (i.e. no well-defined base sequence) - public boolean isSymbolic() { return isSymbolic; } - - // Returns a nice string representation of this object - public String toString() { - return (isNull() ? NULL_ALLELE_STRING : ( isNoCall() ? NO_CALL_STRING : getDisplayString() )) + (isReference() ? "*" : ""); - } - - /** - * Return the DNA bases segregating in this allele. Note this isn't reference polarized, - * so the Null allele is represented by a vector of length 0 - * - * @return the segregating bases - */ - public byte[] getBases() { return isSymbolic ? EMPTY_ALLELE_BASES : bases; } - - /** - * Return the DNA bases segregating in this allele in String format. - * This is useful, because toString() adds a '*' to reference alleles and getBases() returns garbage when you call toString() on it. - * - * @return the segregating bases - */ - public String getBaseString() { return new String(getBases()); } - - /** - * Return the printed representation of this allele. - * Same as getBaseString(), except for symbolic alleles. - * For symbolic alleles, the base string is empty while the display string contains . - * - * @return the allele string representation - */ - public String getDisplayString() { return new String(bases); } - - /** - * @param other the other allele - * - * @return true if these alleles are equal - */ - public boolean equals(Object other) { - return ( ! (other instanceof Allele) ? false : equals((Allele)other, false) ); - } - - /** - * @return hash code - */ - public int hashCode() { - int hash = 1; - for (int i = 0; i < bases.length; i++) - hash += (i+1) * bases[i]; - return hash; - } - - /** - * Returns true if this and other are equal. If ignoreRefState is true, then doesn't require both alleles has the - * same ref tag - * - * @param other allele to compare to - * @param ignoreRefState if true, ignore ref state in comparison - * @return true if this and other are equal - */ - public boolean equals(Allele other, boolean ignoreRefState) { - return this == other || (isRef == other.isRef || ignoreRefState) && isNull == other.isNull && isNoCall == other.isNoCall && (bases == other.bases || Arrays.equals(bases, other.bases)); - } - - /** - * @param test bases to test against - * - * @return true if this Alelle contains the same bases as test, regardless of its reference status; handles Null and NO_CALL alleles - */ - public boolean basesMatch(byte[] test) { return !isSymbolic && (bases == test || Arrays.equals(bases, test)); } - - /** - * @param test bases to test against - * - * @return true if this Alelle contains the same bases as test, regardless of its reference status; handles Null and NO_CALL alleles - */ - public boolean basesMatch(String test) { return basesMatch(test.toUpperCase().getBytes()); } - - /** - * @param test allele to test against - * - * @return true if this Alelle contains the same bases as test, regardless of its reference status; handles Null and NO_CALL alleles - */ - public boolean basesMatch(Allele test) { return basesMatch(test.getBases()); } - - /** - * @return the length of this allele. Null and NO_CALL alleles have 0 length. - */ - public int length() { - return isSymbolic ? 0 : bases.length; - } - - // --------------------------------------------------------------------------------------------------------- - // - // useful static functions - // - // --------------------------------------------------------------------------------------------------------- - - public static Allele getMatchingAllele(Collection allAlleles, String alleleBases) { - return getMatchingAllele(allAlleles, alleleBases.getBytes()); - } - - public static Allele getMatchingAllele(Collection allAlleles, byte[] alleleBases) { - for ( Allele a : allAlleles ) { - if ( a.basesMatch(alleleBases) ) { - return a; - } - } - - if ( wouldBeNoCallAllele(alleleBases) ) - return NO_CALL; - else - return null; // couldn't find anything - } - - public static List resolveAlleles(List possibleAlleles, List alleleStrings) { - List myAlleles = new ArrayList(alleleStrings.size()); - - for ( String alleleString : alleleStrings ) { - Allele allele = getMatchingAllele(possibleAlleles, alleleString); - - if ( allele == null ) { - if ( Allele.wouldBeNoCallAllele(alleleString.getBytes()) ) { - allele = create(alleleString); - } else { - throw new IllegalArgumentException("Allele " + alleleString + " not present in the list of alleles " + possibleAlleles); - } - } - - myAlleles.add(allele); - } - - return myAlleles; - } - - public int compareTo(Allele other) { - if ( isReference() && other.isNonReference() ) - return -1; - else if ( isNonReference() && other.isReference() ) - return 1; - else - return getBaseString().compareTo(other.getBaseString()); // todo -- potential performance issue - } - - public static boolean oneIsPrefixOfOther(Allele a1, Allele a2) { - if ( a1.isNull() || a2.isNull() ) - return true; - - if ( a2.length() >= a1.length() ) - return firstIsPrefixOfSecond(a1, a2); - else - return firstIsPrefixOfSecond(a2, a1); - } - - private static boolean firstIsPrefixOfSecond(Allele a1, Allele a2) { - String a1String = a1.getBaseString(); - return a2.getBaseString().substring(0, a1String.length()).equals(a1String); - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java deleted file mode 100755 index 91aa3b1da..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/Genotype.java +++ /dev/null @@ -1,349 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - - -import org.broad.tribble.util.ParsingUtils; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -import java.util.*; - -/** - * This class encompasses all the basic information about a genotype. It is immutable. - * - * @author Mark DePristo - */ -public class Genotype { - - public final static String PHASED_ALLELE_SEPARATOR = "|"; - public final static String UNPHASED_ALLELE_SEPARATOR = "/"; - - protected InferredGeneticContext commonInfo; - public final static double NO_NEG_LOG_10PERROR = InferredGeneticContext.NO_NEG_LOG_10PERROR; - protected List alleles = null; // new ArrayList(); - protected Type type = null; - - protected boolean isPhased = false; - protected boolean filtersWereAppliedToContext; - - public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased) { - this(sampleName, alleles, negLog10PError, filters, attributes, isPhased, null); - } - - public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { - if ( alleles != null ) - this.alleles = Collections.unmodifiableList(alleles); - commonInfo = new InferredGeneticContext(sampleName, negLog10PError, filters, attributes); - if ( log10Likelihoods != null ) - commonInfo.putAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); - filtersWereAppliedToContext = filters != null; - this.isPhased = isPhased; - validate(); - } - - /** - * Creates a new Genotype for sampleName with genotype according to alleles. - * @param sampleName - * @param alleles - * @param negLog10PError the confidence in these alleles - * @param log10Likelihoods a log10 likelihoods for each of the genotype combinations possible for alleles, in the standard VCF ordering, or null if not known - */ - public Genotype(String sampleName, List alleles, double negLog10PError, double[] log10Likelihoods) { - this(sampleName, alleles, negLog10PError, null, null, false, log10Likelihoods); - } - - public Genotype(String sampleName, List alleles, double negLog10PError) { - this(sampleName, alleles, negLog10PError, null, null, false); - } - - public Genotype(String sampleName, List alleles) { - this(sampleName, alleles, NO_NEG_LOG_10PERROR, null, null, false); - } - - - // --------------------------------------------------------------------------------------------------------- - // - // Partial-cloning routines (because Genotype is immutable). - // - // --------------------------------------------------------------------------------------------------------- - - public static Genotype modifyName(Genotype g, String name) { - return new Genotype(name, g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); - } - - public static Genotype modifyAttributes(Genotype g, Map attributes) { - return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attributes, g.isPhased()); - } - - public static Genotype modifyAlleles(Genotype g, List alleles) { - return new Genotype(g.getSampleName(), alleles, g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); - } - - /** - * @return the alleles for this genotype - */ - public List getAlleles() { - return alleles; - } - - public List getAlleles(Allele allele) { - if ( getType() == Type.UNAVAILABLE ) - throw new ReviewedStingException("Requesting alleles for an UNAVAILABLE genotype"); - - List al = new ArrayList(); - for ( Allele a : alleles ) - if ( a.equals(allele) ) - al.add(a); - - return Collections.unmodifiableList(al); - } - - public Allele getAllele(int i) { - if ( getType() == Type.UNAVAILABLE ) - throw new ReviewedStingException("Requesting alleles for an UNAVAILABLE genotype"); - return alleles.get(i); - } - - public boolean isPhased() { return isPhased; } - - /** - * @return the ploidy of this genotype - */ - public int getPloidy() { - if ( alleles == null ) - throw new ReviewedStingException("Requesting ploidy for an UNAVAILABLE genotype"); - return alleles.size(); - } - - public enum Type { - NO_CALL, - HOM_REF, - HET, - HOM_VAR, - UNAVAILABLE, - MIXED // no-call and call in the same genotype - } - - public Type getType() { - if ( type == null ) { - type = determineType(); - } - return type; - } - - protected Type determineType() { - if ( alleles == null ) - return Type.UNAVAILABLE; - - boolean sawNoCall = false, sawMultipleAlleles = false; - Allele observedAllele = null; - - for ( Allele allele : alleles ) { - if ( allele.isNoCall() ) - sawNoCall = true; - else if ( observedAllele == null ) - observedAllele = allele; - else if ( !allele.equals(observedAllele) ) - sawMultipleAlleles = true; - } - - if ( sawNoCall ) { - if ( observedAllele == null ) - return Type.NO_CALL; - return Type.MIXED; - } - - if ( observedAllele == null ) - throw new ReviewedStingException("BUG: there are no alleles present in this genotype but the alleles list is not null"); - - return sawMultipleAlleles ? Type.HET : observedAllele.isReference() ? Type.HOM_REF : Type.HOM_VAR; - } - - /** - * @return true if all observed alleles are the same (regardless of whether they are ref or alt); if any alleles are no-calls, this method will return false. - */ - public boolean isHom() { return isHomRef() || isHomVar(); } - - /** - * @return true if all observed alleles are ref; if any alleles are no-calls, this method will return false. - */ - public boolean isHomRef() { return getType() == Type.HOM_REF; } - - /** - * @return true if all observed alleles are alt; if any alleles are no-calls, this method will return false. - */ - public boolean isHomVar() { return getType() == Type.HOM_VAR; } - - /** - * @return true if we're het (observed alleles differ); if the ploidy is less than 2 or if any alleles are no-calls, this method will return false. - */ - public boolean isHet() { return getType() == Type.HET; } - - /** - * @return true if this genotype is not actually a genotype but a "no call" (e.g. './.' in VCF); if any alleles are not no-calls (even if some are), this method will return false. - */ - public boolean isNoCall() { return getType() == Type.NO_CALL; } - - /** - * @return true if this genotype is comprised of any alleles that are not no-calls (even if some are). - */ - public boolean isCalled() { return getType() != Type.NO_CALL && getType() != Type.UNAVAILABLE; } - - /** - * @return true if this genotype is comprised of both calls and no-calls. - */ - public boolean isMixed() { return getType() == Type.MIXED; } - - /** - * @return true if the type of this genotype is set. - */ - public boolean isAvailable() { return getType() != Type.UNAVAILABLE; } - - // - // Useful methods for getting genotype likelihoods for a genotype object, if present - // - public boolean hasLikelihoods() { - return (hasAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY) && !getAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY).equals(VCFConstants.MISSING_VALUE_v4)) || - (hasAttribute(VCFConstants.GENOTYPE_LIKELIHOODS_KEY) && !getAttribute(VCFConstants.GENOTYPE_LIKELIHOODS_KEY).equals(VCFConstants.MISSING_VALUE_v4)); - } - - public GenotypeLikelihoods getLikelihoods() { - GenotypeLikelihoods x = getLikelihoods(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, true); - if ( x != null ) - return x; - else { - x = getLikelihoods(VCFConstants.GENOTYPE_LIKELIHOODS_KEY, false); - if ( x != null ) return x; - else - throw new IllegalStateException("BUG: genotype likelihood field in " + this.getSampleName() + " sample are not either a string or a genotype likelihood class!"); - } - } - - private GenotypeLikelihoods getLikelihoods(String key, boolean asPL) { - Object x = getAttribute(key); - if ( x instanceof String ) { - if ( asPL ) - return GenotypeLikelihoods.fromPLField((String)x); - else - return GenotypeLikelihoods.fromGLField((String)x); - } - else if ( x instanceof GenotypeLikelihoods ) return (GenotypeLikelihoods)x; - else return null; - } - - public void validate() { - if ( alleles == null ) return; - if ( alleles.size() == 0) throw new IllegalArgumentException("BUG: alleles cannot be of size 0"); - - // int nNoCalls = 0; - for ( Allele allele : alleles ) { - if ( allele == null ) - throw new IllegalArgumentException("BUG: allele cannot be null in Genotype"); - // nNoCalls += allele.isNoCall() ? 1 : 0; - } - - // Technically, the spec does allow for the below case so this is not an illegal state - //if ( nNoCalls > 0 && nNoCalls != alleles.size() ) - // throw new IllegalArgumentException("BUG: alleles include some No Calls and some Calls, an illegal state " + this); - } - - public String getGenotypeString() { - return getGenotypeString(true); - } - - public String getGenotypeString(boolean ignoreRefState) { - if ( alleles == null ) - return null; - - // Notes: - // 1. Make sure to use the appropriate separator depending on whether the genotype is phased - // 2. If ignoreRefState is true, then we want just the bases of the Alleles (ignoring the '*' indicating a ref Allele) - // 3. So that everything is deterministic with regards to integration tests, we sort Alleles (when the genotype isn't phased, of course) - return ParsingUtils.join(isPhased() ? PHASED_ALLELE_SEPARATOR : UNPHASED_ALLELE_SEPARATOR, - ignoreRefState ? getAlleleStrings() : (isPhased() ? getAlleles() : ParsingUtils.sortList(getAlleles()))); - } - - private List getAlleleStrings() { - List al = new ArrayList(); - for ( Allele a : alleles ) - al.add(a.getBaseString()); - - return al; - } - - public String toString() { - int Q = (int)Math.round(getPhredScaledQual()); - return String.format("[%s %s Q%s %s]", getSampleName(), getGenotypeString(false), - Q == -10 ? "." : String.format("%2d",Q), sortedString(getAttributes())); - } - - public String toBriefString() { - return String.format("%s:Q%.2f", getGenotypeString(false), getPhredScaledQual()); - } - - public boolean sameGenotype(Genotype other) { - return sameGenotype(other, true); - } - - public boolean sameGenotype(Genotype other, boolean ignorePhase) { - if (getPloidy() != other.getPloidy()) - return false; // gotta have the same number of allele to be equal - - // By default, compare the elements in the lists of alleles, element-by-element - Collection thisAlleles = this.getAlleles(); - Collection otherAlleles = other.getAlleles(); - - if (ignorePhase) { // do not care about order, only identity of Alleles - thisAlleles = new TreeSet(thisAlleles); //implemented Allele.compareTo() - otherAlleles = new TreeSet(otherAlleles); - } - - return thisAlleles.equals(otherAlleles); - } - - /** - * a utility method for generating sorted strings from a map key set. - * @param c the map - * @param the key type - * @param the value type - * @return a sting, enclosed in {}, with comma seperated key value pairs in order of the keys - */ - private static , V> String sortedString(Map c) { - // NOTE -- THIS IS COPIED FROM GATK UTILS TO ALLOW US TO KEEP A SEPARATION BETWEEN THE GATK AND VCF CODECS - List t = new ArrayList(c.keySet()); - Collections.sort(t); - - List pairs = new ArrayList(); - for (T k : t) { - pairs.add(k + "=" + c.get(k)); - } - - return "{" + ParsingUtils.join(", ", pairs.toArray(new String[pairs.size()])) + "}"; - } - - - // --------------------------------------------------------------------------------------------------------- - // - // get routines to access context info fields - // - // --------------------------------------------------------------------------------------------------------- - public String getSampleName() { return commonInfo.getName(); } - public Set getFilters() { return commonInfo.getFilters(); } - public boolean isFiltered() { return commonInfo.isFiltered(); } - public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } - public boolean filtersWereApplied() { return filtersWereAppliedToContext; } - public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } - public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } - public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } - - public Map getAttributes() { return commonInfo.getAttributes(); } - public boolean hasAttribute(String key) { return commonInfo.hasAttribute(key); } - public Object getAttribute(String key) { return commonInfo.getAttribute(key); } - - public Object getAttribute(String key, Object defaultValue) { - return commonInfo.getAttribute(key, defaultValue); - } - - public String getAttributeAsString(String key, String defaultValue) { return commonInfo.getAttributeAsString(key, defaultValue); } - public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } - public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } - public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java deleted file mode 100755 index 02fb32451..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/GenotypeLikelihoods.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broad.tribble.TribbleException; -import org.broadinstitute.sting.utils.MathUtils; - -import java.util.EnumMap; -import java.util.Map; - -public class GenotypeLikelihoods { - public static final boolean CAP_PLS = false; - public static final int PL_CAP = 255; - - // - // There are two objects here because we are lazy in creating both representations - // for this object: a vector of log10 Probs and the PL phred-scaled string. Supports - // having one set during initializating, and dynamic creation of the other, if needed - // - private double[] log10Likelihoods = null; - private String likelihoodsAsString_PLs = null; - - public final static GenotypeLikelihoods fromPLField(String PLs) { - return new GenotypeLikelihoods(PLs); - } - - public final static GenotypeLikelihoods fromGLField(String GLs) { - return new GenotypeLikelihoods(parseDeprecatedGLString(GLs)); - } - - public final static GenotypeLikelihoods fromLog10Likelihoods(double[] log10Likelihoods) { - return new GenotypeLikelihoods(log10Likelihoods); - } - - // - // You must use the factory methods now - // - protected GenotypeLikelihoods(String asString) { - likelihoodsAsString_PLs = asString; - } - - protected GenotypeLikelihoods(double[] asVector) { - log10Likelihoods = asVector; - } - - /** - * Returns the genotypes likelihoods in negative log10 vector format. pr{AA} = x, this - * vector returns math.log10(x) for each of the genotypes. Can return null if the - * genotype likelihoods are "missing". - * - * @return - */ - public double[] getAsVector() { - // assumes one of the likelihoods vector or the string isn't null - if ( log10Likelihoods == null ) { - // make sure we create the GL string if it doesn't already exist - log10Likelihoods = parsePLsIntoLikelihoods(likelihoodsAsString_PLs); - } - - return log10Likelihoods; - } - - public String toString() { - return getAsString(); - } - - public String getAsString() { - if ( likelihoodsAsString_PLs == null ) { - // todo -- should we accept null log10Likelihoods and set PLs as MISSING? - if ( log10Likelihoods == null ) - throw new TribbleException("BUG: Attempted to get likelihoods as strings and neither the vector nor the string is set!"); - likelihoodsAsString_PLs = convertLikelihoodsToPLString(log10Likelihoods); - } - - return likelihoodsAsString_PLs; - } - - //Return genotype likelihoods as an EnumMap with Genotypes as keys and likelihoods as values - //Returns null in case of missing likelihoods - public EnumMap getAsMap(boolean normalizeFromLog10){ - //Make sure that the log10likelihoods are set - double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); - if(likelihoods == null) - return null; - EnumMap likelihoodsMap = new EnumMap(Genotype.Type.class); - likelihoodsMap.put(Genotype.Type.HOM_REF,likelihoods[Genotype.Type.HOM_REF.ordinal()-1]); - likelihoodsMap.put(Genotype.Type.HET,likelihoods[Genotype.Type.HET.ordinal()-1]); - likelihoodsMap.put(Genotype.Type.HOM_VAR, likelihoods[Genotype.Type.HOM_VAR.ordinal() - 1]); - return likelihoodsMap; - } - - //Return the neg log10 Genotype Quality (GQ) for the given genotype - //Returns Double.NEGATIVE_INFINITY in case of missing genotype - public double getNegLog10GQ(Genotype.Type genotype){ - - double qual = Double.NEGATIVE_INFINITY; - EnumMap likelihoods = getAsMap(false); - if(likelihoods == null) - return qual; - for(Map.Entry likelihood : likelihoods.entrySet()){ - if(likelihood.getKey() == genotype) - continue; - if(likelihood.getValue() > qual) - qual = likelihood.getValue(); - - } - - //Quality of the most likely genotype = likelihood(most likely) - likelihood (2nd best) - qual = likelihoods.get(genotype) - qual; - - //Quality of other genotypes 1-P(G) - if (qual < 0) { - double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); - double chosenGenotype = normalized[genotype.ordinal()-1]; - qual = -1.0 * Math.log10(1.0 - chosenGenotype); - } - return qual; - } - - private final static double[] parsePLsIntoLikelihoods(String likelihoodsAsString_PLs) { - if ( !likelihoodsAsString_PLs.equals(VCFConstants.MISSING_VALUE_v4) ) { - String[] strings = likelihoodsAsString_PLs.split(","); - double[] likelihoodsAsVector = new double[strings.length]; - for ( int i = 0; i < strings.length; i++ ) { - likelihoodsAsVector[i] = Integer.parseInt(strings[i]) / -10.0; - } - return likelihoodsAsVector; - } else - return null; - } - - /** - * Back-compatibility function to read old style GL formatted genotype likelihoods in VCF format - * @param GLString - * @return - */ - private final static double[] parseDeprecatedGLString(String GLString) { - if ( !GLString.equals(VCFConstants.MISSING_VALUE_v4) ) { - String[] strings = GLString.split(","); - double[] likelihoodsAsVector = new double[strings.length]; - for ( int i = 0; i < strings.length; i++ ) { - likelihoodsAsVector[i] = Double.parseDouble(strings[i]); - } - return likelihoodsAsVector; - } - - return null; - } - - private final static String convertLikelihoodsToPLString(double[] GLs) { - if ( GLs == null ) - return VCFConstants.MISSING_VALUE_v4; - - StringBuilder s = new StringBuilder(); - - double adjust = Double.NEGATIVE_INFINITY; - for ( double l : GLs ) adjust = Math.max(adjust, l); - - boolean first = true; - for ( double l : GLs ) { - if ( ! first ) - s.append(","); - else - first = false; - - long PL = Math.round(-10 * (l - adjust)); - if ( CAP_PLS ) - PL = Math.min(PL, PL_CAP); - s.append(Long.toString(PL)); - } - - return s.toString(); - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java deleted file mode 100644 index 85444c325..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/IndexingVCFWriter.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2011, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import com.google.java.contract.Ensures; -import com.google.java.contract.Requires; -import net.sf.samtools.SAMSequenceDictionary; -import org.broad.tribble.Tribble; -import org.broad.tribble.TribbleException; -import org.broad.tribble.index.DynamicIndexCreator; -import org.broad.tribble.index.Index; -import org.broad.tribble.index.IndexFactory; -import org.broad.tribble.util.LittleEndianOutputStream; -import org.broad.tribble.util.PositionalStream; -import org.broadinstitute.sting.gatk.refdata.tracks.IndexDictionaryUtils; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; - -import java.io.*; - -/** - * this class writes VCF files - */ -abstract class IndexingVCFWriter implements VCFWriter { - final private String name; - private final SAMSequenceDictionary refDict; - - private OutputStream outputStream; - private PositionalStream positionalStream = null; - private DynamicIndexCreator indexer = null; - private LittleEndianOutputStream idxStream = null; - - @Requires({"name != null", - "! ( location == null && output == null )", - "! ( enableOnTheFlyIndexing && location == null )"}) - protected IndexingVCFWriter(final String name, final File location, final OutputStream output, final SAMSequenceDictionary refDict, final boolean enableOnTheFlyIndexing) { - outputStream = output; - this.name = name; - this.refDict = refDict; - - if ( enableOnTheFlyIndexing ) { - try { - idxStream = new LittleEndianOutputStream(new FileOutputStream(Tribble.indexFile(location))); - //System.out.println("Creating index on the fly for " + location); - indexer = new DynamicIndexCreator(IndexFactory.IndexBalanceApproach.FOR_SEEK_TIME); - indexer.initialize(location, indexer.defaultBinSize()); - positionalStream = new PositionalStream(output); - outputStream = positionalStream; - } catch ( IOException ex ) { - // No matter what we keep going, since we don't care if we can't create the index file - idxStream = null; - indexer = null; - positionalStream = null; - } - } - } - - @Ensures("result != null") - public OutputStream getOutputStream() { - return outputStream; - } - - @Ensures("result != null") - public String getStreamName() { - return name; - } - - public abstract void writeHeader(VCFHeader header); - - /** - * attempt to close the VCF file - */ - public void close() { - // try to close the index stream (keep it separate to help debugging efforts) - if ( indexer != null ) { - try { - Index index = indexer.finalizeIndex(positionalStream.getPosition()); - IndexDictionaryUtils.setIndexSequenceDictionary(index, refDict); - index.write(idxStream); - idxStream.close(); - } catch (IOException e) { - throw new ReviewedStingException("Unable to close index for " + getStreamName(), e); - } - } - } - - /** - * add a record to the file - * - * @param vc the Variant Context object - */ - public void add(VariantContext vc) { - // if we are doing on the fly indexing, add the record ***before*** we write any bytes - if ( indexer != null ) - indexer.addFeature(vc, positionalStream.getPosition()); - } - - /** - * Returns a reasonable "name" for this writer, to display to the user if something goes wrong - * - * @param location - * @param stream - * @return - */ - protected static final String writerName(final File location, final OutputStream stream) { - return location == null ? stream.toString() : location.getAbsolutePath(); - } - - /** - * Returns a output stream writing to location, or throws a UserException if this fails - * @param location - * @return - */ - protected static OutputStream openOutputStream(final File location) { - try { - return new FileOutputStream(location); - } catch (FileNotFoundException e) { - throw new UserException.CouldNotCreateOutputFile(location, "Unable to create VCF writer", e); - } - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java deleted file mode 100755 index 43f61343e..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/InferredGeneticContext.java +++ /dev/null @@ -1,243 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - - -import java.util.*; - - -/** - * Common utility routines for VariantContext and Genotype - * - * @author depristo - */ -final class InferredGeneticContext { - public static final double NO_NEG_LOG_10PERROR = -1.0; - - private static Set NO_FILTERS = Collections.unmodifiableSet(new HashSet()); - private static Map NO_ATTRIBUTES = Collections.unmodifiableMap(new HashMap()); - - private double negLog10PError = NO_NEG_LOG_10PERROR; - private String name = null; - private Set filters = NO_FILTERS; - private Map attributes = NO_ATTRIBUTES; - -// public InferredGeneticContext(String name) { -// this.name = name; -// } -// -// public InferredGeneticContext(String name, double negLog10PError) { -// this(name); -// setNegLog10PError(negLog10PError); -// } - - public InferredGeneticContext(String name, double negLog10PError, Set filters, Map attributes) { - this.name = name; - setNegLog10PError(negLog10PError); - if ( filters != null ) - setFilters(filters); - if ( attributes != null ) - setAttributes(attributes); - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * Sets the name - * - * @param name the name associated with this information - */ - public void setName(String name) { - if ( name == null ) throw new IllegalArgumentException("Name cannot be null " + this); - this.name = name; - } - - - // --------------------------------------------------------------------------------------------------------- - // - // Filter - // - // --------------------------------------------------------------------------------------------------------- - - public Set getFilters() { - return Collections.unmodifiableSet(filters); - } - - public boolean isFiltered() { - return filters.size() > 0; - } - - public boolean isNotFiltered() { - return ! isFiltered(); - } - - public void addFilter(String filter) { - if ( filters == NO_FILTERS ) // immutable -> mutable - filters = new HashSet(filters); - - if ( filter == null ) throw new IllegalArgumentException("BUG: Attempting to add null filter " + this); - if ( getFilters().contains(filter) ) throw new IllegalArgumentException("BUG: Attempting to add duplicate filter " + filter + " at " + this); - filters.add(filter); - } - - public void addFilters(Collection filters) { - if ( filters == null ) throw new IllegalArgumentException("BUG: Attempting to add null filters at" + this); - for ( String f : filters ) - addFilter(f); - } - - public void clearFilters() { - filters = new HashSet(); - } - - public void setFilters(Collection filters) { - clearFilters(); - addFilters(filters); - } - - // --------------------------------------------------------------------------------------------------------- - // - // Working with log error rates - // - // --------------------------------------------------------------------------------------------------------- - - public boolean hasNegLog10PError() { - return getNegLog10PError() != NO_NEG_LOG_10PERROR; - } - - /** - * @return the -1 * log10-based error estimate - */ - public double getNegLog10PError() { return negLog10PError; } - public double getPhredScaledQual() { return getNegLog10PError() * 10; } - - public void setNegLog10PError(double negLog10PError) { - if ( negLog10PError < 0 && negLog10PError != NO_NEG_LOG_10PERROR ) throw new IllegalArgumentException("BUG: negLog10PError cannot be < than 0 : " + negLog10PError); - if ( Double.isInfinite(negLog10PError) ) throw new IllegalArgumentException("BUG: negLog10PError should not be Infinity"); - if ( Double.isNaN(negLog10PError) ) throw new IllegalArgumentException("BUG: negLog10PError should not be NaN"); - - this.negLog10PError = negLog10PError; - } - - // --------------------------------------------------------------------------------------------------------- - // - // Working with attributes - // - // --------------------------------------------------------------------------------------------------------- - public void clearAttributes() { - attributes = new HashMap(); - } - - /** - * @return the attribute map - */ - public Map getAttributes() { - return Collections.unmodifiableMap(attributes); - } - - // todo -- define common attributes as enum - - public void setAttributes(Map map) { - clearAttributes(); - putAttributes(map); - } - - public void putAttribute(String key, Object value) { - putAttribute(key, value, false); - } - - public void putAttribute(String key, Object value, boolean allowOverwrites) { - if ( ! allowOverwrites && hasAttribute(key) ) - throw new IllegalStateException("Attempting to overwrite key->value binding: key = " + key + " this = " + this); - - if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable - attributes = new HashMap(); - - attributes.put(key, value); - } - - public void removeAttribute(String key) { - if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable - attributes = new HashMap(); - attributes.remove(key); - } - - public void putAttributes(Map map) { - if ( map != null ) { - // for efficiency, we can skip the validation if the map is empty - if ( attributes.size() == 0 ) { - if ( attributes == NO_ATTRIBUTES ) // immutable -> mutable - attributes = new HashMap(); - attributes.putAll(map); - } else { - for ( Map.Entry elt : map.entrySet() ) { - putAttribute(elt.getKey(), elt.getValue(), false); - } - } - } - } - - public boolean hasAttribute(String key) { - return attributes.containsKey(key); - } - - public int getNumAttributes() { - return attributes.size(); - } - - /** - * @param key the attribute key - * - * @return the attribute value for the given key (or null if not set) - */ - public Object getAttribute(String key) { - return attributes.get(key); - } - - public Object getAttribute(String key, Object defaultValue) { - if ( hasAttribute(key) ) - return attributes.get(key); - else - return defaultValue; - } - - public String getAttributeAsString(String key, String defaultValue) { - Object x = getAttribute(key); - if ( x == null ) return defaultValue; - if ( x instanceof String ) return (String)x; - return String.valueOf(x); // throws an exception if this isn't a string - } - - public int getAttributeAsInt(String key, int defaultValue) { - Object x = getAttribute(key); - if ( x == null || x == VCFConstants.MISSING_VALUE_v4 ) return defaultValue; - if ( x instanceof Integer ) return (Integer)x; - return Integer.valueOf((String)x); // throws an exception if this isn't a string - } - - public double getAttributeAsDouble(String key, double defaultValue) { - Object x = getAttribute(key); - if ( x == null ) return defaultValue; - if ( x instanceof Double ) return (Double)x; - return Double.valueOf((String)x); // throws an exception if this isn't a string - } - - public boolean getAttributeAsBoolean(String key, boolean defaultValue) { - Object x = getAttribute(key); - if ( x == null ) return defaultValue; - if ( x instanceof Boolean ) return (Boolean)x; - return Boolean.valueOf((String)x); // throws an exception if this isn't a string - } - -// public String getAttributeAsString(String key) { return (String.valueOf(getAttribute(key))); } // **NOTE**: will turn a null Object into the String "null" -// public int getAttributeAsInt(String key) { Object x = getAttribute(key); return x instanceof Integer ? (Integer)x : Integer.valueOf((String)x); } -// public double getAttributeAsDouble(String key) { Object x = getAttribute(key); return x instanceof Double ? (Double)x : Double.valueOf((String)x); } -// public boolean getAttributeAsBoolean(String key) { Object x = getAttribute(key); return x instanceof Boolean ? (Boolean)x : Boolean.valueOf((String)x); } -// public Integer getAttributeAsIntegerNoException(String key) { try {return getAttributeAsInt(key);} catch (Exception e) {return null;} } -// public Double getAttributeAsDoubleNoException(String key) { try {return getAttributeAsDouble(key);} catch (Exception e) {return null;} } -// public String getAttributeAsStringNoException(String key) { if (getAttribute(key) == null) return null; return getAttributeAsString(key); } -// public Boolean getAttributeAsBooleanNoException(String key) { try {return getAttributeAsBoolean(key);} catch (Exception e) {return null;} } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java deleted file mode 100755 index f5072e040..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableGenotype.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import java.util.*; - -/** - * This class emcompasses all the basic information about a genotype. It is immutable. - * - * @author Mark DePristo - */ -class MutableGenotype extends Genotype { - public MutableGenotype(Genotype parent) { - super(parent.getSampleName(), parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); - } - - public MutableGenotype(String sampleName, Genotype parent) { - super(sampleName, parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); - } - - - public MutableGenotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean genotypesArePhased) { - super(sampleName, alleles, negLog10PError, filters, attributes, genotypesArePhased); - } - - public MutableGenotype(String sampleName, List alleles, double negLog10PError) { - super(sampleName, alleles, negLog10PError); - } - - public MutableGenotype(String sampleName, List alleles) { - super(sampleName, alleles); - } - - public Genotype unmodifiableGenotype() { - return new Genotype(getSampleName(), getAlleles(), getNegLog10PError(), getFilters(), getAttributes(), isPhased()); - } - - - /** - * - * @param alleles list of alleles - */ - public void setAlleles(List alleles) { - this.alleles = new ArrayList(alleles); - validate(); - } - - public void setPhase(boolean isPhased) { - super.isPhased = isPhased; - } - - // --------------------------------------------------------------------------------------------------------- - // - // InferredGeneticContext mutation operators - // - // --------------------------------------------------------------------------------------------------------- - public void setName(String name) { commonInfo.setName(name); } - public void addFilter(String filter) { commonInfo.addFilter(filter); } - public void addFilters(Collection filters) { commonInfo.addFilters(filters); } - public void clearFilters() { commonInfo.clearFilters(); } - public void setFilters(Collection filters) { commonInfo.setFilters(filters); } - public void setAttributes(Map map) { commonInfo.setAttributes(map); } - public void clearAttributes() { commonInfo.clearAttributes(); } - public void putAttribute(String key, Object value) { commonInfo.putAttribute(key, value); } - public void removeAttribute(String key) { commonInfo.removeAttribute(key); } - public void putAttributes(Map map) { commonInfo.putAttributes(map); } - public void setNegLog10PError(double negLog10PError) { commonInfo.setNegLog10PError(negLog10PError); } - public void putAttribute(String key, Object value, boolean allowOverwrites) { commonInfo.putAttribute(key, value, allowOverwrites); } - -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java deleted file mode 100755 index 24e71ae50..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/MutableVariantContext.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - - -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * Mutable version of VariantContext - * - * @author depristo - */ -class MutableVariantContext extends VariantContext { - // --------------------------------------------------------------------------------------------------------- - // - // constructors - // - // --------------------------------------------------------------------------------------------------------- - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); - } - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { - super(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); - } - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles) { - super(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - public MutableVariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { - super(source, contig, start, stop, alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - public MutableVariantContext(VariantContext parent) { - super(parent.getSource(), parent.contig, parent.start, parent.stop, parent.getAlleles(), parent.getGenotypes(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.getReferenceBaseForIndel()); - } - - /** - * Sets the alleles segregating in this context to the collect of alleles. Each of which must be unique according - * to equals() in Allele. Validate() should be called when you are done modifying the context. - * - * @param alleles - */ - public void setAlleles(Collection alleles) { - this.alleles.clear(); - for ( Allele a : alleles ) - addAllele(a); - } - - /** - * Adds allele to the segregating allele list in this context to the collection of alleles. The new - * allele must be be unique according to equals() in Allele. - * Validate() should be called when you are done modifying the context. - * - * @param allele - */ - public void addAllele(Allele allele) { - final boolean allowDuplicates = false; // used to be a parameter - - type = null; - - for ( Allele a : alleles ) { - if ( a.basesMatch(allele) && ! allowDuplicates ) - throw new IllegalArgumentException("Duplicate allele added to VariantContext" + this); - } - - // we are a novel allele - alleles.add(allele); - } - - public void clearGenotypes() { - genotypes = new TreeMap(); - } - - /** - * Adds this single genotype to the context, not allowing duplicate genotypes to be added - * @param genotype - */ - public void addGenotypes(Genotype genotype) { - putGenotype(genotype.getSampleName(), genotype, false); - } - - /** - * Adds these genotypes to the context, not allowing duplicate genotypes to be added - * @param genotypes - */ - public void addGenotypes(Collection genotypes) { - for ( Genotype g : genotypes ) { - addGenotype(g); - } - } - - /** - * Adds these genotype to the context, not allowing duplicate genotypes to be added. - * @param genotypes - */ - public void addGenotypes(Map genotypes) { - - for ( Map.Entry elt : genotypes.entrySet() ) { - addGenotype(elt.getValue()); - } - } - - /** - * Adds these genotypes to the context. - * - * @param genotypes - */ - public void putGenotypes(Map genotypes) { - for ( Map.Entry g : genotypes.entrySet() ) - putGenotype(g.getKey(), g.getValue()); - } - - /** - * Adds these genotypes to the context. - * - * @param genotypes - */ - public void putGenotypes(Collection genotypes) { - for ( Genotype g : genotypes ) - putGenotype(g); - } - - /** - * Adds this genotype to the context, throwing an error if it's already bound. - * - * @param genotype - */ - public void addGenotype(Genotype genotype) { - addGenotype(genotype.getSampleName(), genotype); - } - - /** - * Adds this genotype to the context, throwing an error if it's already bound. - * - * @param genotype - */ - public void addGenotype(String sampleName, Genotype genotype) { - putGenotype(sampleName, genotype, false); - } - - /** - * Adds this genotype to the context. - * - * @param genotype - */ - public void putGenotype(Genotype genotype) { - putGenotype(genotype.getSampleName(), genotype); - } - - /** - * Adds this genotype to the context. - * - * @param genotype - */ - public void putGenotype(String sampleName, Genotype genotype) { - putGenotype(sampleName, genotype, true); - } - - private void putGenotype(String sampleName, Genotype genotype, boolean allowOverwrites) { - if ( hasGenotype(sampleName) && ! allowOverwrites ) - throw new IllegalStateException("Attempting to overwrite sample->genotype binding: " + sampleName + " this=" + this); - - if ( ! sampleName.equals(genotype.getSampleName()) ) - throw new IllegalStateException("Sample name doesn't equal genotype.getSample(): " + sampleName + " genotype=" + genotype); - - this.genotypes.put(sampleName, genotype); - } - - /** - * Removes the binding from sampleName to genotype. If this doesn't exist, throws an IllegalArgumentException - * @param sampleName - */ - public void removeGenotype(String sampleName) { - if ( ! this.genotypes.containsKey(sampleName) ) - throw new IllegalArgumentException("Sample name isn't contained in genotypes " + sampleName + " genotypes =" + genotypes); - - this.genotypes.remove(sampleName); - } - - /** - * Removes genotype from the context. If this doesn't exist, throws an IllegalArgumentException - * @param genotype - */ - public void removeGenotype(Genotype genotype) { - removeGenotype(genotype.getSampleName()); - } - - // todo -- add replace genotype routine - - // --------------------------------------------------------------------------------------------------------- - // - // InferredGeneticContext mutation operators - // - // --------------------------------------------------------------------------------------------------------- - - public void setSource(String source) { commonInfo.setName(source); } - public void addFilter(String filter) { commonInfo.addFilter(filter); } - public void addFilters(Collection filters) { commonInfo.addFilters(filters); } - public void clearFilters() { commonInfo.clearFilters(); } - public void setFilters(Collection filters) { commonInfo.setFilters(filters); } - public void setAttributes(Map map) { commonInfo.setAttributes(map); } - public void clearAttributes() { commonInfo.clearAttributes(); } - public void putAttribute(String key, Object value) { commonInfo.putAttribute(key, value); } - public void removeAttribute(String key) { commonInfo.removeAttribute(key); } - public void putAttributes(Map map) { commonInfo.putAttributes(map); } - public void setNegLog10PError(double negLog10PError) { commonInfo.setNegLog10PError(negLog10PError); } - public void putAttribute(String key, Object value, boolean allowOverwrites) { commonInfo.putAttribute(key, value, allowOverwrites); } - public void setID(String id) { putAttribute(ID_KEY, id, true); } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java deleted file mode 100755 index 9f653872a..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCF3Codec.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broad.tribble.TribbleException; -import org.broad.tribble.readers.LineReader; -import org.broad.tribble.util.ParsingUtils; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.*; - - -/** - * A feature codec for the VCF3 specification, to read older VCF files. VCF3 has been - * depreciated in favor of VCF4 (See VCF codec for the latest information) - * - *

- * Reads historical VCF3 encoded files (1000 Genomes Pilot results, for example) - *

- * - *

- * See also: @see VCF specification
- * See also: @see VCF spec. publication - *

- * - * @author Mark DePristo - * @since 2010 - */ -class VCF3Codec extends AbstractVCFCodec { - public final static String VCF3_MAGIC_HEADER = "##fileformat=VCFv3"; - - - /** - * @param reader the line reader to take header lines from - * @return the number of header lines - */ - public Object readHeader(LineReader reader) { - List headerStrings = new ArrayList(); - - String line; - try { - boolean foundHeaderVersion = false; - while ((line = reader.readLine()) != null) { - lineNo++; - if (line.startsWith(VCFHeader.METADATA_INDICATOR)) { - String[] lineFields = line.substring(2).split("="); - if (lineFields.length == 2 && VCFHeaderVersion.isFormatString(lineFields[0]) ) { - if ( !VCFHeaderVersion.isVersionString(lineFields[1]) ) - throw new TribbleException.InvalidHeader(lineFields[1] + " is not a supported version"); - foundHeaderVersion = true; - version = VCFHeaderVersion.toHeaderVersion(lineFields[1]); - if ( version != VCFHeaderVersion.VCF3_3 && version != VCFHeaderVersion.VCF3_2 ) - throw new TribbleException.InvalidHeader("This codec is strictly for VCFv3 and does not support " + lineFields[1]); - } - headerStrings.add(line); - } - else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { - if (!foundHeaderVersion) { - throw new TribbleException.InvalidHeader("We never saw a header line specifying VCF version"); - } - return createHeader(headerStrings, line); - } - else { - throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); - } - - } - } catch (IOException e) { - throw new RuntimeException("IO Exception ", e); - } - throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); - } - - - /** - * parse the filter string, first checking to see if we already have parsed it in a previous attempt - * @param filterString the string to parse - * @return a set of the filters applied - */ - protected Set parseFilters(String filterString) { - - // null for unfiltered - if ( filterString.equals(VCFConstants.UNFILTERED) ) - return null; - - // empty set for passes filters - LinkedHashSet fFields = new LinkedHashSet(); - - if ( filterString.equals(VCFConstants.PASSES_FILTERS_v3) ) - return fFields; - - if ( filterString.length() == 0 ) - generateException("The VCF specification requires a valid filter status"); - - // do we have the filter string cached? - if ( filterHash.containsKey(filterString) ) - return filterHash.get(filterString); - - // otherwise we have to parse and cache the value - if ( filterString.indexOf(VCFConstants.FILTER_CODE_SEPARATOR) == -1 ) - fFields.add(filterString); - else - fFields.addAll(Arrays.asList(filterString.split(VCFConstants.FILTER_CODE_SEPARATOR))); - - filterHash.put(filterString, fFields); - - return fFields; - } - - /** - * create a genotype map - * @param str the string - * @param alleles the list of alleles - * @param chr chrom - * @param pos position - * @return a mapping of sample name to genotype object - */ - public Map createGenotypeMap(String str, List alleles, String chr, int pos) { - if (genotypeParts == null) - genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; - - int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - - Map genotypes = new LinkedHashMap(nParts); - - // get the format keys - int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); - - // cycle through the sample names - Iterator sampleNameIterator = header.getGenotypeSamples().iterator(); - - // clear out our allele mapping - alleleMap.clear(); - - // cycle through the genotype strings - for (int genotypeOffset = 1; genotypeOffset < nParts; genotypeOffset++) { - int GTValueSplitSize = ParsingUtils.split(genotypeParts[genotypeOffset], GTValueArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); - - double GTQual = VariantContext.NO_NEG_LOG_10PERROR; - Set genotypeFilters = null; - Map gtAttributes = null; - String sampleName = sampleNameIterator.next(); - - // check to see if the value list is longer than the key list, which is a problem - if (nGTKeys < GTValueSplitSize) - generateException("There are too many keys for the sample " + sampleName + ", keys = " + parts[8] + ", values = " + parts[genotypeOffset]); - - int genotypeAlleleLocation = -1; - if (nGTKeys >= 1) { - gtAttributes = new HashMap(nGTKeys - 1); - - for (int i = 0; i < nGTKeys; i++) { - final String gtKey = new String(genotypeKeyArray[i]); - boolean missing = i >= GTValueSplitSize; - - if (gtKey.equals(VCFConstants.GENOTYPE_KEY)) { - genotypeAlleleLocation = i; - } else if (gtKey.equals(VCFConstants.GENOTYPE_QUALITY_KEY)) { - GTQual = missing ? parseQual(VCFConstants.MISSING_VALUE_v4) : parseQual(GTValueArray[i]); - } else if (gtKey.equals(VCFConstants.GENOTYPE_FILTER_KEY)) { - genotypeFilters = missing ? parseFilters(VCFConstants.MISSING_VALUE_v4) : parseFilters(getCachedString(GTValueArray[i])); - } else if ( missing || GTValueArray[i].equals(VCFConstants.MISSING_GENOTYPE_QUALITY_v3) ) { - gtAttributes.put(gtKey, VCFConstants.MISSING_VALUE_v4); - } else { - gtAttributes.put(gtKey, new String(GTValueArray[i])); - } - } - } - - // check to make sure we found a genotype field - if ( genotypeAlleleLocation < 0 ) - generateException("Unable to find the GT field for the record; the GT field is required"); - if ( genotypeAlleleLocation > 0 ) - generateException("Saw GT field at position " + genotypeAlleleLocation + ", but it must be at the first position for genotypes"); - - boolean phased = GTValueArray[genotypeAlleleLocation].indexOf(VCFConstants.PHASED) != -1; - - // add it to the list - try { - genotypes.put(sampleName, new Genotype(sampleName, - parseGenotypeAlleles(GTValueArray[genotypeAlleleLocation], alleles, alleleMap), - GTQual, - genotypeFilters, - gtAttributes, - phased)); - } catch (TribbleException e) { - throw new TribbleException.InternalCodecException(e.getMessage() + ", at position " + chr+":"+pos); - } - } - - return genotypes; - } - - @Override - public boolean canDecode(final File potentialInput) { - return canDecodeFile(potentialInput, VCF3_MAGIC_HEADER); - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java deleted file mode 100644 index e432fe411..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFAltHeaderLine.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -/** - * @author ebanks - * A class representing a key=value entry for ALT fields in the VCF header - */ -class VCFAltHeaderLine extends VCFSimpleHeaderLine { - - /** - * create a VCF filter header line - * - * @param name the name for this header line - * @param description the description for this header line - */ - public VCFAltHeaderLine(String name, String description) { - super(name, description, SupportedHeaderLineType.ALT); - } - - /** - * create a VCF info header line - * - * @param line the header line - * @param version the vcf header version - */ - protected VCFAltHeaderLine(String line, VCFHeaderVersion version) { - super(line, version, SupportedHeaderLineType.ALT); - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java deleted file mode 100755 index f873aebcc..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCodec.java +++ /dev/null @@ -1,228 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broad.tribble.TribbleException; -import org.broad.tribble.readers.LineReader; -import org.broad.tribble.util.ParsingUtils; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.*; - -/** - * A feature codec for the VCF 4 specification - * - *

- * VCF is a text file format (most likely stored in a compressed manner). It contains meta-information lines, a - * header line, and then data lines each containing information about a position in the genome. - *

- *

One of the main uses of next-generation sequencing is to discover variation amongst large populations - * of related samples. Recently the format for storing next-generation read alignments has been - * standardised by the SAM/BAM file format specification. This has significantly improved the - * interoperability of next-generation tools for alignment, visualisation, and variant calling. - * We propose the Variant Call Format (VCF) as a standarised format for storing the most prevalent - * types of sequence variation, including SNPs, indels and larger structural variants, together - * with rich annotations. VCF is usually stored in a compressed manner and can be indexed for - * fast data retrieval of variants from a range of positions on the reference genome. - * The format was developed for the 1000 Genomes Project, and has also been adopted by other projects - * such as UK10K, dbSNP, or the NHLBI Exome Project. VCFtools is a software suite that implements - * various utilities for processing VCF files, including validation, merging and comparing, - * and also provides a general Perl and Python API. - * The VCF specification and VCFtools are available from http://vcftools.sourceforge.net.

- * - *

- * See also: @see VCF specification
- * See also: @see VCF spec. publication - *

- * - *

File format example

- *
- *     ##fileformat=VCFv4.0
- *     #CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO    FORMAT  NA12878
- *     chr1    109     .       A       T       0       PASS  AC=1    GT:AD:DP:GL:GQ  0/1:610,327:308:-316.30,-95.47,-803.03:99
- *     chr1    147     .       C       A       0       PASS  AC=1    GT:AD:DP:GL:GQ  0/1:294,49:118:-57.87,-34.96,-338.46:99
- * 
- * - * @author Mark DePristo - * @since 2010 - */ -public class VCFCodec extends AbstractVCFCodec { - // Our aim is to read in the records and convert to VariantContext as quickly as possible, relying on VariantContext to do the validation of any contradictory (or malformed) record parameters. - - public final static String VCF4_MAGIC_HEADER = "##fileformat=VCFv4"; - - /** - * @param reader the line reader to take header lines from - * @return the number of header lines - */ - public Object readHeader(LineReader reader) { - List headerStrings = new ArrayList(); - - String line; - try { - boolean foundHeaderVersion = false; - while ((line = reader.readLine()) != null) { - lineNo++; - if (line.startsWith(VCFHeader.METADATA_INDICATOR)) { - String[] lineFields = line.substring(2).split("="); - if (lineFields.length == 2 && VCFHeaderVersion.isFormatString(lineFields[0]) ) { - if ( !VCFHeaderVersion.isVersionString(lineFields[1]) ) - throw new TribbleException.InvalidHeader(lineFields[1] + " is not a supported version"); - foundHeaderVersion = true; - version = VCFHeaderVersion.toHeaderVersion(lineFields[1]); - if ( version == VCFHeaderVersion.VCF3_3 || version == VCFHeaderVersion.VCF3_2 ) - throw new TribbleException.InvalidHeader("This codec is strictly for VCFv4; please use the VCF3 codec for " + lineFields[1]); - if ( version != VCFHeaderVersion.VCF4_0 && version != VCFHeaderVersion.VCF4_1 ) - throw new TribbleException.InvalidHeader("This codec is strictly for VCFv4 and does not support " + lineFields[1]); - } - headerStrings.add(line); - } - else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { - if (!foundHeaderVersion) { - throw new TribbleException.InvalidHeader("We never saw a header line specifying VCF version"); - } - return createHeader(headerStrings, line); - } - else { - throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); - } - - } - } catch (IOException e) { - throw new RuntimeException("IO Exception ", e); - } - throw new TribbleException.InvalidHeader("We never saw the required CHROM header line (starting with one #) for the input VCF file"); - } - - - /** - * parse the filter string, first checking to see if we already have parsed it in a previous attempt - * - * @param filterString the string to parse - * @return a set of the filters applied or null if filters were not applied to the record (e.g. as per the missing value in a VCF) - */ - protected Set parseFilters(String filterString) { - return parseFilters(filterHash, lineNo, filterString); - } - - public static Set parseFilters(final Map> cache, final int lineNo, final String filterString) { - // null for unfiltered - if ( filterString.equals(VCFConstants.UNFILTERED) ) - return null; - - if ( filterString.equals(VCFConstants.PASSES_FILTERS_v4) ) - return Collections.emptySet(); - if ( filterString.equals(VCFConstants.PASSES_FILTERS_v3) ) - generateException(VCFConstants.PASSES_FILTERS_v3 + " is an invalid filter name in vcf4", lineNo); - if ( filterString.length() == 0 ) - generateException("The VCF specification requires a valid filter status: filter was " + filterString, lineNo); - - // do we have the filter string cached? - if ( cache != null && cache.containsKey(filterString) ) - return Collections.unmodifiableSet(cache.get(filterString)); - - // empty set for passes filters - LinkedHashSet fFields = new LinkedHashSet(); - // otherwise we have to parse and cache the value - if ( filterString.indexOf(VCFConstants.FILTER_CODE_SEPARATOR) == -1 ) - fFields.add(filterString); - else - fFields.addAll(Arrays.asList(filterString.split(VCFConstants.FILTER_CODE_SEPARATOR))); - - fFields = fFields; - if ( cache != null ) cache.put(filterString, fFields); - - return Collections.unmodifiableSet(fFields); - } - - - /** - * create a genotype map - * @param str the string - * @param alleles the list of alleles - * @return a mapping of sample name to genotype object - */ - public Map createGenotypeMap(String str, List alleles, String chr, int pos) { - if (genotypeParts == null) - genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; - - int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - - Map genotypes = new LinkedHashMap(nParts); - - // get the format keys - int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); - - // cycle through the sample names - Iterator sampleNameIterator = header.getGenotypeSamples().iterator(); - - // clear out our allele mapping - alleleMap.clear(); - - // cycle through the genotype strings - for (int genotypeOffset = 1; genotypeOffset < nParts; genotypeOffset++) { - int GTValueSplitSize = ParsingUtils.split(genotypeParts[genotypeOffset], GTValueArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); - - double GTQual = VariantContext.NO_NEG_LOG_10PERROR; - Set genotypeFilters = null; - Map gtAttributes = null; - String sampleName = sampleNameIterator.next(); - - // check to see if the value list is longer than the key list, which is a problem - if (nGTKeys < GTValueSplitSize) - generateException("There are too many keys for the sample " + sampleName + ", keys = " + parts[8] + ", values = " + parts[genotypeOffset]); - - int genotypeAlleleLocation = -1; - if (nGTKeys >= 1) { - gtAttributes = new HashMap(nGTKeys - 1); - - for (int i = 0; i < nGTKeys; i++) { - final String gtKey = new String(genotypeKeyArray[i]); - boolean missing = i >= GTValueSplitSize; - - // todo -- all of these on the fly parsing of the missing value should be static constants - if (gtKey.equals(VCFConstants.GENOTYPE_KEY)) { - genotypeAlleleLocation = i; - } else if (gtKey.equals(VCFConstants.GENOTYPE_QUALITY_KEY)) { - GTQual = missing ? parseQual(VCFConstants.MISSING_VALUE_v4) : parseQual(GTValueArray[i]); - } else if (gtKey.equals(VCFConstants.GENOTYPE_FILTER_KEY)) { - genotypeFilters = missing ? parseFilters(VCFConstants.MISSING_VALUE_v4) : parseFilters(getCachedString(GTValueArray[i])); - } else if ( missing ) { - gtAttributes.put(gtKey, VCFConstants.MISSING_VALUE_v4); - } else { - gtAttributes.put(gtKey, new String(GTValueArray[i])); - } - } - } - - // check to make sure we found a genotype field if we are a VCF4.0 file - if ( version == VCFHeaderVersion.VCF4_0 && genotypeAlleleLocation == -1 ) - generateException("Unable to find the GT field for the record; the GT field is required in VCF4.0"); - if ( genotypeAlleleLocation > 0 ) - generateException("Saw GT field at position " + genotypeAlleleLocation + ", but it must be at the first position for genotypes when present"); - - List GTalleles = (genotypeAlleleLocation == -1 ? null : parseGenotypeAlleles(GTValueArray[genotypeAlleleLocation], alleles, alleleMap)); - boolean phased = genotypeAlleleLocation != -1 && GTValueArray[genotypeAlleleLocation].indexOf(VCFConstants.PHASED) != -1; - - // add it to the list - try { - genotypes.put(sampleName, - new Genotype(sampleName, - GTalleles, - GTQual, - genotypeFilters, - gtAttributes, - phased)); - } catch (TribbleException e) { - throw new TribbleException.InternalCodecException(e.getMessage() + ", at position " + chr+":"+pos); - } - } - - return genotypes; - } - - @Override - public boolean canDecode(final File potentialInput) { - return canDecodeFile(potentialInput, VCF4_MAGIC_HEADER); - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java deleted file mode 100755 index 74d6cf62f..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFCompoundHeaderLine.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * a base class for compound header lines, which include info lines and format lines (so far) - */ -abstract class VCFCompoundHeaderLine extends VCFHeaderLine implements VCFNamedHeaderLine { - public enum SupportedHeaderLineType { - INFO(true), FORMAT(false); - - public final boolean allowFlagValues; - SupportedHeaderLineType(boolean flagValues) { - allowFlagValues = flagValues; - } - } - - // the field types - private String name; - private int count = -1; - private VCFHeaderLineCount countType; - private String description; - private VCFHeaderLineType type; - - // access methods - public String getName() { return name; } - public String getDescription() { return description; } - public VCFHeaderLineType getType() { return type; } - public VCFHeaderLineCount getCountType() { return countType; } - public int getCount() { - if ( countType != VCFHeaderLineCount.INTEGER ) - throw new ReviewedStingException("Asking for header line count when type is not an integer"); - return count; - } - - // utility method - public int getCount(int numAltAlleles) { - int myCount; - switch ( countType ) { - case INTEGER: myCount = count; break; - case UNBOUNDED: myCount = -1; break; - case A: myCount = numAltAlleles; break; - case G: myCount = ((numAltAlleles + 1) * (numAltAlleles + 2) / 2); break; - default: throw new ReviewedStingException("Unknown count type: " + countType); - } - return myCount; - } - - public void setNumberToUnbounded() { - countType = VCFHeaderLineCount.UNBOUNDED; - count = -1; - } - - // our type of line, i.e. format, info, etc - private final SupportedHeaderLineType lineType; - - /** - * create a VCF format header line - * - * @param name the name for this header line - * @param count the count for this header line - * @param type the type for this header line - * @param description the description for this header line - * @param lineType the header line type - */ - protected VCFCompoundHeaderLine(String name, int count, VCFHeaderLineType type, String description, SupportedHeaderLineType lineType) { - super(lineType.toString(), ""); - this.name = name; - this.countType = VCFHeaderLineCount.INTEGER; - this.count = count; - this.type = type; - this.description = description; - this.lineType = lineType; - validate(); - } - - /** - * create a VCF format header line - * - * @param name the name for this header line - * @param count the count type for this header line - * @param type the type for this header line - * @param description the description for this header line - * @param lineType the header line type - */ - protected VCFCompoundHeaderLine(String name, VCFHeaderLineCount count, VCFHeaderLineType type, String description, SupportedHeaderLineType lineType) { - super(lineType.toString(), ""); - this.name = name; - this.countType = count; - this.type = type; - this.description = description; - this.lineType = lineType; - validate(); - } - - /** - * create a VCF format header line - * - * @param line the header line - * @param version the VCF header version - * @param lineType the header line type - * - */ - protected VCFCompoundHeaderLine(String line, VCFHeaderVersion version, SupportedHeaderLineType lineType) { - super(lineType.toString(), ""); - Map mapping = VCFHeaderLineTranslator.parseLine(version,line, Arrays.asList("ID","Number","Type","Description")); - name = mapping.get("ID"); - count = -1; - final String numberStr = mapping.get("Number"); - if ( numberStr.equals(VCFConstants.PER_ALLELE_COUNT) ) { - countType = VCFHeaderLineCount.A; - } else if ( numberStr.equals(VCFConstants.PER_GENOTYPE_COUNT) ) { - countType = VCFHeaderLineCount.G; - } else if ( ((version == VCFHeaderVersion.VCF4_0 || version == VCFHeaderVersion.VCF4_1) && - numberStr.equals(VCFConstants.UNBOUNDED_ENCODING_v4)) || - ((version == VCFHeaderVersion.VCF3_2 || version == VCFHeaderVersion.VCF3_3) && - numberStr.equals(VCFConstants.UNBOUNDED_ENCODING_v3)) ) { - countType = VCFHeaderLineCount.UNBOUNDED; - } else { - countType = VCFHeaderLineCount.INTEGER; - count = Integer.valueOf(numberStr); - - } - type = VCFHeaderLineType.valueOf(mapping.get("Type")); - if (type == VCFHeaderLineType.Flag && !allowFlagValues()) - throw new IllegalArgumentException("Flag is an unsupported type for this kind of field"); - - description = mapping.get("Description"); - if ( description == null && ALLOW_UNBOUND_DESCRIPTIONS ) // handle the case where there's no description provided - description = UNBOUND_DESCRIPTION; - - this.lineType = lineType; - - validate(); - } - - private void validate() { - if ( name == null || type == null || description == null || lineType == null ) - throw new IllegalArgumentException(String.format("Invalid VCFCompoundHeaderLine: key=%s name=%s type=%s desc=%s lineType=%s", - super.getKey(), name, type, description, lineType )); - } - - /** - * make a string representation of this header line - * @return a string representation - */ - protected String toStringEncoding() { - Map map = new LinkedHashMap(); - map.put("ID", name); - Object number; - switch ( countType ) { - case A: number = VCFConstants.PER_ALLELE_COUNT; break; - case G: number = VCFConstants.PER_GENOTYPE_COUNT; break; - case UNBOUNDED: number = VCFConstants.UNBOUNDED_ENCODING_v4; break; - case INTEGER: - default: number = count; - } - map.put("Number", number); - map.put("Type", type); - map.put("Description", description); - return lineType.toString() + "=" + toStringEncoding(map); - } - - /** - * returns true if we're equal to another compounder header line - * @param o a compound header line - * @return true if equal - */ - public boolean equals(Object o) { - if ( !(o instanceof VCFCompoundHeaderLine) ) - return false; - VCFCompoundHeaderLine other = (VCFCompoundHeaderLine)o; - return equalsExcludingDescription(other) && - description.equals(other.description); - } - - public boolean equalsExcludingDescription(VCFCompoundHeaderLine other) { - return count == other.count && - countType == other.countType && - type == other.type && - lineType == other.lineType && - name.equals(other.name); - } - - public boolean sameLineTypeAndName(VCFCompoundHeaderLine other) { - return lineType == other.lineType && - name.equals(other.name); - } - - /** - * do we allow flag (boolean) values? (i.e. booleans where you don't have specify the value, AQ means AQ=true) - * @return true if we do, false otherwise - */ - abstract boolean allowFlagValues(); - -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java deleted file mode 100755 index 91f6b1ba9..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFConstants.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2010. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR - * THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import java.util.Locale; - -final class VCFConstants { - public static final Locale VCF_LOCALE = Locale.US; - - // reserved INFO/FORMAT field keys - public static final String ANCESTRAL_ALLELE_KEY = "AA"; - public static final String ALLELE_COUNT_KEY = "AC"; - public static final String ALLELE_FREQUENCY_KEY = "AF"; - public static final String ALLELE_NUMBER_KEY = "AN"; - public static final String RMS_BASE_QUALITY_KEY = "BQ"; - public static final String CIGAR_KEY = "CIGAR"; - public static final String DBSNP_KEY = "DB"; - public static final String DEPTH_KEY = "DP"; - public static final String DOWNSAMPLED_KEY = "DS"; - public static final String EXPECTED_ALLELE_COUNT_KEY = "EC"; - public static final String END_KEY = "END"; - public static final String GENOTYPE_FILTER_KEY = "FT"; - public static final String GENOTYPE_KEY = "GT"; - @Deprecated - public static final String GENOTYPE_LIKELIHOODS_KEY = "GL"; // log10 scaled genotype likelihoods - public static final String GENOTYPE_POSTERIORS_KEY = "GP"; - public static final String GENOTYPE_QUALITY_KEY = "GQ"; - public static final String HAPMAP2_KEY = "H2"; - public static final String HAPMAP3_KEY = "H3"; - public static final String HAPLOTYPE_QUALITY_KEY = "HQ"; - public static final String RMS_MAPPING_QUALITY_KEY = "MQ"; - public static final String MAPPING_QUALITY_ZERO_KEY = "MQ0"; - public static final String SAMPLE_NUMBER_KEY = "NS"; - public static final String PHRED_GENOTYPE_LIKELIHOODS_KEY = "PL"; // phred-scaled genotype likelihoods - public static final String PHASE_QUALITY_KEY = "PQ"; - public static final String PHASE_SET_KEY = "PS"; - public static final String OLD_DEPTH_KEY = "RD"; - public static final String STRAND_BIAS_KEY = "SB"; - public static final String SOMATIC_KEY = "SOMATIC"; - public static final String VALIDATED_KEY = "VALIDATED"; - public static final String THOUSAND_GENOMES_KEY = "1000G"; - - // separators - public static final String FORMAT_FIELD_SEPARATOR = ":"; - public static final String GENOTYPE_FIELD_SEPARATOR = ":"; - public static final char GENOTYPE_FIELD_SEPARATOR_CHAR = ':'; - public static final String FIELD_SEPARATOR = "\t"; - public static final char FIELD_SEPARATOR_CHAR = '\t'; - public static final String FILTER_CODE_SEPARATOR = ";"; - public static final String INFO_FIELD_ARRAY_SEPARATOR = ","; - public static final char INFO_FIELD_ARRAY_SEPARATOR_CHAR = ','; - public static final String ID_FIELD_SEPARATOR = ";"; - public static final String INFO_FIELD_SEPARATOR = ";"; - public static final char INFO_FIELD_SEPARATOR_CHAR = ';'; - public static final String UNPHASED = "/"; - public static final String PHASED = "|"; - public static final String PHASED_SWITCH_PROB_v3 = "\\"; - public static final String PHASING_TOKENS = "/|\\"; - - // old indel alleles - public static final char DELETION_ALLELE_v3 = 'D'; - public static final char INSERTION_ALLELE_v3 = 'I'; - - // missing/default values - public static final String UNFILTERED = "."; - public static final String PASSES_FILTERS_v3 = "0"; - public static final String PASSES_FILTERS_v4 = "PASS"; - public static final String EMPTY_ID_FIELD = "."; - public static final String EMPTY_INFO_FIELD = "."; - public static final String EMPTY_ALTERNATE_ALLELE_FIELD = "."; - public static final String MISSING_VALUE_v4 = "."; - public static final String MISSING_QUALITY_v3 = "-1"; - public static final Double MISSING_QUALITY_v3_DOUBLE = Double.valueOf(MISSING_QUALITY_v3); - - public static final String MISSING_GENOTYPE_QUALITY_v3 = "-1"; - public static final String MISSING_HAPLOTYPE_QUALITY_v3 = "-1"; - public static final String MISSING_DEPTH_v3 = "-1"; - public static final String UNBOUNDED_ENCODING_v4 = "."; - public static final String UNBOUNDED_ENCODING_v3 = "-1"; - public static final String PER_ALLELE_COUNT = "A"; - public static final String PER_GENOTYPE_COUNT = "G"; - public static final String EMPTY_ALLELE = "."; - public static final String EMPTY_GENOTYPE = "./."; - public static final double MAX_GENOTYPE_QUAL = 99.0; - - public static final String DOUBLE_PRECISION_FORMAT_STRING = "%.2f"; - public static final String DOUBLE_PRECISION_INT_SUFFIX = ".00"; - public static final Double VCF_ENCODING_EPSILON = 0.00005; // when we consider fields equal(), used in the Qual compare -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java deleted file mode 100755 index 5e16fbed0..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFilterHeaderLine.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -/** - * @author ebanks - * A class representing a key=value entry for FILTER fields in the VCF header - */ -class VCFFilterHeaderLine extends VCFSimpleHeaderLine { - - /** - * create a VCF filter header line - * - * @param name the name for this header line - * @param description the description for this header line - */ - public VCFFilterHeaderLine(String name, String description) { - super(name, description, SupportedHeaderLineType.FILTER); - } - - /** - * create a VCF info header line - * - * @param line the header line - * @param version the vcf header version - */ - protected VCFFilterHeaderLine(String line, VCFHeaderVersion version) { - super(line, version, SupportedHeaderLineType.FILTER); - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java deleted file mode 100755 index f73c032cc..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFFormatHeaderLine.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - - -/** - * @author ebanks - *

- * Class VCFFormatHeaderLine - *

- * A class representing a key=value entry for genotype FORMAT fields in the VCF header - */ -class VCFFormatHeaderLine extends VCFCompoundHeaderLine { - - public VCFFormatHeaderLine(String name, int count, VCFHeaderLineType type, String description) { - super(name, count, type, description, SupportedHeaderLineType.FORMAT); - if (type == VCFHeaderLineType.Flag) - throw new IllegalArgumentException("Flag is an unsupported type for format fields"); - } - - public VCFFormatHeaderLine(String name, VCFHeaderLineCount count, VCFHeaderLineType type, String description) { - super(name, count, type, description, SupportedHeaderLineType.FORMAT); - } - - protected VCFFormatHeaderLine(String line, VCFHeaderVersion version) { - super(line, version, SupportedHeaderLineType.FORMAT); - } - - // format fields do not allow flag values (that wouldn't make much sense, how would you encode this in the genotype). - @Override - boolean allowFlagValues() { - return false; - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java deleted file mode 100755 index be1b49ec1..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeader.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - - -import org.broad.tribble.util.ParsingUtils; - -import java.util.*; - - -/** - * @author aaron - *

- * Class VCFHeader - *

- * A class representing the VCF header - */ -class VCFHeader { - - // the mandatory header fields - public enum HEADER_FIELDS { - CHROM, POS, ID, REF, ALT, QUAL, FILTER, INFO - } - - // the associated meta data - private final Set mMetaData; - private final Map mInfoMetaData = new HashMap(); - private final Map mFormatMetaData = new HashMap(); - private final Map mOtherMetaData = new HashMap(); - - // the list of auxillary tags - private final Set mGenotypeSampleNames = new LinkedHashSet(); - - // the character string that indicates meta data - public static final String METADATA_INDICATOR = "##"; - - // the header string indicator - public static final String HEADER_INDICATOR = "#"; - - // were the input samples sorted originally (or are we sorting them)? - private boolean samplesWereAlreadySorted = true; - - - /** - * create a VCF header, given a list of meta data and auxillary tags - * - * @param metaData the meta data associated with this header - */ - public VCFHeader(Set metaData) { - mMetaData = new TreeSet(metaData); - loadVCFVersion(); - loadMetaDataMaps(); - } - - /** - * create a VCF header, given a list of meta data and auxillary tags - * - * @param metaData the meta data associated with this header - * @param genotypeSampleNames the sample names - */ - public VCFHeader(Set metaData, Set genotypeSampleNames) { - mMetaData = new TreeSet(); - if ( metaData != null ) - mMetaData.addAll(metaData); - - mGenotypeSampleNames.addAll(genotypeSampleNames); - - loadVCFVersion(); - loadMetaDataMaps(); - - samplesWereAlreadySorted = ParsingUtils.isSorted(genotypeSampleNames); - } - - /** - * Adds a header line to the header metadata. - * - * @param headerLine Line to add to the existing metadata component. - */ - public void addMetaDataLine(VCFHeaderLine headerLine) { - mMetaData.add(headerLine); - } - - /** - * check our metadata for a VCF version tag, and throw an exception if the version is out of date - * or the version is not present - */ - public void loadVCFVersion() { - List toRemove = new ArrayList(); - for ( VCFHeaderLine line : mMetaData ) - if ( VCFHeaderVersion.isFormatString(line.getKey())) { - toRemove.add(line); - } - // remove old header lines for now, - mMetaData.removeAll(toRemove); - - } - - /** - * load the format/info meta data maps (these are used for quick lookup by key name) - */ - private void loadMetaDataMaps() { - for ( VCFHeaderLine line : mMetaData ) { - if ( line instanceof VCFInfoHeaderLine ) { - VCFInfoHeaderLine infoLine = (VCFInfoHeaderLine)line; - mInfoMetaData.put(infoLine.getName(), infoLine); - } - else if ( line instanceof VCFFormatHeaderLine ) { - VCFFormatHeaderLine formatLine = (VCFFormatHeaderLine)line; - mFormatMetaData.put(formatLine.getName(), formatLine); - } - else { - mOtherMetaData.put(line.getKey(), line); - } - } - } - - /** - * get the header fields in order they're presented in the input file (which is now required to be - * the order presented in the spec). - * - * @return a set of the header fields, in order - */ - public Set getHeaderFields() { - Set fields = new LinkedHashSet(); - for (HEADER_FIELDS field : HEADER_FIELDS.values()) - fields.add(field); - return fields; - } - - /** - * get the meta data, associated with this header - * - * @return a set of the meta data - */ - public Set getMetaData() { - Set lines = new LinkedHashSet(); - lines.add(new VCFHeaderLine(VCFHeaderVersion.VCF4_0.getFormatString(), VCFHeaderVersion.VCF4_0.getVersionString())); - lines.addAll(mMetaData); - return Collections.unmodifiableSet(lines); - } - - /** - * get the genotyping sample names - * - * @return a list of the genotype column names, which may be empty if hasGenotypingData() returns false - */ - public Set getGenotypeSamples() { - return mGenotypeSampleNames; - } - - /** - * do we have genotyping data? - * - * @return true if we have genotyping columns, false otherwise - */ - public boolean hasGenotypingData() { - return mGenotypeSampleNames.size() > 0; - } - - /** - * were the input samples sorted originally? - * - * @return true if the input samples were sorted originally, false otherwise - */ - public boolean samplesWereAlreadySorted() { - return samplesWereAlreadySorted; - } - - /** @return the column count */ - public int getColumnCount() { - return HEADER_FIELDS.values().length + (hasGenotypingData() ? mGenotypeSampleNames.size() + 1 : 0); - } - - /** - * @param key the header key name - * @return the meta data line, or null if there is none - */ - public VCFInfoHeaderLine getInfoHeaderLine(String key) { - return mInfoMetaData.get(key); - } - - /** - * @param key the header key name - * @return the meta data line, or null if there is none - */ - public VCFFormatHeaderLine getFormatHeaderLine(String key) { - return mFormatMetaData.get(key); - } - - /** - * @param key the header key name - * @return the meta data line, or null if there is none - */ - public VCFHeaderLine getOtherHeaderLine(String key) { - return mOtherMetaData.get(key); - } -} - - - diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java deleted file mode 100755 index 61b0722bd..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLine.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2010. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR - * THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broad.tribble.TribbleException; - -import java.util.Map; - - -/** - * @author ebanks - *

- * Class VCFHeaderLine - *

- * A class representing a key=value entry in the VCF header - */ -class VCFHeaderLine implements Comparable { - protected static boolean ALLOW_UNBOUND_DESCRIPTIONS = true; - protected static String UNBOUND_DESCRIPTION = "Not provided in original VCF header"; - - private String mKey = null; - private String mValue = null; - - - /** - * create a VCF header line - * - * @param key the key for this header line - * @param value the value for this header line - */ - public VCFHeaderLine(String key, String value) { - if ( key == null ) - throw new IllegalArgumentException("VCFHeaderLine: key cannot be null: key = " + key); - mKey = key; - mValue = value; - } - - /** - * Get the key - * - * @return the key - */ - public String getKey() { - return mKey; - } - - /** - * Get the value - * - * @return the value - */ - public String getValue() { - return mValue; - } - - public String toString() { - return toStringEncoding(); - } - - /** - * Should be overloaded in sub classes to do subclass specific - * - * @return the string encoding - */ - protected String toStringEncoding() { - return mKey + "=" + mValue; - } - - public boolean equals(Object o) { - if ( !(o instanceof VCFHeaderLine) ) - return false; - return mKey.equals(((VCFHeaderLine)o).getKey()) && mValue.equals(((VCFHeaderLine)o).getValue()); - } - - public int compareTo(Object other) { - return toString().compareTo(other.toString()); - } - - /** - * @param line the line - * @return true if the line is a VCF meta data line, or false if it is not - */ - public static boolean isHeaderLine(String line) { - return line != null && line.length() > 0 && VCFHeader.HEADER_INDICATOR.equals(line.substring(0,1)); - } - - /** - * create a string of a mapping pair for the target VCF version - * @param keyValues a mapping of the key->value pairs to output - * @return a string, correctly formatted - */ - public static String toStringEncoding(Map keyValues) { - StringBuilder builder = new StringBuilder(); - builder.append("<"); - boolean start = true; - for (Map.Entry entry : keyValues.entrySet()) { - if (start) start = false; - else builder.append(","); - - if ( entry.getValue() == null ) throw new TribbleException.InternalCodecException("Header problem: unbound value at " + entry + " from " + keyValues); - - builder.append(entry.getKey()); - builder.append("="); - builder.append(entry.getValue().toString().contains(",") || - entry.getValue().toString().contains(" ") || - entry.getKey().equals("Description") ? "\""+ entry.getValue() + "\"" : entry.getValue()); - } - builder.append(">"); - return builder.toString(); - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java deleted file mode 100644 index 8fd29d188..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineCount.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -/** - * the count encodings we use for fields in VCF header lines - */ -public enum VCFHeaderLineCount { - INTEGER, A, G, UNBOUNDED; -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java deleted file mode 100755 index 538b4ff8e..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineTranslator.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import java.util.*; - -/** - * A class for translating between vcf header versions - */ -public class VCFHeaderLineTranslator { - private static Map mapping; - - static { - mapping = new HashMap(); - mapping.put(VCFHeaderVersion.VCF4_0,new VCF4Parser()); - mapping.put(VCFHeaderVersion.VCF4_1,new VCF4Parser()); - mapping.put(VCFHeaderVersion.VCF3_3,new VCF3Parser()); - mapping.put(VCFHeaderVersion.VCF3_2,new VCF3Parser()); - } - - public static Map parseLine(VCFHeaderVersion version, String valueLine, List expectedTagOrder) { - return mapping.get(version).parseLine(valueLine,expectedTagOrder); - } -} - - -interface VCFLineParser { - public Map parseLine(String valueLine, List expectedTagOrder); -} - - -/** - * a class that handles the to and from disk for VCF 4 lines - */ -class VCF4Parser implements VCFLineParser { - Set bracketed = new HashSet(); - - /** - * parse a VCF4 line - * @param valueLine the line - * @return a mapping of the tags parsed out - */ - public Map parseLine(String valueLine, List expectedTagOrder) { - // our return map - Map ret = new LinkedHashMap(); - - // a builder to store up characters as we go - StringBuilder builder = new StringBuilder(); - - // store the key when we're parsing out the values - String key = ""; - - // where are we in the stream of characters? - int index = 0; - - // are we inside a quotation? we don't special case ',' then - boolean inQuote = false; - - // a little switch machine to parse out the tags. Regex ended up being really complicated and ugly [yes, but this machine is getting ugly now... MAD] - for (char c: valueLine.toCharArray()) { - if ( c == '\"' ) { - inQuote = ! inQuote; - } else if ( inQuote ) { - builder.append(c); - } else { - switch (c) { - case ('<') : if (index == 0) break; // if we see a open bracket at the beginning, ignore it - case ('>') : if (index == valueLine.length()-1) ret.put(key,builder.toString().trim()); break; // if we see a close bracket, and we're at the end, add an entry to our list - case ('=') : key = builder.toString().trim(); builder = new StringBuilder(); break; // at an equals, copy the key and reset the builder - case (',') : ret.put(key,builder.toString().trim()); builder = new StringBuilder(); break; // drop the current key value to the return map - default: builder.append(c); // otherwise simply append to the current string - } - } - - index++; - } - - // validate the tags against the expected list - index = 0; - if (ret.size() > expectedTagOrder.size()) throw new IllegalArgumentException("Unexpected tag count " + ret.size() + " in string " + expectedTagOrder.size()); - for (String str : ret.keySet()) { - if (!expectedTagOrder.get(index).equals(str)) throw new IllegalArgumentException("Unexpected tag " + str + " in string " + valueLine); - index++; - } - return ret; - } -} - -class VCF3Parser implements VCFLineParser { - - public Map parseLine(String valueLine, List expectedTagOrder) { - // our return map - Map ret = new LinkedHashMap(); - - // a builder to store up characters as we go - StringBuilder builder = new StringBuilder(); - - // where are we in the stream of characters? - int index = 0; - // where in the expected tag order are we? - int tagIndex = 0; - - // are we inside a quotation? we don't special case ',' then - boolean inQuote = false; - - // a little switch machine to parse out the tags. Regex ended up being really complicated and ugly - for (char c: valueLine.toCharArray()) { - switch (c) { - case ('\"') : inQuote = !inQuote; break; // a quote means we ignore ',' in our strings, keep track of it - case (',') : if (!inQuote) { ret.put(expectedTagOrder.get(tagIndex++),builder.toString()); builder = new StringBuilder(); break; } // drop the current key value to the return map - default: builder.append(c); // otherwise simply append to the current string - } - index++; - } - ret.put(expectedTagOrder.get(tagIndex++),builder.toString()); - - // validate the tags against the expected list - index = 0; - if (tagIndex != expectedTagOrder.size()) throw new IllegalArgumentException("Unexpected tag count " + tagIndex + ", we expected " + expectedTagOrder.size()); - for (String str : ret.keySet()){ - if (!expectedTagOrder.get(index).equals(str)) throw new IllegalArgumentException("Unexpected tag " + str + " in string " + valueLine); - index++; - } - return ret; - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java deleted file mode 100755 index 65cf1a327..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderLineType.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -/** - * the type encodings we use for fields in VCF header lines - */ -enum VCFHeaderLineType { - Integer, Float, String, Character, Flag; -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java deleted file mode 100755 index 21e737abe..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFHeaderVersion.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broad.tribble.TribbleException; - -/** - * information that identifies each header version - */ -enum VCFHeaderVersion { - VCF3_2("VCRv3.2","format"), - VCF3_3("VCFv3.3","fileformat"), - VCF4_0("VCFv4.0","fileformat"), - VCF4_1("VCFv4.1","fileformat"); - - private final String versionString; - private final String formatString; - - /** - * create the enum, privately, using: - * @param vString the version string - * @param fString the format string - */ - VCFHeaderVersion(String vString, String fString) { - this.versionString = vString; - this.formatString = fString; - } - - /** - * get the header version - * @param version the version string - * @return a VCFHeaderVersion object - */ - public static VCFHeaderVersion toHeaderVersion(String version) { - version = clean(version); - for (VCFHeaderVersion hv : VCFHeaderVersion.values()) - if (hv.versionString.equals(version)) - return hv; - return null; - } - - /** - * are we a valid version string of some type - * @param version the version string - * @return true if we're valid of some type, false otherwise - */ - public static boolean isVersionString(String version){ - return toHeaderVersion(version) != null; - } - - /** - * are we a valid format string for some type - * @param format the format string - * @return true if we're valid of some type, false otherwise - */ - public static boolean isFormatString(String format){ - format = clean(format); - for (VCFHeaderVersion hv : VCFHeaderVersion.values()) - if (hv.formatString.equals(format)) - return true; - return false; - } - - public static VCFHeaderVersion getHeaderVersion(String versionLine) { - String[] lineFields = versionLine.split("="); - if ( lineFields.length != 2 || !isFormatString(lineFields[0].substring(2)) ) - throw new TribbleException.InvalidHeader(versionLine + " is not a valid VCF version line"); - - if ( !isVersionString(lineFields[1]) ) - throw new TribbleException.InvalidHeader(lineFields[1] + " is not a supported version"); - - return toHeaderVersion(lineFields[1]); - } - - /** - * Utility function to clean up a VCF header string - * - * @param s string - * @return trimmed version of s - */ - private static String clean(String s) { - return s.trim(); - } - - - public String getVersionString() { - return versionString; - } - - public String getFormatString() { - return formatString; - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java deleted file mode 100755 index 642a78b76..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFInfoHeaderLine.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - - -/** - * @author ebanks - *

- * Class VCFInfoHeaderLine - *

- * A class representing a key=value entry for INFO fields in the VCF header - */ -class VCFInfoHeaderLine extends VCFCompoundHeaderLine { - public VCFInfoHeaderLine(String name, int count, VCFHeaderLineType type, String description) { - super(name, count, type, description, SupportedHeaderLineType.INFO); - } - - public VCFInfoHeaderLine(String name, VCFHeaderLineCount count, VCFHeaderLineType type, String description) { - super(name, count, type, description, SupportedHeaderLineType.INFO); - } - - protected VCFInfoHeaderLine(String line, VCFHeaderVersion version) { - super(line, version, SupportedHeaderLineType.INFO); - } - - // info fields allow flag values - @Override - boolean allowFlagValues() { - return true; - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java deleted file mode 100755 index b3ce5d841..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFNamedHeaderLine.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -/** an interface for named header lines **/ -interface VCFNamedHeaderLine { - String getName(); -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java deleted file mode 100755 index 95a3f7bf1..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFParser.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import java.util.List; -import java.util.Map; - - -/** - * All VCF codecs need to implement this interface so that we can perform lazy loading. - */ -interface VCFParser { - - /** - * create a genotype map - * @param str the string - * @param alleles the list of alleles - * @param chr chrom - * @param pos position - * @return a mapping of sample name to genotype object - */ - public Map createGenotypeMap(String str, List alleles, String chr, int pos); - -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java deleted file mode 100644 index 17706c705..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFSimpleHeaderLine.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; - - -/** - * @author ebanks - * A class representing a key=value entry for simple VCF header types - */ -abstract class VCFSimpleHeaderLine extends VCFHeaderLine implements VCFNamedHeaderLine { - - public enum SupportedHeaderLineType { - FILTER, ALT; - } - - private String name; - private String description; - - // our type of line, i.e. filter, alt, etc - private final SupportedHeaderLineType lineType; - - - /** - * create a VCF filter header line - * - * @param name the name for this header line - * @param description the description for this header line - * @param lineType the header line type - */ - public VCFSimpleHeaderLine(String name, String description, SupportedHeaderLineType lineType) { - super(lineType.toString(), ""); - this.lineType = lineType; - this.name = name; - this.description = description; - - if ( name == null || description == null ) - throw new IllegalArgumentException(String.format("Invalid VCFSimpleHeaderLine: key=%s name=%s desc=%s", super.getKey(), name, description )); - } - - /** - * create a VCF info header line - * - * @param line the header line - * @param version the vcf header version - * @param lineType the header line type - */ - protected VCFSimpleHeaderLine(String line, VCFHeaderVersion version, SupportedHeaderLineType lineType) { - super(lineType.toString(), ""); - this.lineType = lineType; - Map mapping = VCFHeaderLineTranslator.parseLine(version,line, Arrays.asList("ID","Description")); - name = mapping.get("ID"); - description = mapping.get("Description"); - if ( description == null && ALLOW_UNBOUND_DESCRIPTIONS ) // handle the case where there's no description provided - description = UNBOUND_DESCRIPTION; - } - - protected String toStringEncoding() { - Map map = new LinkedHashMap(); - map.put("ID", name); - map.put("Description", description); - return lineType.toString() + "=" + toStringEncoding(map); - } - - public boolean equals(Object o) { - if ( !(o instanceof VCFSimpleHeaderLine) ) - return false; - VCFSimpleHeaderLine other = (VCFSimpleHeaderLine)o; - return name.equals(other.name) && - description.equals(other.description); - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java deleted file mode 100755 index dc78d40ac..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFUtils.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (c) 2010 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR - * THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.apache.log4j.Logger; -import org.broad.tribble.Feature; -import org.broadinstitute.sting.commandline.RodBinding; -import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; -import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; - -import java.util.*; - -/** - * A set of static utility methods for common operations on VCF files/records. - */ -class VCFUtils { - /** - * Constructor access disallowed...static utility methods only! - */ - private VCFUtils() { } - - public static Map getVCFHeadersFromRods(GenomeAnalysisEngine toolkit, List> rodBindings) { - // Collect the eval rod names - final Set names = new TreeSet(); - for ( final RodBinding evalRod : rodBindings ) - names.add(evalRod.getName()); - return getVCFHeadersFromRods(toolkit, names); - } - - public static Map getVCFHeadersFromRods(GenomeAnalysisEngine toolkit) { - return getVCFHeadersFromRods(toolkit, (Collection)null); - } - - public static Map getVCFHeadersFromRods(GenomeAnalysisEngine toolkit, Collection rodNames) { - Map data = new HashMap(); - - // iterate to get all of the sample names - List dataSources = toolkit.getRodDataSources(); - for ( ReferenceOrderedDataSource source : dataSources ) { - // ignore the rod if it's not in our list - if ( rodNames != null && !rodNames.contains(source.getName()) ) - continue; - - if ( source.getHeader() != null && source.getHeader() instanceof VCFHeader ) - data.put(source.getName(), (VCFHeader)source.getHeader()); - } - - return data; - } - - public static Map getVCFHeadersFromRodPrefix(GenomeAnalysisEngine toolkit,String prefix) { - Map data = new HashMap(); - - // iterate to get all of the sample names - List dataSources = toolkit.getRodDataSources(); - for ( ReferenceOrderedDataSource source : dataSources ) { - // ignore the rod if lacks the prefix - if ( ! source.getName().startsWith(prefix) ) - continue; - - if ( source.getHeader() != null && source.getHeader() instanceof VCFHeader ) - data.put(source.getName(), (VCFHeader)source.getHeader()); - } - - return data; - } - - /** - * Gets the header fields from all VCF rods input by the user - * - * @param toolkit GATK engine - * - * @return a set of all fields - */ - public static Set getHeaderFields(GenomeAnalysisEngine toolkit) { - return getHeaderFields(toolkit, null); - } - - /** - * Gets the header fields from all VCF rods input by the user - * - * @param toolkit GATK engine - * @param rodNames names of rods to use, or null if we should use all possible ones - * - * @return a set of all fields - */ - public static Set getHeaderFields(GenomeAnalysisEngine toolkit, Collection rodNames) { - - // keep a map of sample name to occurrences encountered - TreeSet fields = new TreeSet(); - - // iterate to get all of the sample names - List dataSources = toolkit.getRodDataSources(); - for ( ReferenceOrderedDataSource source : dataSources ) { - // ignore the rod if it's not in our list - if ( rodNames != null && !rodNames.contains(source.getName()) ) - continue; - - if ( source.getRecordType().equals(VariantContext.class)) { - VCFHeader header = (VCFHeader)source.getHeader(); - if ( header != null ) - fields.addAll(header.getMetaData()); - } - } - - return fields; - } - - /** Only displays a warning if a logger is provided and an identical warning hasn't been already issued */ - private static final class HeaderConflictWarner { - Logger logger; - Set alreadyIssued = new HashSet(); - - private HeaderConflictWarner(final Logger logger) { - this.logger = logger; - } - - public void warn(final VCFHeaderLine line, final String msg) { - if ( logger != null && ! alreadyIssued.contains(line.getKey()) ) { - alreadyIssued.add(line.getKey()); - logger.warn(msg); - } - } - } - - public static Set smartMergeHeaders(Collection headers, Logger logger) throws IllegalStateException { - HashMap map = new HashMap(); // from KEY.NAME -> line - HeaderConflictWarner conflictWarner = new HeaderConflictWarner(logger); - - // todo -- needs to remove all version headers from sources and add its own VCF version line - for ( VCFHeader source : headers ) { - //System.out.printf("Merging in header %s%n", source); - for ( VCFHeaderLine line : source.getMetaData()) { - String key = line.getKey(); - - if ( line instanceof VCFNamedHeaderLine) - key = key + "" + ((VCFNamedHeaderLine) line).getName(); - - if ( map.containsKey(key) ) { - VCFHeaderLine other = map.get(key); - if ( line.equals(other) ) - continue; - else if ( ! line.getClass().equals(other.getClass()) ) - throw new IllegalStateException("Incompatible header types: " + line + " " + other ); - else if ( line instanceof VCFFilterHeaderLine) { - String lineName = ((VCFFilterHeaderLine) line).getName(); String otherName = ((VCFFilterHeaderLine) other).getName(); - if ( ! lineName.equals(otherName) ) - throw new IllegalStateException("Incompatible header types: " + line + " " + other ); - } else if ( line instanceof VCFCompoundHeaderLine ) { - VCFCompoundHeaderLine compLine = (VCFCompoundHeaderLine)line; - VCFCompoundHeaderLine compOther = (VCFCompoundHeaderLine)other; - - // if the names are the same, but the values are different, we need to quit - if (! (compLine).equalsExcludingDescription(compOther) ) { - if ( compLine.getType().equals(compOther.getType()) ) { - // The Number entry is an Integer that describes the number of values that can be - // included with the INFO field. For example, if the INFO field contains a single - // number, then this value should be 1. However, if the INFO field describes a pair - // of numbers, then this value should be 2 and so on. If the number of possible - // values varies, is unknown, or is unbounded, then this value should be '.'. - conflictWarner.warn(line, "Promoting header field Number to . due to number differences in header lines: " + line + " " + other); - compOther.setNumberToUnbounded(); - } else if ( compLine.getType() == VCFHeaderLineType.Integer && compOther.getType() == VCFHeaderLineType.Float ) { - // promote key to Float - conflictWarner.warn(line, "Promoting Integer to Float in header: " + compOther); - map.put(key, compOther); - } else if ( compLine.getType() == VCFHeaderLineType.Float && compOther.getType() == VCFHeaderLineType.Integer ) { - // promote key to Float - conflictWarner.warn(line, "Promoting Integer to Float in header: " + compOther); - } else { - throw new IllegalStateException("Incompatible header types, collision between these two types: " + line + " " + other ); - } - } - if ( ! compLine.getDescription().equals(compOther) ) - conflictWarner.warn(line, "Allowing unequal description fields through: keeping " + compOther + " excluding " + compLine); - } else { - // we are not equal, but we're not anything special either - conflictWarner.warn(line, "Ignoring header line already in map: this header line = " + line + " already present header = " + other); - } - } else { - map.put(key, line); - //System.out.printf("Adding header line %s%n", line); - } - } - } - - return new HashSet(map.values()); - } - - public static String rsIDOfFirstRealVariant(List VCs, VariantContext.Type type) { - if ( VCs == null ) - return null; - - String rsID = null; - for ( VariantContext vc : VCs ) { - if ( vc.getType() == type ) { - rsID = vc.getID(); - break; - } - } - - return rsID; - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java deleted file mode 100755 index 15bdb5046..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VCFWriter.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -/** - * this class writes VCF files - */ -public interface VCFWriter { - - public void writeHeader(VCFHeader header); - - /** - * attempt to close the VCF file - */ - public void close(); - - public void add(VariantContext vc); -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java deleted file mode 100755 index 3a193a00a..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContext.java +++ /dev/null @@ -1,1615 +0,0 @@ -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.broad.tribble.Feature; -import org.broad.tribble.TribbleException; -import org.broad.tribble.util.ParsingUtils; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -import java.util.*; - -/** - * Class VariantContext - * - * == High-level overview == - * - * The VariantContext object is a single general class system for representing genetic variation data composed of: - * - * * Allele: representing single genetic haplotypes (A, T, ATC, -) - * * Genotype: an assignment of alleles for each chromosome of a single named sample at a particular locus - * * VariantContext: an abstract class holding all segregating alleles at a locus as well as genotypes - * for multiple individuals containing alleles at that locus - * - * The class system works by defining segregating alleles, creating a variant context representing the segregating - * information at a locus, and potentially creating and associating genotypes with individuals in the context. - * - * All of the classes are highly validating -- call validate() if you modify them -- so you can rely on the - * self-consistency of the data once you have a VariantContext in hand. The system has a rich set of assessor - * and manipulator routines, as well as more complex static support routines in VariantContextUtils. - * - * The VariantContext (and Genotype) objects are attributed (supporting addition of arbitrary key/value pairs) and - * filtered (can represent a variation that is viewed as suspect). - * - * VariantContexts are dynamically typed, so whether a VariantContext is a SNP, Indel, or NoVariant depends - * on the properties of the alleles in the context. See the detailed documentation on the Type parameter below. - * - * It's also easy to create subcontexts based on selected genotypes. - * - * == Working with Variant Contexts == - * By default, VariantContexts are immutable. In order to access (in the rare circumstances where you need them) - * setter routines, you need to create MutableVariantContexts and MutableGenotypes. - * - * === Some example data === - * - * Allele A, Aref, T, Tref; - * Allele del, delRef, ATC, ATCref; - * - * A [ref] / T at 10 - * GenomeLoc snpLoc = GenomeLocParser.createGenomeLoc("chr1", 10, 10); - * - * - / ATC [ref] from 20-23 - * GenomeLoc delLoc = GenomeLocParser.createGenomeLoc("chr1", 20, 22); - * - * // - [ref] / ATC immediately after 20 - * GenomeLoc insLoc = GenomeLocParser.createGenomeLoc("chr1", 20, 20); - * - * === Alleles === - * - * See the documentation in the Allele class itself - * - * What are they? - * - * Alleles can be either reference or non-reference - * - * Example alleles used here: - * - * del = new Allele("-"); - * A = new Allele("A"); - * Aref = new Allele("A", true); - * T = new Allele("T"); - * ATC = new Allele("ATC"); - * - * === Creating variant contexts === - * - * ==== By hand ==== - * - * Here's an example of a A/T polymorphism with the A being reference: - * - *

- * VariantContext vc = new VariantContext(name, snpLoc, Arrays.asList(Aref, T));
- * 
- * - * If you want to create a non-variant site, just put in a single reference allele - * - *
- * VariantContext vc = new VariantContext(name, snpLoc, Arrays.asList(Aref));
- * 
- * - * A deletion is just as easy: - * - *
- * VariantContext vc = new VariantContext(name, delLoc, Arrays.asList(ATCref, del));
- * 
- * - * The only 2 things that distinguishes between a insertion and deletion are the reference allele - * and the location of the variation. An insertion has a Null reference allele and at least - * one non-reference Non-Null allele. Additionally, the location of the insertion is immediately after - * a 1-bp GenomeLoc (at say 20). - * - *
- * VariantContext vc = new VariantContext("name", insLoc, Arrays.asList(delRef, ATC));
- * 
- * - * ==== Converting rods and other data structures to VCs ==== - * - * You can convert many common types into VariantContexts using the general function: - * - *
- * VariantContextAdaptors.convertToVariantContext(name, myObject)
- * 
- * - * dbSNP and VCFs, for example, can be passed in as myObject and a VariantContext corresponding to that - * object will be returned. A null return type indicates that the type isn't yet supported. This is the best - * and easiest way to create contexts using RODs. - * - * - * === Working with genotypes === - * - *
- * List alleles = Arrays.asList(Aref, T);
- * Genotype g1 = new Genotype(Arrays.asList(Aref, Aref), "g1", 10);
- * Genotype g2 = new Genotype(Arrays.asList(Aref, T), "g2", 10);
- * Genotype g3 = new Genotype(Arrays.asList(T, T), "g3", 10);
- * VariantContext vc = new VariantContext(snpLoc, alleles, Arrays.asList(g1, g2, g3));
- * 
- * - * At this point we have 3 genotypes in our context, g1-g3. - * - * You can assess a good deal of information about the genotypes through the VariantContext: - * - *
- * vc.hasGenotypes()
- * vc.isMonomorphic()
- * vc.isPolymorphic()
- * vc.getSamples().size()
- *
- * vc.getGenotypes()
- * vc.getGenotypes().get("g1")
- * vc.hasGenotype("g1")
- *
- * vc.getChromosomeCount()
- * vc.getChromosomeCount(Aref)
- * vc.getChromosomeCount(T)
- * 
- * - * === NO_CALL alleles === - * - * The system allows one to create Genotypes carrying special NO_CALL alleles that aren't present in the - * set of context alleles and that represent undetermined alleles in a genotype: - * - * Genotype g4 = new Genotype(Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), "NO_DATA_FOR_SAMPLE", 10); - * - * - * === subcontexts === - * It's also very easy get subcontext based only the data in a subset of the genotypes: - * - *
- * VariantContext vc12 = vc.subContextFromGenotypes(Arrays.asList(g1,g2));
- * VariantContext vc1 = vc.subContextFromGenotypes(Arrays.asList(g1));
- * 
- * - * @author depristo - */ -public class VariantContext implements Feature { // to enable tribble intergration - protected InferredGeneticContext commonInfo = null; - public final static double NO_NEG_LOG_10PERROR = InferredGeneticContext.NO_NEG_LOG_10PERROR; - public final static String UNPARSED_GENOTYPE_MAP_KEY = "_UNPARSED_GENOTYPE_MAP_"; - public final static String UNPARSED_GENOTYPE_PARSER_KEY = "_UNPARSED_GENOTYPE_PARSER_"; - public final static String ID_KEY = "ID"; - - private final Byte REFERENCE_BASE_FOR_INDEL; - - public final static Set PASSES_FILTERS = Collections.unmodifiableSet(new LinkedHashSet()); - - /** The location of this VariantContext */ - protected String contig; - protected long start; - protected long stop; - - /** The type (cached for performance reasons) of this context */ - protected Type type = null; - - /** A set of the alleles segregating in this context */ - final protected List alleles; - - /** A mapping from sampleName -> genotype objects for all genotypes associated with this context */ - protected Map genotypes = null; - - /** Counts for each of the possible Genotype types in this context */ - protected int[] genotypeCounts = null; - - public final static Map NO_GENOTYPES = Collections.unmodifiableMap(new HashMap()); - - // a fast cached access point to the ref / alt alleles for biallelic case - private Allele REF = null; - - // set to the alt allele when biallelic, otherwise == null - private Allele ALT = null; - - // were filters applied? - private boolean filtersWereAppliedToContext; - - // --------------------------------------------------------------------------------------------------------- - // - // constructors - // - // --------------------------------------------------------------------------------------------------------- - - - /** - * the complete constructor. Makes a complete VariantContext from its arguments - * This is the only constructor that is able to create indels! DO NOT USE THE OTHER ONES. - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes map - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @param referenceBaseForIndel padded reference base - */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); - } - - /** - * the complete constructor. Makes a complete VariantContext from its arguments - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes map - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Map genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); - } - - /** - * Makes a VariantContext from its arguments without parsing the genotypes. - * Note that this constructor assumes that if there is genotype data, then it's been put into - * the attributes with the UNPARSED_GENOTYPE_MAP_KEY and that the codec has been added with the - * UNPARSED_GENOTYPE_PARSER_KEY. It doesn't validate that this is the case because it's possible - * that there is no genotype data. - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @param referenceBaseForIndel padded reference base - */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true, true); - } - - /** - * Create a new VariantContext - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes set - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes, null, false, true); - } - - /** - * Create a new variant context without genotypes and no Perror, no filters, and no attributes - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles) { - this(source, contig, start, stop, alleles, NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null, null, false, true); - } - - /** - * Create a new variant context with genotypes but without Perror, filters, and attributes - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes - */ - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { - this(source, contig, start, stop, alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - /** - * Copy constructor - * - * @param other the VariantContext to copy - */ - public VariantContext(VariantContext other) { - this(other.getSource(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, true); - } - - /** - * the actual constructor. Private access only - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes map - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @param referenceBaseForIndel padded reference base - * @param genotypesAreUnparsed true if the genotypes have not yet been parsed - * @param performValidation if true, call validate() as the final step in construction - */ - private VariantContext(String source, String contig, long start, long stop, - Collection alleles, Map genotypes, - double negLog10PError, Set filters, Map attributes, - Byte referenceBaseForIndel, boolean genotypesAreUnparsed, - boolean performValidation ) { - if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } - this.contig = contig; - this.start = start; - this.stop = stop; - - if ( !genotypesAreUnparsed && attributes != null ) { - if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) - attributes.remove(UNPARSED_GENOTYPE_MAP_KEY); - if ( attributes.containsKey(UNPARSED_GENOTYPE_PARSER_KEY) ) - attributes.remove(UNPARSED_GENOTYPE_PARSER_KEY); - } - - this.commonInfo = new InferredGeneticContext(source, negLog10PError, filters, attributes); - filtersWereAppliedToContext = filters != null; - REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; - - if ( alleles == null ) { throw new IllegalArgumentException("Alleles cannot be null"); } - - // we need to make this a LinkedHashSet in case the user prefers a given ordering of alleles - this.alleles = makeAlleles(alleles); - - - if ( genotypes == null ) { genotypes = NO_GENOTYPES; } - this.genotypes = Collections.unmodifiableMap(genotypes); - - // cache the REF and ALT alleles - int nAlleles = alleles.size(); - for ( Allele a : alleles ) { - if ( a.isReference() ) { - REF = a; - } else if ( nAlleles == 2 ) { // only cache ALT when biallelic - ALT = a; - } - } - - if ( performValidation ) { - validate(); - } - } - - // --------------------------------------------------------------------------------------------------------- - // - // Partial-cloning routines (because Variant Context is immutable). - // - // IMPORTANT: These routines assume that the VariantContext on which they're called is already valid. - // Due to this assumption, they explicitly tell the constructor NOT to perform validation by - // calling validate(), and instead perform validation only on the data that's changed. - // - // Note that we don't call vc.getGenotypes() because that triggers the lazy loading. - // Also note that we need to create a new attributes map because it's unmodifiable and the constructor may try to modify it. - // - // --------------------------------------------------------------------------------------------------------- - - public static VariantContext modifyGenotypes(VariantContext vc, Map genotypes) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), false, false); - modifiedVC.validateGenotypes(); - return modifiedVC; - } - - public static VariantContext modifyLocation(VariantContext vc, String chr, int start, int end) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); - - // Since start and end have changed, we need to call both validateAlleles() and validateReferencePadding(), - // since those validation routines rely on the values of start and end: - modifiedVC.validateAlleles(); - modifiedVC.validateReferencePadding(); - - return modifiedVC; - } - - public static VariantContext modifyFilters(VariantContext vc, Set filters) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); - } - - public static VariantContext modifyAttributes(VariantContext vc, Map attributes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); - } - - public static VariantContext modifyReferencePadding(VariantContext vc, Byte b) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true, false); - modifiedVC.validateReferencePadding(); - return modifiedVC; - } - - public static VariantContext modifyPErrorFiltersAndAttributes(VariantContext vc, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true, false); - } - - // --------------------------------------------------------------------------------------------------------- - // - // Selectors - // - // --------------------------------------------------------------------------------------------------------- - - /** - * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotype - * genotype and alleles in genotype. This is the right way to test if a single genotype is actually - * variant or not. - * - * @param genotype genotype - * @return vc subcontext - */ - public VariantContext subContextFromGenotypes(Genotype genotype) { - return subContextFromGenotypes(Arrays.asList(genotype)); - } - - - /** - * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes - * genotypes and alleles in these genotypes. This is the right way to test if a single genotype is actually - * variant or not. - * - * @param genotypes genotypes - * @return vc subcontext - */ - public VariantContext subContextFromGenotypes(Collection genotypes) { - return subContextFromGenotypes(genotypes, allelesOfGenotypes(genotypes)) ; - } - - /** - * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes - * genotypes. Also, the resulting variant context will contain the alleles provided, not only those found in genotypes - * - * @param genotypes genotypes - * @param alleles the set of allele segregating alleles at this site. Must include those in genotypes, but may be more - * @return vc subcontext - */ - public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { - return new VariantContext(getSource(), contig, start, stop, alleles, genotypes != null ? genotypeCollectionToMap(new TreeMap(), genotypes) : null, getNegLog10PError(), filtersWereApplied() ? getFilters() : null, getAttributes(), getReferenceBaseForIndel()); - } - - - /** - * helper routine for subcontext - * @param genotypes genotypes - * @return allele set - */ - private Set allelesOfGenotypes(Collection genotypes) { - Set alleles = new HashSet(); - - boolean addedref = false; - for ( Genotype g : genotypes ) { - for ( Allele a : g.getAlleles() ) { - addedref = addedref || a.isReference(); - if ( a.isCalled() ) - alleles.add(a); - } - } - if ( ! addedref ) alleles.add(getReference()); - - return alleles; - } - - // --------------------------------------------------------------------------------------------------------- - // - // type operations - // - // --------------------------------------------------------------------------------------------------------- - - /** - * see: http://www.ncbi.nlm.nih.gov/bookshelf/br.fcgi?book=handbook&part=ch5&rendertype=table&id=ch5.ch5_t3 - * - * Format: - * dbSNP variation class - * Rules for assigning allele classes - * Sample allele definition - * - * Single Nucleotide Polymorphisms (SNPs)a - * Strictly defined as single base substitutions involving A, T, C, or G. - * A/T - * - * Deletion/Insertion Polymorphisms (DIPs) - * Designated using the full sequence of the insertion as one allele, and either a fully - * defined string for the variant allele or a '-' character to specify the deleted allele. - * This class will be assigned to a variation if the variation alleles are of different lengths or - * if one of the alleles is deleted ('-'). - * T/-/CCTA/G - * - * No-variation - * Reports may be submitted for segments of sequence that are assayed and determined to be invariant - * in the sample. - * (NoVariation) - * - * Mixed - * Mix of other classes - * - * Also supports NO_VARIATION type, used to indicate that the site isn't polymorphic in the population - * - * - * Not currently supported: - * - * Heterozygous sequencea - * The term heterozygous is used to specify a region detected by certain methods that do not - * resolve the polymorphism into a specific sequence motif. In these cases, a unique flanking - * sequence must be provided to define a sequence context for the variation. - * (heterozygous) - * - * Microsatellite or short tandem repeat (STR) - * Alleles are designated by providing the repeat motif and the copy number for each allele. - * Expansion of the allele repeat motif designated in dbSNP into full-length sequence will - * be only an approximation of the true genomic sequence because many microsatellite markers are - * not fully sequenced and are resolved as size variants only. - * (CAC)8/9/10/11 - * - * Named variant - * Applies to insertion/deletion polymorphisms of longer sequence features, such as retroposon - * dimorphism for Alu or line elements. These variations frequently include a deletion '-' indicator - * for the absent allele. - * (alu) / - - * - * Multi-Nucleotide Polymorphism (MNP) - * Assigned to variations that are multi-base variations of a single, common length - * GGA/AGT - */ - public enum Type { - NO_VARIATION, - SNP, - MNP, // a multi-nucleotide polymorphism - INDEL, - SYMBOLIC, - MIXED, - } - - /** - * Determines (if necessary) and returns the type of this variation by examining the alleles it contains. - * - * @return the type of this VariantContext - **/ - public Type getType() { - if ( type == null ) - determineType(); - - return type; - } - - /** - * convenience method for SNPs - * - * @return true if this is a SNP, false otherwise - */ - public boolean isSNP() { return getType() == Type.SNP; } - - - /** - * convenience method for variants - * - * @return true if this is a variant allele, false if it's reference - */ - public boolean isVariant() { return getType() != Type.NO_VARIATION; } - - /** - * convenience method for point events - * - * @return true if this is a SNP or ref site, false if it's an indel or mixed event - */ - public boolean isPointEvent() { return isSNP() || !isVariant(); } - - /** - * convenience method for indels - * - * @return true if this is an indel, false otherwise - */ - public boolean isIndel() { return getType() == Type.INDEL; } - - /** - * @return true if the alleles indicate a simple insertion (i.e., the reference allele is Null) - */ - public boolean isSimpleInsertion() { - // can't just call !isSimpleDeletion() because of complex indels - return getType() == Type.INDEL && getReference().isNull() && isBiallelic(); - } - - /** - * @return true if the alleles indicate a simple deletion (i.e., a single alt allele that is Null) - */ - public boolean isSimpleDeletion() { - // can't just call !isSimpleInsertion() because of complex indels - return getType() == Type.INDEL && getAlternateAllele(0).isNull() && isBiallelic(); - } - - /** - * @return true if the alleles indicate neither a simple deletion nor a simple insertion - */ - public boolean isComplexIndel() { - return isIndel() && !isSimpleDeletion() && !isSimpleInsertion(); - } - - public boolean isSymbolic() { - return getType() == Type.SYMBOLIC; - } - - public boolean isMNP() { - return getType() == Type.MNP; - } - - /** - * convenience method for indels - * - * @return true if this is an mixed variation, false otherwise - */ - public boolean isMixed() { return getType() == Type.MIXED; } - - - // --------------------------------------------------------------------------------------------------------- - // - // Generic accessors - // - // --------------------------------------------------------------------------------------------------------- - - public boolean hasID() { - return commonInfo.hasAttribute(ID_KEY); - } - - public String getID() { - return (String)commonInfo.getAttribute(ID_KEY); - } - - public boolean hasReferenceBaseForIndel() { - return REFERENCE_BASE_FOR_INDEL != null; - } - - // the indel base that gets stripped off for indels - public Byte getReferenceBaseForIndel() { - return REFERENCE_BASE_FOR_INDEL; - } - - // --------------------------------------------------------------------------------------------------------- - // - // get routines to access context info fields - // - // --------------------------------------------------------------------------------------------------------- - public String getSource() { return commonInfo.getName(); } - public Set getFilters() { return commonInfo.getFilters(); } - public boolean isFiltered() { return commonInfo.isFiltered(); } - public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } - public boolean filtersWereApplied() { return filtersWereAppliedToContext; } - public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } - public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } - public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } - - public Map getAttributes() { return commonInfo.getAttributes(); } - public boolean hasAttribute(String key) { return commonInfo.hasAttribute(key); } - public Object getAttribute(String key) { return commonInfo.getAttribute(key); } - - public Object getAttribute(String key, Object defaultValue) { - return commonInfo.getAttribute(key, defaultValue); - } - - public String getAttributeAsString(String key, String defaultValue) { return commonInfo.getAttributeAsString(key, defaultValue); } - public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } - public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } - public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } - - // --------------------------------------------------------------------------------------------------------- - // - // Working with alleles - // - // --------------------------------------------------------------------------------------------------------- - - /** - * @return the reference allele for this context - */ - public Allele getReference() { - Allele ref = REF; - if ( ref == null ) - throw new IllegalStateException("BUG: no reference allele found at " + this); - return ref; - } - - - /** - * @return true if the context is strictly bi-allelic - */ - public boolean isBiallelic() { - return getNAlleles() == 2; - } - - /** - * @return The number of segregating alleles in this context - */ - public int getNAlleles() { - return alleles.size(); - } - - /** - * @return The allele sharing the same bases as this String. A convenience method; better to use byte[] - */ - public Allele getAllele(String allele) { - return getAllele(allele.getBytes()); - } - - /** - * @return The allele sharing the same bases as this byte[], or null if no such allele is present. - */ - public Allele getAllele(byte[] allele) { - return Allele.getMatchingAllele(getAlleles(), allele); - } - - /** - * @return True if this context contains Allele allele, or false otherwise - */ - public boolean hasAllele(Allele allele) { - return hasAllele(allele, false); - } - - public boolean hasAllele(Allele allele, boolean ignoreRefState) { - if ( allele == REF || allele == ALT ) // optimization for cached cases - return true; - - for ( Allele a : getAlleles() ) { - if ( a.equals(allele, ignoreRefState) ) - return true; - } - - return false; - } - - - /** - * Gets the alleles. This method should return all of the alleles present at the location, - * including the reference allele. There are no constraints imposed on the ordering of alleles - * in the set. If the reference is not an allele in this context it will not be included. - * - * @return the set of alleles - */ - public List getAlleles() { return alleles; } - - /** - * Gets the alternate alleles. This method should return all the alleles present at the location, - * NOT including the reference allele. There are no constraints imposed on the ordering of alleles - * in the set. - * - * @return the set of alternate alleles - */ - public List getAlternateAlleles() { - return alleles.subList(1, alleles.size()); - } - - /** - * Gets the sizes of the alternate alleles if they are insertion/deletion events, and returns a list of their sizes - * - * @return a list of indel lengths ( null if not of type indel or mixed ) - */ - public List getIndelLengths() { - if ( getType() != Type.INDEL && getType() != Type.MIXED ) { - return null; - } - - List lengths = new ArrayList(); - for ( Allele a : getAlternateAlleles() ) { - lengths.add(a.length() - getReference().length()); - } - - return lengths; - } - - /** - * @param i -- the ith allele (from 0 to n - 2 for a context with n alleles including a reference allele) - * @return the ith non-reference allele in this context - * @throws IllegalArgumentException if i is invalid - */ - public Allele getAlternateAllele(int i) { - return alleles.get(i+1); - } - - /** - * @param other VariantContext whose alternate alleles to compare against - * @return true if this VariantContext has the same alternate alleles as other, - * regardless of ordering. Otherwise returns false. - */ - public boolean hasSameAlternateAllelesAs ( VariantContext other ) { - List thisAlternateAlleles = getAlternateAlleles(); - List otherAlternateAlleles = other.getAlternateAlleles(); - - if ( thisAlternateAlleles.size() != otherAlternateAlleles.size() ) { - return false; - } - - for ( Allele allele : thisAlternateAlleles ) { - if ( ! otherAlternateAlleles.contains(allele) ) { - return false; - } - } - - return true; - } - - // --------------------------------------------------------------------------------------------------------- - // - // Working with genotypes - // - // --------------------------------------------------------------------------------------------------------- - - private void loadGenotypes() { - if ( !hasAttribute(UNPARSED_GENOTYPE_MAP_KEY) ) { - if ( genotypes == null ) - genotypes = NO_GENOTYPES; - return; - } - - Object parserObj = getAttribute(UNPARSED_GENOTYPE_PARSER_KEY); - if ( parserObj == null || !(parserObj instanceof VCFParser) ) - throw new IllegalStateException("There is no VCF parser stored to unparse the genotype data"); - VCFParser parser = (VCFParser)parserObj; - - Object mapObj = getAttribute(UNPARSED_GENOTYPE_MAP_KEY); - if ( mapObj == null ) - throw new IllegalStateException("There is no mapping string stored to unparse the genotype data"); - - genotypes = parser.createGenotypeMap(mapObj.toString(), new ArrayList(alleles), getChr(), getStart()); - - commonInfo.removeAttribute(UNPARSED_GENOTYPE_MAP_KEY); - commonInfo.removeAttribute(UNPARSED_GENOTYPE_PARSER_KEY); - - validateGenotypes(); - } - - /** - * @return the number of samples in the context - */ - public int getNSamples() { - loadGenotypes(); - return genotypes.size(); - } - - /** - * @return true if the context has associated genotypes - */ - public boolean hasGenotypes() { - loadGenotypes(); - return genotypes.size() > 0; - } - - public boolean hasGenotypes(Collection sampleNames) { - loadGenotypes(); - for ( String name : sampleNames ) { - if ( ! genotypes.containsKey(name) ) - return false; - } - return true; - } - - /** - * @return set of all Genotypes associated with this context - */ - public Map getGenotypes() { - loadGenotypes(); - return genotypes; - } - - public List getGenotypesSortedByName() { - loadGenotypes(); - Collection types = new TreeMap(genotypes).values(); - return new ArrayList(types); - } - - /** - * Returns a map from sampleName -> Genotype for the genotype associated with sampleName. Returns a map - * for consistency with the multi-get function. - * - * @param sampleName - * @return - * @throws IllegalArgumentException if sampleName isn't bound to a genotype - */ - public Map getGenotypes(String sampleName) { - return getGenotypes(Arrays.asList(sampleName)); - } - - /** - * Returns a map from sampleName -> Genotype for each sampleName in sampleNames. Returns a map - * for consistency with the multi-get function. - * - * @param sampleNames a unique list of sample names - * @return - * @throws IllegalArgumentException if sampleName isn't bound to a genotype - */ - public Map getGenotypes(Collection sampleNames) { - HashMap map = new HashMap(); - - for ( String name : sampleNames ) { - if ( map.containsKey(name) ) throw new IllegalArgumentException("Duplicate names detected in requested samples " + sampleNames); - final Genotype g = getGenotype(name); - if ( g != null ) { - map.put(name, g); - } - } - - return map; - } - - /** - * @return the set of all sample names in this context - */ - public Set getSampleNames() { - return getGenotypes().keySet(); - } - - /** - * @param sample the sample name - * - * @return the Genotype associated with the given sample in this context or null if the sample is not in this context - */ - public Genotype getGenotype(String sample) { - return getGenotypes().get(sample); - } - - public boolean hasGenotype(String sample) { - return getGenotypes().containsKey(sample); - } - - public Genotype getGenotype(int ith) { - return getGenotypesSortedByName().get(ith); - } - - - /** - * Returns the number of chromosomes carrying any allele in the genotypes (i.e., excluding NO_CALLS - * - * @return chromosome count - */ - public int getChromosomeCount() { - int n = 0; - - for ( Genotype g : getGenotypes().values() ) { - n += g.isNoCall() ? 0 : g.getPloidy(); - } - - return n; - } - - /** - * Returns the number of chromosomes carrying allele A in the genotypes - * - * @param a allele - * @return chromosome count - */ - public int getChromosomeCount(Allele a) { - int n = 0; - - for ( Genotype g : getGenotypes().values() ) { - n += g.getAlleles(a).size(); - } - - return n; - } - - /** - * Genotype-specific functions -- are the genotypes monomorphic w.r.t. to the alleles segregating at this - * site? That is, is the number of alternate alleles among all fo the genotype == 0? - * - * @return true if it's monomorphic - */ - public boolean isMonomorphic() { - return ! isVariant() || (hasGenotypes() && getHomRefCount() + getNoCallCount() == getNSamples()); - } - - /** - * Genotype-specific functions -- are the genotypes polymorphic w.r.t. to the alleles segregating at this - * site? That is, is the number of alternate alleles among all fo the genotype > 0? - * - * @return true if it's polymorphic - */ - public boolean isPolymorphic() { - return ! isMonomorphic(); - } - - private void calculateGenotypeCounts() { - if ( genotypeCounts == null ) { - genotypeCounts = new int[Genotype.Type.values().length]; - - for ( Genotype g : getGenotypes().values() ) { - if ( g.isNoCall() ) - genotypeCounts[Genotype.Type.NO_CALL.ordinal()]++; - else if ( g.isHomRef() ) - genotypeCounts[Genotype.Type.HOM_REF.ordinal()]++; - else if ( g.isHet() ) - genotypeCounts[Genotype.Type.HET.ordinal()]++; - else if ( g.isHomVar() ) - genotypeCounts[Genotype.Type.HOM_VAR.ordinal()]++; - else - genotypeCounts[Genotype.Type.MIXED.ordinal()]++; - } - } - } - - /** - * Genotype-specific functions -- how many no-calls are there in the genotypes? - * - * @return number of no calls - */ - public int getNoCallCount() { - calculateGenotypeCounts(); - return genotypeCounts[Genotype.Type.NO_CALL.ordinal()]; - } - - /** - * Genotype-specific functions -- how many hom ref calls are there in the genotypes? - * - * @return number of hom ref calls - */ - public int getHomRefCount() { - calculateGenotypeCounts(); - return genotypeCounts[Genotype.Type.HOM_REF.ordinal()]; - } - - /** - * Genotype-specific functions -- how many het calls are there in the genotypes? - * - * @return number of het calls - */ - public int getHetCount() { - calculateGenotypeCounts(); - return genotypeCounts[Genotype.Type.HET.ordinal()]; - } - - /** - * Genotype-specific functions -- how many hom var calls are there in the genotypes? - * - * @return number of hom var calls - */ - public int getHomVarCount() { - return genotypeCounts[Genotype.Type.HOM_VAR.ordinal()]; - } - - /** - * Genotype-specific functions -- how many mixed calls are there in the genotypes? - * - * @return number of mixed calls - */ - public int getMixedCount() { - return genotypeCounts[Genotype.Type.MIXED.ordinal()]; - } - - // --------------------------------------------------------------------------------------------------------- - // - // validation: extra-strict validation routines for paranoid users - // - // --------------------------------------------------------------------------------------------------------- - - /** - * Run all extra-strict validation tests on a Variant Context object - * - * @param reference the true reference allele - * @param paddedRefBase the reference base used for padding indels - * @param rsIDs the true dbSNP IDs - */ - public void extraStrictValidation(Allele reference, Byte paddedRefBase, Set rsIDs) { - // validate the reference - validateReferenceBases(reference, paddedRefBase); - - // validate the RS IDs - validateRSIDs(rsIDs); - - // validate the altenate alleles - validateAlternateAlleles(); - - // validate the AN and AC fields - validateChromosomeCounts(); - - // TODO: implement me - //checkReferenceTrack(); - } - - public void validateReferenceBases(Allele reference, Byte paddedRefBase) { - // don't validate if we're a complex event - if ( !isComplexIndel() && !reference.isNull() && !reference.basesMatch(getReference()) ) { - throw new TribbleException.InternalCodecException(String.format("the REF allele is incorrect for the record at position %s:%d, fasta says %s vs. VCF says %s", getChr(), getStart(), reference.getBaseString(), getReference().getBaseString())); - } - - // we also need to validate the padding base for simple indels - if ( hasReferenceBaseForIndel() && !getReferenceBaseForIndel().equals(paddedRefBase) ) { - throw new TribbleException.InternalCodecException(String.format("the padded REF base is incorrect for the record at position %s:%d, fasta says %s vs. VCF says %s", getChr(), getStart(), (char)paddedRefBase.byteValue(), (char)getReferenceBaseForIndel().byteValue())); - } - } - - public void validateRSIDs(Set rsIDs) { - if ( rsIDs != null && hasAttribute(VariantContext.ID_KEY) ) { - for ( String id : getID().split(VCFConstants.ID_FIELD_SEPARATOR) ) { - if ( id.startsWith("rs") && !rsIDs.contains(id) ) - throw new TribbleException.InternalCodecException(String.format("the rsID %s for the record at position %s:%d is not in dbSNP", id, getChr(), getStart())); - } - } - } - - public void validateAlternateAlleles() { - if ( !hasGenotypes() ) - return; - - List reportedAlleles = getAlleles(); - Set observedAlleles = new HashSet(); - observedAlleles.add(getReference()); - for ( Genotype g : getGenotypes().values() ) { - if ( g.isCalled() ) - observedAlleles.addAll(g.getAlleles()); - } - - if ( reportedAlleles.size() != observedAlleles.size() ) - throw new TribbleException.InternalCodecException(String.format("the ALT allele(s) for the record at position %s:%d do not match what is observed in the per-sample genotypes", getChr(), getStart())); - - int originalSize = reportedAlleles.size(); - // take the intersection and see if things change - observedAlleles.retainAll(reportedAlleles); - if ( observedAlleles.size() != originalSize ) - throw new TribbleException.InternalCodecException(String.format("the ALT allele(s) for the record at position %s:%d do not match what is observed in the per-sample genotypes", getChr(), getStart())); - } - - public void validateChromosomeCounts() { - Map observedAttrs = calculateChromosomeCounts(); - - // AN - if ( hasAttribute(VCFConstants.ALLELE_NUMBER_KEY) ) { - int reportedAN = Integer.valueOf(getAttribute(VCFConstants.ALLELE_NUMBER_KEY).toString()); - int observedAN = (Integer)observedAttrs.get(VCFConstants.ALLELE_NUMBER_KEY); - if ( reportedAN != observedAN ) - throw new TribbleException.InternalCodecException(String.format("the Allele Number (AN) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedAN, observedAN)); - } - - // AC - if ( hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) { - List observedACs = (List)observedAttrs.get(VCFConstants.ALLELE_COUNT_KEY); - if ( getAttribute(VCFConstants.ALLELE_COUNT_KEY) instanceof List ) { - Collections.sort(observedACs); - List reportedACs = (List)getAttribute(VCFConstants.ALLELE_COUNT_KEY); - Collections.sort(reportedACs); - if ( observedACs.size() != reportedACs.size() ) - throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag doesn't have the correct number of values for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedACs.size(), observedACs.size())); - for (int i = 0; i < observedACs.size(); i++) { - if ( Integer.valueOf(reportedACs.get(i).toString()) != observedACs.get(i) ) - throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedACs.get(i), observedACs.get(i))); - } - } else { - if ( observedACs.size() != 1 ) - throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag doesn't have enough values for the record at position %s:%d", getChr(), getStart())); - int reportedAC = Integer.valueOf(getAttribute(VCFConstants.ALLELE_COUNT_KEY).toString()); - if ( reportedAC != observedACs.get(0) ) - throw new TribbleException.InternalCodecException(String.format("the Allele Count (AC) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedAC, observedACs.get(0))); - } - } - } - - private Map calculateChromosomeCounts() { - Map attributes = new HashMap(); - - attributes.put(VCFConstants.ALLELE_NUMBER_KEY, getChromosomeCount()); - ArrayList alleleFreqs = new ArrayList(); - ArrayList alleleCounts = new ArrayList(); - - // if there are alternate alleles, record the relevant tags - if ( getAlternateAlleles().size() > 0 ) { - for ( Allele allele : getAlternateAlleles() ) { - alleleCounts.add(getChromosomeCount(allele)); - alleleFreqs.add((double)getChromosomeCount(allele) / (double)getChromosomeCount()); - } - } - // otherwise, set them to 0 - else { - alleleCounts.add(0); - alleleFreqs.add(0.0); - } - - attributes.put(VCFConstants.ALLELE_COUNT_KEY, alleleCounts); - attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, alleleFreqs); - return attributes; - } - - // --------------------------------------------------------------------------------------------------------- - // - // validation: the normal validation routines are called automatically upon creation of the VC - // - // --------------------------------------------------------------------------------------------------------- - - /** - * To be called by any modifying routines - */ - private boolean validate() { - return validate(true); - } - - private boolean validate(boolean throwException) { - try { - validateReferencePadding(); - validateAlleles(); - validateGenotypes(); - } catch ( IllegalArgumentException e ) { - if ( throwException ) - throw e; - else - return false; - } - - return true; - } - - private void validateReferencePadding() { - if (hasSymbolicAlleles()) // symbolic alleles don't need padding... - return; - - boolean needsPadding = (getReference().length() == getEnd() - getStart()); // off by one because padded base was removed - - if ( needsPadding && !hasReferenceBaseForIndel() ) - throw new ReviewedStingException("Badly formed variant context at location " + getChr() + ":" + getStart() + "; no padded reference base was provided."); - } - - private void validateAlleles() { - // check alleles - boolean alreadySeenRef = false, alreadySeenNull = false; - for ( Allele allele : alleles ) { - // make sure there's only one reference allele - if ( allele.isReference() ) { - if ( alreadySeenRef ) throw new IllegalArgumentException("BUG: Received two reference tagged alleles in VariantContext " + alleles + " this=" + this); - alreadySeenRef = true; - } - - if ( allele.isNoCall() ) { - throw new IllegalArgumentException("BUG: Cannot add a no call allele to a variant context " + alleles + " this=" + this); - } - - // make sure there's only one null allele - if ( allele.isNull() ) { - if ( alreadySeenNull ) throw new IllegalArgumentException("BUG: Received two null alleles in VariantContext " + alleles + " this=" + this); - alreadySeenNull = true; - } - } - - // make sure there's one reference allele - if ( ! alreadySeenRef ) - throw new IllegalArgumentException("No reference allele found in VariantContext"); - -// if ( getType() == Type.INDEL ) { -// if ( getReference().length() != (getLocation().size()-1) ) { - long length = (stop - start) + 1; - if ( (getReference().isNull() && length != 1 ) || - (getReference().isNonNull() && (length - getReference().length() > 1))) { - throw new IllegalStateException("BUG: GenomeLoc " + contig + ":" + start + "-" + stop + " has a size == " + length + " but the variation reference allele has length " + getReference().length() + " this = " + this); - } - } - - private void validateGenotypes() { - if ( this.genotypes == null ) throw new IllegalStateException("Genotypes is null"); - - for ( Map.Entry elt : this.genotypes.entrySet() ) { - String name = elt.getKey(); - Genotype g = elt.getValue(); - - if ( ! name.equals(g.getSampleName()) ) throw new IllegalStateException("Bound sample name " + name + " does not equal the name of the genotype " + g.getSampleName()); - - if ( g.isAvailable() ) { - for ( Allele gAllele : g.getAlleles() ) { - if ( ! hasAllele(gAllele) && gAllele.isCalled() ) - throw new IllegalStateException("Allele in genotype " + gAllele + " not in the variant context " + alleles); - } - } - } - } - - // --------------------------------------------------------------------------------------------------------- - // - // utility routines - // - // --------------------------------------------------------------------------------------------------------- - - private void determineType() { - if ( type == null ) { - switch ( getNAlleles() ) { - case 0: - throw new IllegalStateException("Unexpected error: requested type of VariantContext with no alleles!" + this); - case 1: - // note that this doesn't require a reference allele. You can be monomorphic independent of having a - // reference allele - type = Type.NO_VARIATION; - break; - default: - determinePolymorphicType(); - } - } - } - - private void determinePolymorphicType() { - type = null; - - // do a pairwise comparison of all alleles against the reference allele - for ( Allele allele : alleles ) { - if ( allele == REF ) - continue; - - // find the type of this allele relative to the reference - Type biallelicType = typeOfBiallelicVariant(REF, allele); - - // for the first alternate allele, set the type to be that one - if ( type == null ) { - type = biallelicType; - } - // if the type of this allele is different from that of a previous one, assign it the MIXED type and quit - else if ( biallelicType != type ) { - type = Type.MIXED; - return; - } - } - } - - private static Type typeOfBiallelicVariant(Allele ref, Allele allele) { - if ( ref.isSymbolic() ) - throw new IllegalStateException("Unexpected error: encountered a record with a symbolic reference allele"); - - if ( allele.isSymbolic() ) - return Type.SYMBOLIC; - - if ( ref.length() == allele.length() ) { - if ( allele.length() == 1 ) - return Type.SNP; - else - return Type.MNP; - } - - // Important note: previously we were checking that one allele is the prefix of the other. However, that's not an - // appropriate check as can be seen from the following example: - // REF = CTTA and ALT = C,CT,CA - // This should be assigned the INDEL type but was being marked as a MIXED type because of the prefix check. - // In truth, it should be absolutely impossible to return a MIXED type from this method because it simply - // performs a pairwise comparison of a single alternate allele against the reference allele (whereas the MIXED type - // is reserved for cases of multiple alternate alleles of different types). Therefore, if we've reached this point - // in the code (so we're not a SNP, MNP, or symbolic allele), we absolutely must be an INDEL. - return Type.INDEL; - - // old incorrect logic: - // if (oneIsPrefixOfOther(ref, allele)) - // return Type.INDEL; - // else - // return Type.MIXED; - } - - public String toString() { - return String.format("[VC %s @ %s of type=%s alleles=%s attr=%s GT=%s", - getSource(), contig + ":" + (start - stop == 0 ? start : start + "-" + stop), this.getType(), - ParsingUtils.sortList(this.getAlleles()), ParsingUtils.sortedString(this.getAttributes()), this.getGenotypesSortedByName()); - } - - // protected basic manipulation routines - private static List makeAlleles(Collection alleles) { - final List alleleList = new ArrayList(alleles.size()); - - boolean sawRef = false; - for ( final Allele a : alleles ) { - for ( final Allele b : alleleList ) { - if ( a.equals(b, true) ) - throw new IllegalArgumentException("Duplicate allele added to VariantContext: " + a); - } - - // deal with the case where the first allele isn't the reference - if ( a.isReference() ) { - if ( sawRef ) - throw new IllegalArgumentException("Alleles for a VariantContext must contain at most one reference allele: " + alleles); - alleleList.add(0, a); - sawRef = true; - } - else - alleleList.add(a); - } - - if ( alleleList.isEmpty() ) - throw new IllegalArgumentException("Cannot create a VariantContext with an empty allele list"); - - if ( alleleList.get(0).isNonReference() ) - throw new IllegalArgumentException("Alleles for a VariantContext must contain at least one reference allele: " + alleles); - - return alleleList; - } - - public static Map genotypeCollectionToMap(Map dest, Collection genotypes) { - for ( Genotype g : genotypes ) { - if ( dest.containsKey(g.getSampleName() ) ) - throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); - dest.put(g.getSampleName(), g); - } - - return dest; - } - - // --------------------------------------------------------------------------------------------------------- - // - // tribble integration routines -- not for public consumption - // - // --------------------------------------------------------------------------------------------------------- - public String getChr() { - return contig; - } - - public int getStart() { - return (int)start; - } - - public int getEnd() { - return (int)stop; - } - - private boolean hasSymbolicAlleles() { - for (Allele a: getAlleles()) { - if (a.isSymbolic()) { - return true; - } - } - return false; - } - - public static VariantContext createVariantContextWithPaddedAlleles(VariantContext inputVC, boolean refBaseShouldBeAppliedToEndOfAlleles) { - - // see if we need to pad common reference base from all alleles - boolean padVC; - - // We need to pad a VC with a common base if the length of the reference allele is less than the length of the VariantContext. - // This happens because the position of e.g. an indel is always one before the actual event (as per VCF convention). - long locLength = (inputVC.getEnd() - inputVC.getStart()) + 1; - if (inputVC.hasSymbolicAlleles()) - padVC = true; - else if (inputVC.getReference().length() == locLength) - padVC = false; - else if (inputVC.getReference().length() == locLength-1) - padVC = true; - else throw new IllegalArgumentException("Badly formed variant context at location " + String.valueOf(inputVC.getStart()) + - " in contig " + inputVC.getChr() + ". Reference length must be at most one base shorter than location size"); - - // nothing to do if we don't need to pad bases - if (padVC) { - - if ( !inputVC.hasReferenceBaseForIndel() ) - throw new ReviewedStingException("Badly formed variant context at location " + inputVC.getChr() + ":" + inputVC.getStart() + "; no padded reference base is available."); - - Byte refByte = inputVC.getReferenceBaseForIndel(); - - List alleles = new ArrayList(); - Map genotypes = new TreeMap(); - - Map inputGenotypes = inputVC.getGenotypes(); - - for (Allele a : inputVC.getAlleles()) { - // get bases for current allele and create a new one with trimmed bases - if (a.isSymbolic()) { - alleles.add(a); - } else { - String newBases; - if ( refBaseShouldBeAppliedToEndOfAlleles ) - newBases = a.getBaseString() + new String(new byte[]{refByte}); - else - newBases = new String(new byte[]{refByte}) + a.getBaseString(); - alleles.add(Allele.create(newBases,a.isReference())); - } - } - - // now we can recreate new genotypes with trimmed alleles - for (String sample : inputVC.getSampleNames()) { - Genotype g = inputGenotypes.get(sample); - - List inAlleles = g.getAlleles(); - List newGenotypeAlleles = new ArrayList(); - for (Allele a : inAlleles) { - if (a.isCalled()) { - if (a.isSymbolic()) { - newGenotypeAlleles.add(a); - } else { - String newBases; - if ( refBaseShouldBeAppliedToEndOfAlleles ) - newBases = a.getBaseString() + new String(new byte[]{refByte}); - else - newBases = new String(new byte[]{refByte}) + a.getBaseString(); - newGenotypeAlleles.add(Allele.create(newBases,a.isReference())); - } - } - else { - // add no-call allele - newGenotypeAlleles.add(Allele.NO_CALL); - } - } - genotypes.put(sample, new Genotype(sample, newGenotypeAlleles, g.getNegLog10PError(), - g.getFilters(),g.getAttributes(),g.isPhased())); - - } - - // Do not change the filter state if filters were not applied to this context - Set inputVCFilters = inputVC.filtersWereAppliedToContext ? inputVC.getFilters() : null; - return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); - } - else - return inputVC; - - } - - public ArrayList getTwoAllelesWithHighestAlleleCounts() { - // first idea: get two alleles with highest AC - int maxAC1 = 0, maxAC2=0,maxAC1ind =0, maxAC2ind = 0; - int i=0; - int[] alleleCounts = new int[this.getAlleles().size()]; - ArrayList alleleArray = new ArrayList(); - for (Allele a:this.getAlleles()) { - int ac = this.getChromosomeCount(a); - if (ac >=maxAC1) { - maxAC1 = ac; - maxAC1ind = i; - } - alleleArray.add(a); - alleleCounts[i++] = ac; - } - // now get second best allele - for (i=0; i < alleleCounts.length; i++) { - if (i == maxAC1ind) - continue; - if (alleleCounts[i] >= maxAC2) { - maxAC2 = alleleCounts[i]; - maxAC2ind = i; - } - } - - Allele alleleA, alleleB; - if (alleleArray.get(maxAC1ind).isReference()) { - alleleA = alleleArray.get(maxAC1ind); - alleleB = alleleArray.get(maxAC2ind); - } - else if (alleleArray.get(maxAC2ind).isReference()) { - alleleA = alleleArray.get(maxAC2ind); - alleleB = alleleArray.get(maxAC1ind); - } else { - alleleA = alleleArray.get(maxAC1ind); - alleleB = alleleArray.get(maxAC2ind); - } - ArrayList a = new ArrayList(); - a.add(alleleA); - a.add(alleleB); - return a; - } - public Allele getAltAlleleWithHighestAlleleCount() { - // first idea: get two alleles with highest AC - Allele best = null; - int maxAC1 = 0; - for (Allele a:this.getAlternateAlleles()) { - int ac = this.getChromosomeCount(a); - if (ac >=maxAC1) { - maxAC1 = ac; - best = a; - } - - } - return best; - } - - public int[] getGLIndecesOfAllele(Allele inputAllele) { - int[] idxVector = new int[3]; - int numAlleles = this.getAlleles().size(); - - int idxDiag = numAlleles; - int incr = numAlleles - 1; - int k=1; - for (Allele a: getAlternateAlleles()) { - // multi-allelic approximation, part 1: Ideally - // for each alt allele compute marginal (suboptimal) posteriors - - // compute indices for AA,AB,BB for current allele - genotype likelihoods are a linear vector that can be thought of - // as a row-wise upper triangular matrix of likelihoods. - // So, for example, with 2 alt alleles, likelihoods have AA,AB,AC,BB,BC,CC. - // 3 alt alleles: AA,AB,AC,AD BB BC BD CC CD DD - - int idxAA = 0; - int idxAB = k++; - // yy is always element on the diagonal. - // 2 alleles: BBelement 2 - // 3 alleles: BB element 3. CC element 5 - // 4 alleles: - int idxBB = idxDiag; - - if (a.equals(inputAllele)) { - idxVector[0] = idxAA; - idxVector[1] = idxAB; - idxVector[2] = idxBB; - break; - } - idxDiag += incr--; - } - return idxVector; - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java deleted file mode 100755 index e2cf2ecf0..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantContextUtils.java +++ /dev/null @@ -1,1407 +0,0 @@ -/* - * Copyright (c) 2010. The Broad Institute - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import com.google.java.contract.Ensures; -import com.google.java.contract.Requires; -import net.sf.picard.reference.ReferenceSequenceFile; -import net.sf.samtools.util.StringUtil; -import org.apache.commons.jexl2.Expression; -import org.apache.commons.jexl2.JexlEngine; -import org.apache.log4j.Logger; -import org.broad.tribble.util.popgen.HardyWeinbergCalculation; -import org.broadinstitute.sting.gatk.walkers.phasing.ReadBackedPhasingWalker; -import org.broadinstitute.sting.utils.BaseUtils; -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.Utils; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; - -import java.io.Serializable; -import java.util.*; - -public class VariantContextUtils { - private static Logger logger = Logger.getLogger(VariantContextUtils.class); - public final static String MERGE_INTERSECTION = "Intersection"; - public final static String MERGE_FILTER_IN_ALL = "FilteredInAll"; - public final static String MERGE_REF_IN_ALL = "ReferenceInAll"; - public final static String MERGE_FILTER_PREFIX = "filterIn"; - - final public static JexlEngine engine = new JexlEngine(); - static { - engine.setSilent(false); // will throw errors now for selects that don't evaluate properly - engine.setLenient(false); - } - - /** - * Create a new VariantContext - * - * @param name name - * @param loc location - * @param alleles alleles - * @param genotypes genotypes set - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @return VariantContext object - */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes != null ? VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes) : null, negLog10PError, filters, attributes); - } - - /** - * Create a new variant context without genotypes and no Perror, no filters, and no attributes - * @param name name - * @param loc location - * @param alleles alleles - * @return VariantContext object - */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles) { - return new VariantContext (name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, VariantContext.NO_GENOTYPES, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - /** - * Create a new variant context without genotypes and no Perror, no filters, and no attributes - * @param name name - * @param loc location - * @param alleles alleles - * @param genotypes genotypes - * @return VariantContext object - */ - public static VariantContext toVC(String name, GenomeLoc loc, Collection alleles, Collection genotypes) { - return new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, InferredGeneticContext.NO_NEG_LOG_10PERROR, null, null); - } - - /** - * Copy constructor - * - * @param other the VariantContext to copy - * @return VariantContext object - */ - public static VariantContext toVC(VariantContext other) { - return new VariantContext(other.getSource(), other.getChr(), other.getStart(), other.getEnd(), other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.getFilters(), other.getAttributes()); - } - - /** - * Update the attributes of the attributes map given the VariantContext to reflect the proper chromosome-based VCF tags - * - * @param vc the VariantContext - * @param attributes the attributes map to populate; must not be null; may contain old values - * @param removeStaleValues should we remove stale values from the mapping? - */ - public static void calculateChromosomeCounts(VariantContext vc, Map attributes, boolean removeStaleValues) { - // if everyone is a no-call, remove the old attributes if requested - if ( vc.getChromosomeCount() == 0 && removeStaleValues ) { - if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) - attributes.remove(VCFConstants.ALLELE_COUNT_KEY); - if ( attributes.containsKey(VCFConstants.ALLELE_FREQUENCY_KEY) ) - attributes.remove(VCFConstants.ALLELE_FREQUENCY_KEY); - if ( attributes.containsKey(VCFConstants.ALLELE_NUMBER_KEY) ) - attributes.remove(VCFConstants.ALLELE_NUMBER_KEY); - return; - } - - if ( vc.hasGenotypes() ) { - attributes.put(VCFConstants.ALLELE_NUMBER_KEY, vc.getChromosomeCount()); - - // if there are alternate alleles, record the relevant tags - if ( vc.getAlternateAlleles().size() > 0 ) { - ArrayList alleleFreqs = new ArrayList(); - ArrayList alleleCounts = new ArrayList(); - double totalChromosomes = (double)vc.getChromosomeCount(); - for ( Allele allele : vc.getAlternateAlleles() ) { - int altChromosomes = vc.getChromosomeCount(allele); - alleleCounts.add(altChromosomes); - String freq = String.format(makePrecisionFormatStringFromDenominatorValue(totalChromosomes), ((double)altChromosomes / totalChromosomes)); - alleleFreqs.add(freq); - } - - attributes.put(VCFConstants.ALLELE_COUNT_KEY, alleleCounts.size() == 1 ? alleleCounts.get(0) : alleleCounts); - attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, alleleFreqs.size() == 1 ? alleleFreqs.get(0) : alleleFreqs); - } - else { - attributes.put(VCFConstants.ALLELE_COUNT_KEY, 0); - attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, 0.0); - } - } - } - - private static String makePrecisionFormatStringFromDenominatorValue(double maxValue) { - int precision = 1; - - while ( maxValue > 1 ) { - precision++; - maxValue /= 10.0; - } - - return "%." + precision + "f"; - } - - public static Genotype removePLs(Genotype g) { - Map attrs = new HashMap(g.getAttributes()); - attrs.remove(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY); - attrs.remove(VCFConstants.GENOTYPE_LIKELIHOODS_KEY); - return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attrs, g.isPhased()); - } - - /** - * A simple but common wrapper for matching VariantContext objects using JEXL expressions - */ - public static class JexlVCMatchExp { - public String name; - public Expression exp; - - /** - * Create a new matcher expression with name and JEXL expression exp - * @param name name - * @param exp expression - */ - public JexlVCMatchExp(String name, Expression exp) { - this.name = name; - this.exp = exp; - } - } - - /** - * Method for creating JexlVCMatchExp from input walker arguments names and exps. These two arrays contain - * the name associated with each JEXL expression. initializeMatchExps will parse each expression and return - * a list of JexlVCMatchExp, in order, that correspond to the names and exps. These are suitable input to - * match() below. - * - * @param names names - * @param exps expressions - * @return list of matches - */ - public static List initializeMatchExps(String[] names, String[] exps) { - if ( names == null || exps == null ) - throw new ReviewedStingException("BUG: neither names nor exps can be null: names " + Arrays.toString(names) + " exps=" + Arrays.toString(exps) ); - - if ( names.length != exps.length ) - throw new UserException("Inconsistent number of provided filter names and expressions: names=" + Arrays.toString(names) + " exps=" + Arrays.toString(exps)); - - Map map = new HashMap(); - for ( int i = 0; i < names.length; i++ ) { map.put(names[i], exps[i]); } - - return VariantContextUtils.initializeMatchExps(map); - } - - public static List initializeMatchExps(ArrayList names, ArrayList exps) { - String[] nameArray = new String[names.size()]; - String[] expArray = new String[exps.size()]; - return initializeMatchExps(names.toArray(nameArray), exps.toArray(expArray)); - } - - - /** - * Method for creating JexlVCMatchExp from input walker arguments mapping from names to exps. These two arrays contain - * the name associated with each JEXL expression. initializeMatchExps will parse each expression and return - * a list of JexlVCMatchExp, in order, that correspond to the names and exps. These are suitable input to - * match() below. - * - * @param names_and_exps mapping of names to expressions - * @return list of matches - */ - public static List initializeMatchExps(Map names_and_exps) { - List exps = new ArrayList(); - - for ( Map.Entry elt : names_and_exps.entrySet() ) { - String name = elt.getKey(); - String expStr = elt.getValue(); - - if ( name == null || expStr == null ) throw new IllegalArgumentException("Cannot create null expressions : " + name + " " + expStr); - try { - Expression exp = engine.createExpression(expStr); - exps.add(new JexlVCMatchExp(name, exp)); - } catch (Exception e) { - throw new UserException.BadArgumentValue(name, "Invalid expression used (" + expStr + "). Please see the JEXL docs for correct syntax.") ; - } - } - - return exps; - } - - /** - * Returns true if exp match VC. See collection<> version for full docs. - * @param vc variant context - * @param exp expression - * @return true if there is a match - */ - public static boolean match(VariantContext vc, JexlVCMatchExp exp) { - return match(vc,Arrays.asList(exp)).get(exp); - } - - /** - * Matches each JexlVCMatchExp exp against the data contained in vc, and returns a map from these - * expressions to true (if they matched) or false (if they didn't). This the best way to apply JEXL - * expressions to VariantContext records. Use initializeMatchExps() to create the list of JexlVCMatchExp - * expressions. - * - * @param vc variant context - * @param exps expressions - * @return true if there is a match - */ - public static Map match(VariantContext vc, Collection exps) { - return new JEXLMap(exps,vc); - - } - - /** - * Returns true if exp match VC/g. See collection<> version for full docs. - * @param vc variant context - * @param g genotype - * @param exp expression - * @return true if there is a match - */ - public static boolean match(VariantContext vc, Genotype g, JexlVCMatchExp exp) { - return match(vc,g,Arrays.asList(exp)).get(exp); - } - - /** - * Matches each JexlVCMatchExp exp against the data contained in vc/g, and returns a map from these - * expressions to true (if they matched) or false (if they didn't). This the best way to apply JEXL - * expressions to VariantContext records/genotypes. Use initializeMatchExps() to create the list of JexlVCMatchExp - * expressions. - * - * @param vc variant context - * @param g genotype - * @param exps expressions - * @return true if there is a match - */ - public static Map match(VariantContext vc, Genotype g, Collection exps) { - return new JEXLMap(exps,vc,g); - } - - public static double computeHardyWeinbergPvalue(VariantContext vc) { - if ( vc.getChromosomeCount() == 0 ) - return 0.0; - return HardyWeinbergCalculation.hwCalculate(vc.getHomRefCount(), vc.getHetCount(), vc.getHomVarCount()); - } - - /** - * Returns a newly allocated VC that is the same as VC, but without genotypes - * @param vc variant context - * @return new VC without genotypes - */ - @Requires("vc != null") - @Ensures("result != null") - public static VariantContext sitesOnlyVariantContext(VariantContext vc) { - return VariantContext.modifyGenotypes(vc, null); - } - - /** - * Returns a newly allocated list of VC, where each VC is the same as the input VCs, but without genotypes - * @param vcs collection of VCs - * @return new VCs without genotypes - */ - @Requires("vcs != null") - @Ensures("result != null") - public static Collection sitesOnlyVariantContexts(Collection vcs) { - List r = new ArrayList(); - for ( VariantContext vc : vcs ) - r.add(sitesOnlyVariantContext(vc)); - return r; - } - - public static VariantContext pruneVariantContext(VariantContext vc) { - return pruneVariantContext(vc, null); - } - - public static VariantContext pruneVariantContext(final VariantContext vc, final Collection keysToPreserve ) { - final MutableVariantContext mvc = new MutableVariantContext(vc); - - if ( keysToPreserve == null || keysToPreserve.size() == 0 ) - mvc.clearAttributes(); - else { - final Map d = mvc.getAttributes(); - mvc.clearAttributes(); - for ( String key : keysToPreserve ) - if ( d.containsKey(key) ) - mvc.putAttribute(key, d.get(key)); - } - - // this must be done as the ID is stored in the attributes field - if ( vc.hasID() ) mvc.setID(vc.getID()); - - Collection gs = mvc.getGenotypes().values(); - mvc.clearGenotypes(); - for ( Genotype g : gs ) { - MutableGenotype mg = new MutableGenotype(g); - mg.clearAttributes(); - if ( keysToPreserve != null ) - for ( String key : keysToPreserve ) - if ( g.hasAttribute(key) ) - mg.putAttribute(key, g.getAttribute(key)); - mvc.addGenotype(mg); - } - - return mvc; - } - - public enum GenotypeMergeType { - /** - * Make all sample genotypes unique by file. Each sample shared across RODs gets named sample.ROD. - */ - UNIQUIFY, - /** - * Take genotypes in priority order (see the priority argument). - */ - PRIORITIZE, - /** - * Take the genotypes in any order. - */ - UNSORTED, - /** - * Require that all samples/genotypes be unique between all inputs. - */ - REQUIRE_UNIQUE - } - - public enum FilteredRecordMergeType { - /** - * Union - leaves the record if any record is unfiltered. - */ - KEEP_IF_ANY_UNFILTERED, - /** - * Requires all records present at site to be unfiltered. VCF files that don't contain the record don't influence this. - */ - KEEP_IF_ALL_UNFILTERED - } - - /** - * Performs a master merge on the VCs. Here there is a master input [contains all of the information] and many - * VCs containing partial, extra genotype information which should be added to the master. For example, - * we scatter out the phasing algorithm over some samples in the master, producing a minimal VCF with phasing - * information per genotype. The master merge will add the PQ information from each genotype record, where - * appropriate, to the master VC. - * - * @param unsortedVCs collection of VCs - * @param masterName name of master VC - * @return master-merged VC - */ - public static VariantContext masterMerge(Collection unsortedVCs, String masterName) { - VariantContext master = findMaster(unsortedVCs, masterName); - Map genotypes = master.getGenotypes(); - for (Genotype g : genotypes.values()) { - genotypes.put(g.getSampleName(), new MutableGenotype(g)); - } - - Map masterAttributes = new HashMap(master.getAttributes()); - - for (VariantContext vc : unsortedVCs) { - if (!vc.getSource().equals(masterName)) { - for (Genotype g : vc.getGenotypes().values()) { - MutableGenotype masterG = (MutableGenotype) genotypes.get(g.getSampleName()); - for (Map.Entry attr : g.getAttributes().entrySet()) { - if (!masterG.hasAttribute(attr.getKey())) { - //System.out.printf("Adding GT attribute %s to masterG %s, new %s%n", attr, masterG, g); - masterG.putAttribute(attr.getKey(), attr.getValue()); - } - } - - if (masterG.isPhased() != g.isPhased()) { - if (masterG.sameGenotype(g)) { - // System.out.printf("Updating phasing %s to masterG %s, new %s%n", g.isPhased(), masterG, g); - masterG.setAlleles(g.getAlleles()); - masterG.setPhase(g.isPhased()); - } - //else System.out.println("WARNING: Not updating phase, since genotypes differ between master file and auxiliary info file!"); - } - -// if ( MathUtils.compareDoubles(masterG.getNegLog10PError(), g.getNegLog10PError()) != 0 ) { -// System.out.printf("Updating GQ %s to masterG %s, new %s%n", g.getNegLog10PError(), masterG, g); -// masterG.setNegLog10PError(g.getNegLog10PError()); -// } - - } - - for (Map.Entry attr : vc.getAttributes().entrySet()) { - if (!masterAttributes.containsKey(attr.getKey())) { - //System.out.printf("Adding VC attribute %s to master %s, new %s%n", attr, master, vc); - masterAttributes.put(attr.getKey(), attr.getValue()); - } - } - } - } - - return new VariantContext(master.getSource(), master.getChr(), master.getStart(), master.getEnd(), master.getAlleles(), genotypes, master.getNegLog10PError(), master.getFilters(), masterAttributes); - } - - private static VariantContext findMaster(Collection unsortedVCs, String masterName) { - for (VariantContext vc : unsortedVCs) { - if (vc.getSource().equals(masterName)) { - return vc; - } - } - - throw new ReviewedStingException(String.format("Couldn't find master VCF %s at %s", masterName, unsortedVCs.iterator().next())); - } - - /** - * Merges VariantContexts into a single hybrid. Takes genotypes for common samples in priority order, if provided. - * If uniqifySamples is true, the priority order is ignored and names are created by concatenating the VC name with - * the sample name - * - * @param genomeLocParser loc parser - * @param unsortedVCs collection of unsorted VCs - * @param priorityListOfVCs priority list detailing the order in which we should grab the VCs - * @param filteredRecordMergeType merge type for filtered records - * @param genotypeMergeOptions merge option for genotypes - * @param annotateOrigin should we annotate the set it came from? - * @param printMessages should we print messages? - * @param setKey the key name of the set - * @param filteredAreUncalled are filtered records uncalled? - * @param mergeInfoWithMaxAC should we merge in info from the VC with maximum allele count? - * @return new VariantContext representing the merge of unsortedVCs - */ - public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, - final Collection unsortedVCs, - final List priorityListOfVCs, - final FilteredRecordMergeType filteredRecordMergeType, - final GenotypeMergeType genotypeMergeOptions, - final boolean annotateOrigin, - final boolean printMessages, - final String setKey, - final boolean filteredAreUncalled, - final boolean mergeInfoWithMaxAC ) { - if ( unsortedVCs == null || unsortedVCs.size() == 0 ) - return null; - - if ( annotateOrigin && priorityListOfVCs == null ) - throw new IllegalArgumentException("Cannot merge calls and annotate their origins without a complete priority list of VariantContexts"); - - if ( genotypeMergeOptions == GenotypeMergeType.REQUIRE_UNIQUE ) - verifyUniqueSampleNames(unsortedVCs); - - List prepaddedVCs = sortVariantContextsByPriority(unsortedVCs, priorityListOfVCs, genotypeMergeOptions); - // Make sure all variant contexts are padded with reference base in case of indels if necessary - List VCs = new ArrayList(); - - for (VariantContext vc : prepaddedVCs) { - // also a reasonable place to remove filtered calls, if needed - if ( ! filteredAreUncalled || vc.isNotFiltered() ) - VCs.add(VariantContext.createVariantContextWithPaddedAlleles(vc, false)); - } - if ( VCs.size() == 0 ) // everything is filtered out and we're filteredAreUncalled - return null; - - // establish the baseline info from the first VC - final VariantContext first = VCs.get(0); - final String name = first.getSource(); - final Allele refAllele = determineReferenceAllele(VCs); - - final Set alleles = new LinkedHashSet(); - final Set filters = new TreeSet(); - final Map attributes = new TreeMap(); - final Set inconsistentAttributes = new HashSet(); - final Set variantSources = new HashSet(); // contains the set of sources we found in our set of VCs that are variant - final Set rsIDs = new LinkedHashSet(1); // most of the time there's one id - - GenomeLoc loc = getLocation(genomeLocParser,first); - int depth = 0; - int maxAC = -1; - final Map attributesWithMaxAC = new TreeMap(); - double negLog10PError = -1; - VariantContext vcWithMaxAC = null; - Map genotypes = new TreeMap(); - - // counting the number of filtered and variant VCs - int nFiltered = 0; - - boolean remapped = false; - - // cycle through and add info from the other VCs, making sure the loc/reference matches - - for ( VariantContext vc : VCs ) { - if ( loc.getStart() != vc.getStart() ) // || !first.getReference().equals(vc.getReference()) ) - throw new ReviewedStingException("BUG: attempting to merge VariantContexts with different start sites: first="+ first.toString() + " second=" + vc.toString()); - - if ( getLocation(genomeLocParser,vc).size() > loc.size() ) - loc = getLocation(genomeLocParser,vc); // get the longest location - - nFiltered += vc.isFiltered() ? 1 : 0; - if ( vc.isVariant() ) variantSources.add(vc.getSource()); - - AlleleMapper alleleMapping = resolveIncompatibleAlleles(refAllele, vc, alleles); - remapped = remapped || alleleMapping.needsRemapping(); - - alleles.addAll(alleleMapping.values()); - - mergeGenotypes(genotypes, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); - - negLog10PError = Math.max(negLog10PError, vc.isVariant() ? vc.getNegLog10PError() : -1); - - filters.addAll(vc.getFilters()); - - // - // add attributes - // - // special case DP (add it up) and ID (just preserve it) - // - if (vc.hasAttribute(VCFConstants.DEPTH_KEY)) - depth += vc.getAttributeAsInt(VCFConstants.DEPTH_KEY, 0); - if ( vc.hasID() && ! vc.getID().equals(VCFConstants.EMPTY_ID_FIELD) ) rsIDs.add(vc.getID()); - if (mergeInfoWithMaxAC && vc.hasAttribute(VCFConstants.ALLELE_COUNT_KEY)) { - String rawAlleleCounts = vc.getAttributeAsString(VCFConstants.ALLELE_COUNT_KEY, null); - // lets see if the string contains a , separator - if (rawAlleleCounts.contains(VCFConstants.INFO_FIELD_ARRAY_SEPARATOR)) { - List alleleCountArray = Arrays.asList(rawAlleleCounts.substring(1, rawAlleleCounts.length() - 1).split(VCFConstants.INFO_FIELD_ARRAY_SEPARATOR)); - for (String alleleCount : alleleCountArray) { - final int ac = Integer.valueOf(alleleCount.trim()); - if (ac > maxAC) { - maxAC = ac; - vcWithMaxAC = vc; - } - } - } else { - final int ac = Integer.valueOf(rawAlleleCounts); - if (ac > maxAC) { - maxAC = ac; - vcWithMaxAC = vc; - } - } - } - - for (Map.Entry p : vc.getAttributes().entrySet()) { - String key = p.getKey(); - // if we don't like the key already, don't go anywhere - if ( ! inconsistentAttributes.contains(key) ) { - boolean alreadyFound = attributes.containsKey(key); - Object boundValue = attributes.get(key); - boolean boundIsMissingValue = alreadyFound && boundValue.equals(VCFConstants.MISSING_VALUE_v4); - - if ( alreadyFound && ! boundValue.equals(p.getValue()) && ! boundIsMissingValue ) { - // we found the value but we're inconsistent, put it in the exclude list - //System.out.printf("Inconsistent INFO values: %s => %s and %s%n", key, boundValue, p.getValue()); - inconsistentAttributes.add(key); - attributes.remove(key); - } else if ( ! alreadyFound || boundIsMissingValue ) { // no value - //if ( vc != first ) System.out.printf("Adding key %s => %s%n", p.getKey(), p.getValue()); - attributes.put(key, p.getValue()); - } - } - } - } - - // if we have more alternate alleles in the merged VC than in one or more of the - // original VCs, we need to strip out the GL/PLs (because they are no longer accurate), as well as allele-dependent attributes like AC,AF - for ( VariantContext vc : VCs ) { - if (vc.alleles.size() == 1) - continue; - if ( hasPLIncompatibleAlleles(alleles, vc.alleles)) { - logger.warn(String.format("Stripping PLs at %s due incompatible alleles merged=%s vs. single=%s", - genomeLocParser.createGenomeLoc(vc), alleles, vc.alleles)); - genotypes = stripPLs(genotypes); - // this will remove stale AC,AF attributed from vc - calculateChromosomeCounts(vc, attributes, true); - break; - } - } - - // take the VC with the maxAC and pull the attributes into a modifiable map - if ( mergeInfoWithMaxAC && vcWithMaxAC != null ) { - attributesWithMaxAC.putAll(vcWithMaxAC.getAttributes()); - } - - // if at least one record was unfiltered and we want a union, clear all of the filters - if ( filteredRecordMergeType == FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED && nFiltered != VCs.size() ) - filters.clear(); - - - if ( annotateOrigin ) { // we care about where the call came from - String setValue; - if ( nFiltered == 0 && variantSources.size() == priorityListOfVCs.size() ) // nothing was unfiltered - setValue = MERGE_INTERSECTION; - else if ( nFiltered == VCs.size() ) // everything was filtered out - setValue = MERGE_FILTER_IN_ALL; - else if ( variantSources.isEmpty() ) // everyone was reference - setValue = MERGE_REF_IN_ALL; - else { - LinkedHashSet s = new LinkedHashSet(); - for ( VariantContext vc : VCs ) - if ( vc.isVariant() ) - s.add( vc.isFiltered() ? MERGE_FILTER_PREFIX + vc.getSource() : vc.getSource() ); - setValue = Utils.join("-", s); - } - - if ( setKey != null ) { - attributes.put(setKey, setValue); - if( mergeInfoWithMaxAC && vcWithMaxAC != null ) { attributesWithMaxAC.put(setKey, vcWithMaxAC.getSource()); } - } - } - - if ( depth > 0 ) - attributes.put(VCFConstants.DEPTH_KEY, String.valueOf(depth)); - - if ( ! rsIDs.isEmpty() ) { - attributes.put(VariantContext.ID_KEY, Utils.join(",", rsIDs)); - } - - VariantContext merged = new VariantContext(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, negLog10PError, filters, (mergeInfoWithMaxAC ? attributesWithMaxAC : attributes) ); - // Trim the padded bases of all alleles if necessary - merged = createVariantContextWithTrimmedAlleles(merged); - - if ( printMessages && remapped ) System.out.printf("Remapped => %s%n", merged); - return merged; - } - - private static final boolean hasPLIncompatibleAlleles(final Collection alleleSet1, final Collection alleleSet2) { - final Iterator it1 = alleleSet1.iterator(); - final Iterator it2 = alleleSet2.iterator(); - - while ( it1.hasNext() && it2.hasNext() ) { - final Allele a1 = it1.next(); - final Allele a2 = it2.next(); - if ( ! a1.equals(a2) ) - return true; - } - - // by this point, at least one of the iterators is empty. All of the elements - // we've compared are equal up until this point. But it's possible that the - // sets aren't the same size, which is indicated by the test below. If they - // are of the same size, though, the sets are compatible - return it1.hasNext() || it2.hasNext(); - } - - public static boolean allelesAreSubset(VariantContext vc1, VariantContext vc2) { - // if all alleles of vc1 are a contained in alleles of vc2, return true - if (!vc1.getReference().equals(vc2.getReference())) - return false; - - for (Allele a :vc1.getAlternateAlleles()) { - if (!vc2.getAlternateAlleles().contains(a)) - return false; - } - - return true; - } - public static VariantContext createVariantContextWithTrimmedAlleles(VariantContext inputVC) { - // see if we need to trim common reference base from all alleles - boolean trimVC; - - // We need to trim common reference base from all alleles in all genotypes if a ref base is common to all alleles - Allele refAllele = inputVC.getReference(); - if (!inputVC.isVariant()) - trimVC = false; - else if (refAllele.isNull()) - trimVC = false; - else { - trimVC = (AbstractVCFCodec.computeForwardClipping(new ArrayList(inputVC.getAlternateAlleles()), - inputVC.getReference().getDisplayString()) > 0); - } - - // nothing to do if we don't need to trim bases - if (trimVC) { - List alleles = new ArrayList(); - Map genotypes = new TreeMap(); - - // set the reference base for indels in the attributes - Map attributes = new TreeMap(inputVC.getAttributes()); - - Map originalToTrimmedAlleleMap = new HashMap(); - - for (Allele a : inputVC.getAlleles()) { - if (a.isSymbolic()) { - alleles.add(a); - originalToTrimmedAlleleMap.put(a, a); - } else { - // get bases for current allele and create a new one with trimmed bases - byte[] newBases = Arrays.copyOfRange(a.getBases(), 1, a.length()); - Allele trimmedAllele = Allele.create(newBases, a.isReference()); - alleles.add(trimmedAllele); - originalToTrimmedAlleleMap.put(a, trimmedAllele); - } - } - - // detect case where we're trimming bases but resulting vc doesn't have any null allele. In that case, we keep original representation - // example: mixed records such as {TA*,TGA,TG} - boolean hasNullAlleles = false; - - for (Allele a: originalToTrimmedAlleleMap.values()) { - if (a.isNull()) - hasNullAlleles = true; - if (a.isReference()) - refAllele = a; - } - - if (!hasNullAlleles) - return inputVC; - // now we can recreate new genotypes with trimmed alleles - for ( Map.Entry sample : inputVC.getGenotypes().entrySet() ) { - - List originalAlleles = sample.getValue().getAlleles(); - List trimmedAlleles = new ArrayList(); - for ( Allele a : originalAlleles ) { - if ( a.isCalled() ) - trimmedAlleles.add(originalToTrimmedAlleleMap.get(a)); - else - trimmedAlleles.add(Allele.NO_CALL); - } - genotypes.put(sample.getKey(), Genotype.modifyAlleles(sample.getValue(), trimmedAlleles)); - - } - return new VariantContext(inputVC.getSource(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVC.filtersWereApplied() ? inputVC.getFilters() : null, attributes, new Byte(inputVC.getReference().getBases()[0])); - - } - - return inputVC; - } - - public static Map stripPLs(Map genotypes) { - Map newGs = new HashMap(genotypes.size()); - - for ( Map.Entry g : genotypes.entrySet() ) { - newGs.put(g.getKey(), g.getValue().hasLikelihoods() ? removePLs(g.getValue()) : g.getValue()); - } - - return newGs; - } - - public static Map> separateVariantContextsByType(Collection VCs) { - HashMap> mappedVCs = new HashMap>(); - for ( VariantContext vc : VCs ) { - - // look at previous variant contexts of different type. If: - // a) otherVC has alleles which are subset of vc, remove otherVC from its list and add otherVC to vc's list - // b) vc has alleles which are subset of otherVC. Then, add vc to otherVC's type list (rather, do nothing since vc will be added automatically to its list) - // c) neither: do nothing, just add vc to its own list - boolean addtoOwnList = true; - for (VariantContext.Type type : VariantContext.Type.values()) { - if (type.equals(vc.getType())) - continue; - - if (!mappedVCs.containsKey(type)) - continue; - - List vcList = mappedVCs.get(type); - for (int k=0; k < vcList.size(); k++) { - VariantContext otherVC = vcList.get(k); - if (allelesAreSubset(otherVC,vc)) { - // otherVC has a type different than vc and its alleles are a subset of vc: remove otherVC from its list and add it to vc's type list - vcList.remove(k); - // avoid having empty lists - if (vcList.size() == 0) - mappedVCs.remove(vcList); - if ( !mappedVCs.containsKey(vc.getType()) ) - mappedVCs.put(vc.getType(), new ArrayList()); - mappedVCs.get(vc.getType()).add(otherVC); - break; - } - else if (allelesAreSubset(vc,otherVC)) { - // vc has a type different than otherVC and its alleles are a subset of VC: add vc to otherVC's type list and don't add to its own - mappedVCs.get(type).add(vc); - addtoOwnList = false; - break; - } - } - } - if (addtoOwnList) { - if ( !mappedVCs.containsKey(vc.getType()) ) - mappedVCs.put(vc.getType(), new ArrayList()); - mappedVCs.get(vc.getType()).add(vc); - } - } - - return mappedVCs; - } - - private static class AlleleMapper { - private VariantContext vc = null; - private Map map = null; - public AlleleMapper(VariantContext vc) { this.vc = vc; } - public AlleleMapper(Map map) { this.map = map; } - public boolean needsRemapping() { return this.map != null; } - public Collection values() { return map != null ? map.values() : vc.getAlleles(); } - - public Allele remap(Allele a) { return map != null && map.containsKey(a) ? map.get(a) : a; } - - public List remap(List as) { - List newAs = new ArrayList(); - for ( Allele a : as ) { - //System.out.printf(" Remapping %s => %s%n", a, remap(a)); - newAs.add(remap(a)); - } - return newAs; - } - } - - static private void verifyUniqueSampleNames(Collection unsortedVCs) { - Set names = new HashSet(); - for ( VariantContext vc : unsortedVCs ) { - for ( String name : vc.getSampleNames() ) { - //System.out.printf("Checking %s %b%n", name, names.contains(name)); - if ( names.contains(name) ) - throw new UserException("REQUIRE_UNIQUE sample names is true but duplicate names were discovered " + name); - } - - names.addAll(vc.getSampleNames()); - } - } - - - static private Allele determineReferenceAllele(List VCs) { - Allele ref = null; - - for ( VariantContext vc : VCs ) { - Allele myRef = vc.getReference(); - if ( ref == null || ref.length() < myRef.length() ) - ref = myRef; - else if ( ref.length() == myRef.length() && ! ref.equals(myRef) ) - throw new UserException.BadInput(String.format("The provided variant file(s) have inconsistent references for the same position(s) at %s:%d, %s vs. %s", vc.getChr(), vc.getStart(), ref, myRef)); - } - - return ref; - } - - static private AlleleMapper resolveIncompatibleAlleles(Allele refAllele, VariantContext vc, Set allAlleles) { - if ( refAllele.equals(vc.getReference()) ) - return new AlleleMapper(vc); - else { - // we really need to do some work. The refAllele is the longest reference allele seen at this - // start site. So imagine it is: - // - // refAllele: ACGTGA - // myRef: ACGT - // myAlt: - - // - // We need to remap all of the alleles in vc to include the extra GA so that - // myRef => refAllele and myAlt => GA - // - - Allele myRef = vc.getReference(); - if ( refAllele.length() <= myRef.length() ) throw new ReviewedStingException("BUG: myRef="+myRef+" is longer than refAllele="+refAllele); - byte[] extraBases = Arrays.copyOfRange(refAllele.getBases(), myRef.length(), refAllele.length()); - -// System.out.printf("Remapping allele at %s%n", vc); -// System.out.printf("ref %s%n", refAllele); -// System.out.printf("myref %s%n", myRef ); -// System.out.printf("extrabases %s%n", new String(extraBases)); - - Map map = new HashMap(); - for ( Allele a : vc.getAlleles() ) { - if ( a.isReference() ) - map.put(a, refAllele); - else { - Allele extended = Allele.extend(a, extraBases); - for ( Allele b : allAlleles ) - if ( extended.equals(b) ) - extended = b; -// System.out.printf(" Extending %s => %s%n", a, extended); - map.put(a, extended); - } - } - - // debugging -// System.out.printf("mapping %s%n", map); - - return new AlleleMapper(map); - } - } - - static class CompareByPriority implements Comparator, Serializable { - List priorityListOfVCs; - public CompareByPriority(List priorityListOfVCs) { - this.priorityListOfVCs = priorityListOfVCs; - } - - private int getIndex(VariantContext vc) { - int i = priorityListOfVCs.indexOf(vc.getSource()); - if ( i == -1 ) throw new UserException.BadArgumentValue(Utils.join(",", priorityListOfVCs), "Priority list " + priorityListOfVCs + " doesn't contain variant context " + vc.getSource()); - return i; - } - - public int compare(VariantContext vc1, VariantContext vc2) { - return Integer.valueOf(getIndex(vc1)).compareTo(getIndex(vc2)); - } - } - - public static List sortVariantContextsByPriority(Collection unsortedVCs, List priorityListOfVCs, GenotypeMergeType mergeOption ) { - if ( mergeOption == GenotypeMergeType.PRIORITIZE && priorityListOfVCs == null ) - throw new IllegalArgumentException("Cannot merge calls by priority with a null priority list"); - - if ( priorityListOfVCs == null || mergeOption == GenotypeMergeType.UNSORTED ) - return new ArrayList(unsortedVCs); - else { - ArrayList sorted = new ArrayList(unsortedVCs); - Collections.sort(sorted, new CompareByPriority(priorityListOfVCs)); - return sorted; - } - } - - private static void mergeGenotypes(Map mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { - for ( Genotype g : oneVC.getGenotypes().values() ) { - String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); - if ( ! mergedGenotypes.containsKey(name) ) { - // only add if the name is new - Genotype newG = g; - - if ( uniqifySamples || alleleMapping.needsRemapping() ) { - MutableGenotype mutG = new MutableGenotype(name, g); - if ( alleleMapping.needsRemapping() ) mutG.setAlleles(alleleMapping.remap(g.getAlleles())); - newG = mutG; - } - - mergedGenotypes.put(name, newG); - } - } - } - - public static String mergedSampleName(String trackName, String sampleName, boolean uniqify ) { - return uniqify ? sampleName + "." + trackName : sampleName; - } - - /** - * Returns a context identical to this with the REF and ALT alleles reverse complemented. - * - * @param vc variant context - * @return new vc - */ - public static VariantContext reverseComplement(VariantContext vc) { - // create a mapping from original allele to reverse complemented allele - HashMap alleleMap = new HashMap(vc.getAlleles().size()); - for ( Allele originalAllele : vc.getAlleles() ) { - Allele newAllele; - if ( originalAllele.isNoCall() || originalAllele.isNull() ) - newAllele = originalAllele; - else - newAllele = Allele.create(BaseUtils.simpleReverseComplement(originalAllele.getBases()), originalAllele.isReference()); - alleleMap.put(originalAllele, newAllele); - } - - // create new Genotype objects - Map newGenotypes = new HashMap(vc.getNSamples()); - for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { - List newAlleles = new ArrayList(); - for ( Allele allele : genotype.getValue().getAlleles() ) { - Allele newAllele = alleleMap.get(allele); - if ( newAllele == null ) - newAllele = Allele.NO_CALL; - newAlleles.add(newAllele); - } - newGenotypes.put(genotype.getKey(), Genotype.modifyAlleles(genotype.getValue(), newAlleles)); - } - - return new VariantContext(vc.getSource(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes()); - - } - - public static VariantContext purgeUnallowedGenotypeAttributes(VariantContext vc, Set allowedAttributes) { - if ( allowedAttributes == null ) - return vc; - - Map newGenotypes = new HashMap(vc.getNSamples()); - for ( Map.Entry genotype : vc.getGenotypes().entrySet() ) { - Map attrs = new HashMap(); - for ( Map.Entry attr : genotype.getValue().getAttributes().entrySet() ) { - if ( allowedAttributes.contains(attr.getKey()) ) - attrs.put(attr.getKey(), attr.getValue()); - } - newGenotypes.put(genotype.getKey(), Genotype.modifyAttributes(genotype.getValue(), attrs)); - } - - return VariantContext.modifyGenotypes(vc, newGenotypes); - } - - public static BaseUtils.BaseSubstitutionType getSNPSubstitutionType(VariantContext context) { - if (!context.isSNP() || !context.isBiallelic()) - throw new IllegalStateException("Requested SNP substitution type for bialleic non-SNP " + context); - return BaseUtils.SNPSubstitutionType(context.getReference().getBases()[0], context.getAlternateAllele(0).getBases()[0]); - } - - /** - * If this is a BiAlleic SNP, is it a transition? - */ - public static boolean isTransition(VariantContext context) { - return getSNPSubstitutionType(context) == BaseUtils.BaseSubstitutionType.TRANSITION; - } - - /** - * If this is a BiAlleic SNP, is it a transversion? - */ - public static boolean isTransversion(VariantContext context) { - return getSNPSubstitutionType(context) == BaseUtils.BaseSubstitutionType.TRANSVERSION; - } - - /** - * create a genome location, given a variant context - * @param genomeLocParser parser - * @param vc the variant context - * @return the genomeLoc - */ - public static final GenomeLoc getLocation(GenomeLocParser genomeLocParser,VariantContext vc) { - return genomeLocParser.createGenomeLoc(vc.getChr(), vc.getStart(), vc.getEnd(), true); - } - - public abstract static class AlleleMergeRule { - // vc1, vc2 are ONLY passed to allelesShouldBeMerged() if mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2) AND allSamplesAreMergeable(vc1, vc2): - abstract public boolean allelesShouldBeMerged(VariantContext vc1, VariantContext vc2); - - public String toString() { - return "all samples are mergeable"; - } - } - - // NOTE: returns null if vc1 and vc2 are not merged into a single MNP record - - public static VariantContext mergeIntoMNP(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile, AlleleMergeRule alleleMergeRule) { - if (!mergeIntoMNPvalidationCheck(genomeLocParser, vc1, vc2)) - return null; - - // Check that it's logically possible to merge the VCs: - if (!allSamplesAreMergeable(vc1, vc2)) - return null; - - // Check if there's a "point" in merging the VCs (e.g., annotations could be changed) - if (!alleleMergeRule.allelesShouldBeMerged(vc1, vc2)) - return null; - - return reallyMergeIntoMNP(vc1, vc2, referenceFile); - } - - private static VariantContext reallyMergeIntoMNP(VariantContext vc1, VariantContext vc2, ReferenceSequenceFile referenceFile) { - int startInter = vc1.getEnd() + 1; - int endInter = vc2.getStart() - 1; - byte[] intermediateBases = null; - if (startInter <= endInter) { - intermediateBases = referenceFile.getSubsequenceAt(vc1.getChr(), startInter, endInter).getBases(); - StringUtil.toUpperCase(intermediateBases); - } - MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added - - Map mergedGenotypes = new HashMap(); - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); - - List site1Alleles = gt1.getAlleles(); - List site2Alleles = gt2.getAlleles(); - - List mergedAllelesForSample = new LinkedList(); - - /* NOTE: Since merged alleles are added to mergedAllelesForSample in the SAME order as in the input VC records, - we preserve phase information (if any) relative to whatever precedes vc1: - */ - Iterator all2It = site2Alleles.iterator(); - for (Allele all1 : site1Alleles) { - Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() - - Allele mergedAllele = mergeData.ensureMergedAllele(all1, all2); - mergedAllelesForSample.add(mergedAllele); - } - - double mergedGQ = Math.max(gt1.getNegLog10PError(), gt2.getNegLog10PError()); - Set mergedGtFilters = new HashSet(); // Since gt1 and gt2 were unfiltered, the Genotype remains unfiltered - - Map mergedGtAttribs = new HashMap(); - PhaseAndQuality phaseQual = calcPhaseForMergedGenotypes(gt1, gt2); - if (phaseQual.PQ != null) - mergedGtAttribs.put(ReadBackedPhasingWalker.PQ_KEY, phaseQual.PQ); - - Genotype mergedGt = new Genotype(sample, mergedAllelesForSample, mergedGQ, mergedGtFilters, mergedGtAttribs, phaseQual.isPhased); - mergedGenotypes.put(sample, mergedGt); - } - - String mergedName = VariantContextUtils.mergeVariantContextNames(vc1.getSource(), vc2.getSource()); - double mergedNegLog10PError = Math.max(vc1.getNegLog10PError(), vc2.getNegLog10PError()); - Set mergedFilters = new HashSet(); // Since vc1 and vc2 were unfiltered, the merged record remains unfiltered - Map mergedAttribs = VariantContextUtils.mergeVariantContextAttributes(vc1, vc2); - - VariantContext mergedVc = new VariantContext(mergedName, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); - - mergedAttribs = new HashMap(mergedVc.getAttributes()); - VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); - mergedVc = VariantContext.modifyAttributes(mergedVc, mergedAttribs); - - return mergedVc; - } - - private static class AlleleOneAndTwo { - private Allele all1; - private Allele all2; - - public AlleleOneAndTwo(Allele all1, Allele all2) { - this.all1 = all1; - this.all2 = all2; - } - - public int hashCode() { - return all1.hashCode() + all2.hashCode(); - } - - public boolean equals(Object other) { - if (!(other instanceof AlleleOneAndTwo)) - return false; - - AlleleOneAndTwo otherAot = (AlleleOneAndTwo) other; - return (this.all1.equals(otherAot.all1) && this.all2.equals(otherAot.all2)); - } - } - - private static class MergedAllelesData { - private Map mergedAlleles; - private byte[] intermediateBases; - private int intermediateLength; - - public MergedAllelesData(byte[] intermediateBases, VariantContext vc1, VariantContext vc2) { - this.mergedAlleles = new HashMap(); // implemented equals() and hashCode() for AlleleOneAndTwo - this.intermediateBases = intermediateBases; - this.intermediateLength = this.intermediateBases != null ? this.intermediateBases.length : 0; - - this.ensureMergedAllele(vc1.getReference(), vc2.getReference(), true); - } - - public Allele ensureMergedAllele(Allele all1, Allele all2) { - return ensureMergedAllele(all1, all2, false); // false <-> since even if all1+all2 = reference, it was already created in the constructor - } - - private Allele ensureMergedAllele(Allele all1, Allele all2, boolean creatingReferenceForFirstTime) { - AlleleOneAndTwo all12 = new AlleleOneAndTwo(all1, all2); - Allele mergedAllele = mergedAlleles.get(all12); - - if (mergedAllele == null) { - byte[] bases1 = all1.getBases(); - byte[] bases2 = all2.getBases(); - - byte[] mergedBases = new byte[bases1.length + intermediateLength + bases2.length]; - System.arraycopy(bases1, 0, mergedBases, 0, bases1.length); - if (intermediateBases != null) - System.arraycopy(intermediateBases, 0, mergedBases, bases1.length, intermediateLength); - System.arraycopy(bases2, 0, mergedBases, bases1.length + intermediateLength, bases2.length); - - mergedAllele = Allele.create(mergedBases, creatingReferenceForFirstTime); - mergedAlleles.put(all12, mergedAllele); - } - - return mergedAllele; - } - - public Set getAllMergedAlleles() { - return new HashSet(mergedAlleles.values()); - } - } - - private static String mergeVariantContextNames(String name1, String name2) { - return name1 + "_" + name2; - } - - private static Map mergeVariantContextAttributes(VariantContext vc1, VariantContext vc2) { - Map mergedAttribs = new HashMap(); - - List vcList = new LinkedList(); - vcList.add(vc1); - vcList.add(vc2); - - String[] MERGE_OR_ATTRIBS = {VCFConstants.DBSNP_KEY}; - for (String orAttrib : MERGE_OR_ATTRIBS) { - boolean attribVal = false; - for (VariantContext vc : vcList) { - attribVal = vc.getAttributeAsBoolean(orAttrib, false); - if (attribVal) // already true, so no reason to continue: - break; - } - mergedAttribs.put(orAttrib, attribVal); - } - - // Merge ID fields: - String iDVal = null; - for (VariantContext vc : vcList) { - String val = vc.getAttributeAsString(VariantContext.ID_KEY, null); - if (val != null && !val.equals(VCFConstants.EMPTY_ID_FIELD)) { - if (iDVal == null) - iDVal = val; - else - iDVal += VCFConstants.ID_FIELD_SEPARATOR + val; - } - } - if (iDVal != null) - mergedAttribs.put(VariantContext.ID_KEY, iDVal); - - return mergedAttribs; - } - - private static boolean mergeIntoMNPvalidationCheck(GenomeLocParser genomeLocParser, VariantContext vc1, VariantContext vc2) { - GenomeLoc loc1 = VariantContextUtils.getLocation(genomeLocParser, vc1); - GenomeLoc loc2 = VariantContextUtils.getLocation(genomeLocParser, vc2); - - if (!loc1.onSameContig(loc2)) - throw new ReviewedStingException("Can only merge vc1, vc2 if on the same chromosome"); - - if (!loc1.isBefore(loc2)) - throw new ReviewedStingException("Can only merge if vc1 is BEFORE vc2"); - - if (vc1.isFiltered() || vc2.isFiltered()) - return false; - - if (!vc1.getSampleNames().equals(vc2.getSampleNames())) // vc1, vc2 refer to different sample sets - return false; - - if (!allGenotypesAreUnfilteredAndCalled(vc1) || !allGenotypesAreUnfilteredAndCalled(vc2)) - return false; - - return true; - } - - private static boolean allGenotypesAreUnfilteredAndCalled(VariantContext vc) { - for (Map.Entry gtEntry : vc.getGenotypes().entrySet()) { - Genotype gt = gtEntry.getValue(); - if (gt.isNoCall() || gt.isFiltered()) - return false; - } - - return true; - } - - // Assumes that vc1 and vc2 were already checked to have the same sample names: - - private static boolean allSamplesAreMergeable(VariantContext vc1, VariantContext vc2) { - // Check that each sample's genotype in vc2 is uniquely appendable onto its genotype in vc1: - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); - - if (!alleleSegregationIsKnown(gt1, gt2)) // can merge if: phased, or if either is a hom - return false; - } - - return true; - } - - public static boolean alleleSegregationIsKnown(Genotype gt1, Genotype gt2) { - if (gt1.getPloidy() != gt2.getPloidy()) - return false; - - /* If gt2 is phased or hom, then could even be MERGED with gt1 [This is standard]. - - HOWEVER, EVEN if this is not the case, but gt1.isHom(), - it is trivially known that each of gt2's alleles segregate with the single allele type present in gt1. - */ - return (gt2.isPhased() || gt2.isHom() || gt1.isHom()); - } - - private static class PhaseAndQuality { - public boolean isPhased; - public Double PQ = null; - - public PhaseAndQuality(Genotype gt) { - this.isPhased = gt.isPhased(); - if (this.isPhased) { - this.PQ = gt.getAttributeAsDouble(ReadBackedPhasingWalker.PQ_KEY, -1); - if ( this.PQ == -1 ) this.PQ = null; - } - } - } - - // Assumes that alleleSegregationIsKnown(gt1, gt2): - - private static PhaseAndQuality calcPhaseForMergedGenotypes(Genotype gt1, Genotype gt2) { - if (gt2.isPhased() || gt2.isHom()) - return new PhaseAndQuality(gt1); // maintain the phase of gt1 - - if (!gt1.isHom()) - throw new ReviewedStingException("alleleSegregationIsKnown(gt1, gt2) implies: gt2.genotypesArePhased() || gt2.isHom() || gt1.isHom()"); - - /* We're dealing with: gt1.isHom(), gt2.isHet(), !gt2.genotypesArePhased(); so, the merged (het) Genotype is not phased relative to the previous Genotype - - For example, if we're merging the third Genotype with the second one: - 0/1 - 1|1 - 0/1 - - Then, we want to output: - 0/1 - 1/2 - */ - return new PhaseAndQuality(gt2); // maintain the phase of gt2 [since !gt2.genotypesArePhased()] - } - - /* Checks if any sample has a MNP of ALT alleles (segregating together): - [Assumes that vc1 and vc2 were already checked to have the same sample names && allSamplesAreMergeable(vc1, vc2)] - */ - - public static boolean someSampleHasDoubleNonReferenceAllele(VariantContext vc1, VariantContext vc2) { - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); - - List site1Alleles = gt1.getAlleles(); - List site2Alleles = gt2.getAlleles(); - - Iterator all2It = site2Alleles.iterator(); - for (Allele all1 : site1Alleles) { - Allele all2 = all2It.next(); // this is OK, since allSamplesAreMergeable() - - if (all1.isNonReference() && all2.isNonReference()) // corresponding alleles are alternate - return true; - } - } - - return false; - } - - /* Checks if all samples are consistent in their haplotypes: - [Assumes that vc1 and vc2 were already checked to have the same sample names && allSamplesAreMergeable(vc1, vc2)] - */ - - public static boolean doubleAllelesSegregatePerfectlyAmongSamples(VariantContext vc1, VariantContext vc2) { - // Check that Alleles at vc1 and at vc2 always segregate together in all samples (including reference): - Map allele1ToAllele2 = new HashMap(); - Map allele2ToAllele1 = new HashMap(); - - // Note the segregation of the alleles for the reference genome: - allele1ToAllele2.put(vc1.getReference(), vc2.getReference()); - allele2ToAllele1.put(vc2.getReference(), vc1.getReference()); - - // Note the segregation of the alleles for each sample (and check that it is consistent with the reference and all previous samples). - for (Map.Entry gt1Entry : vc1.getGenotypes().entrySet()) { - String sample = gt1Entry.getKey(); - Genotype gt1 = gt1Entry.getValue(); - Genotype gt2 = vc2.getGenotype(sample); - - List site1Alleles = gt1.getAlleles(); - List site2Alleles = gt2.getAlleles(); - - Iterator all2It = site2Alleles.iterator(); - for (Allele all1 : site1Alleles) { - Allele all2 = all2It.next(); - - Allele all1To2 = allele1ToAllele2.get(all1); - if (all1To2 == null) - allele1ToAllele2.put(all1, all2); - else if (!all1To2.equals(all2)) // all1 segregates with two different alleles at site 2 - return false; - - Allele all2To1 = allele2ToAllele1.get(all2); - if (all2To1 == null) - allele2ToAllele1.put(all2, all1); - else if (!all2To1.equals(all1)) // all2 segregates with two different alleles at site 1 - return false; - } - } - - return true; - } -} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java deleted file mode 100644 index f16861f30..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/v13/VariantJEXLContext.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (c) 2010. The Broad Institute - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.utils.variantcontext.v13; - -import org.apache.commons.jexl2.JexlContext; -import org.apache.commons.jexl2.MapContext; -import org.broadinstitute.sting.utils.Utils; -import org.broadinstitute.sting.utils.exceptions.UserException; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * - * @author aaron - * @author depristo - * - * Class VariantJEXLContext - * - * implements the JEXML context for VariantContext; this saves us from - * having to generate a JEXML context lookup map everytime we want to evaluate an expression. - * - * This is package protected, only classes in variantcontext should have access to it. - * - * // todo -- clean up to remove or better support genotype filtering - */ - -class VariantJEXLContext implements JexlContext { - // our stored variant context - private VariantContext vc; - - private interface AttributeGetter { - public Object get(VariantContext vc); - } - - private static Map x = new HashMap(); - - static { - x.put("vc", new AttributeGetter() { public Object get(VariantContext vc) { return vc; }}); - x.put("CHROM", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getChr(); }}); - x.put("POS", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getStart(); }}); - x.put("TYPE", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getType().toString(); }}); - x.put("QUAL", new AttributeGetter() { public Object get(VariantContext vc) { return 10 * vc.getNegLog10PError(); }}); - x.put("ALLELES", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getAlleles(); }}); - x.put("N_ALLELES", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getNAlleles(); }}); - x.put("FILTER", new AttributeGetter() { public Object get(VariantContext vc) { return vc.isFiltered() ? "1" : "0"; }}); - -// x.put("GT", new AttributeGetter() { public Object get(VariantContext vc) { return g.getGenotypeString(); }}); - x.put("homRefCount", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getHomRefCount(); }}); - x.put("hetCount", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getHetCount(); }}); - x.put("homVarCount", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getHomVarCount(); }}); - } - - public VariantJEXLContext(VariantContext vc) { - this.vc = vc; - } - - public Object get(String name) { - Object result = null; - if ( x.containsKey(name) ) { // dynamic resolution of name -> value via map - result = x.get(name).get(vc); - } else if ( vc.hasAttribute(name)) { - result = vc.getAttribute(name); - } else if ( vc.getFilters().contains(name) ) { - result = "1"; - } - - //System.out.printf("dynamic lookup %s => %s%n", name, result); - - return result; - } - - public boolean has(String name) { - return get(name) != null; - } - - public void set(String name, Object value) { - throw new UnsupportedOperationException("remove() not supported on a VariantJEXLContext"); - } -} - - - - -/** - * this is an implementation of a Map of JexlVCMatchExp to true or false values. It lazy initializes each value - * as requested to save as much processing time as possible. - * - * Compatible with JEXL 1.1 (this code will be easier if we move to 2.0, all of the functionality can go into the - * JexlContext's get() - * - */ - -class JEXLMap implements Map { - // our variant context and/or Genotype - private final VariantContext vc; - private final Genotype g; - - // our context - private JexlContext jContext = null; - - // our mapping from JEXLVCMatchExp to Booleans, which will be set to NULL for previously uncached JexlVCMatchExp - private Map jexl; - - - public JEXLMap(Collection jexlCollection, VariantContext vc, Genotype g) { - this.vc = vc; - this.g = g; - initialize(jexlCollection); - } - - public JEXLMap(Collection jexlCollection, VariantContext vc) { - this(jexlCollection, vc, null); - } - - private void initialize(Collection jexlCollection) { - jexl = new HashMap(); - for (VariantContextUtils.JexlVCMatchExp exp: jexlCollection) { - jexl.put(exp, null); - } - } - - /** - * create the internal JexlContext, only when required. This code is where new JEXL context variables - * should get added. - * - */ - private void createContext() { - if ( g == null ) { - // todo -- remove dependancy on g to the entire system - jContext = new VariantJEXLContext(vc); - } else { - // - // this whole branch is here just to support G jexl operations - // - Map infoMap = new HashMap(); - - if ( vc != null ) { - // create a mapping of what we know about the variant context, its Chromosome, positions, etc. - infoMap.put("CHROM", vc.getChr()); - infoMap.put("POS", vc.getStart()); - infoMap.put("TYPE", vc.getType().toString()); - infoMap.put("QUAL", String.valueOf(vc.getPhredScaledQual())); - - // add alleles - infoMap.put("ALLELES", Utils.join(";", vc.getAlleles())); - infoMap.put("N_ALLELES", String.valueOf(vc.getNAlleles())); - - // add attributes - addAttributesToMap(infoMap, vc.getAttributes()); - - // add filter fields - infoMap.put("FILTER", vc.isFiltered() ? "1" : "0"); - for ( Object filterCode : vc.getFilters() ) { - infoMap.put(String.valueOf(filterCode), "1"); - } - - // add genotype-specific fields - // TODO -- implement me when we figure out a good way to represent this - // for ( Genotype g : vc.getGenotypes().values() ) { - // String prefix = g.getSampleName() + "."; - // addAttributesToMap(infoMap, g.getAttributes(), prefix); - // infoMap.put(prefix + "GT", g.getGenotypeString()); - // } - - // add specific genotype if one is provided - infoMap.put(VCFConstants.GENOTYPE_KEY, g.getGenotypeString()); - infoMap.put("isHomRef", g.isHomRef() ? "1" : "0"); - infoMap.put("isHet", g.isHet() ? "1" : "0"); - infoMap.put("isHomVar", g.isHomVar() ? "1" : "0"); - infoMap.put(VCFConstants.GENOTYPE_QUALITY_KEY, new Double(g.getPhredScaledQual())); - for ( Map.Entry e : g.getAttributes().entrySet() ) { - if ( e.getValue() != null && !e.getValue().equals(VCFConstants.MISSING_VALUE_v4) ) - infoMap.put(e.getKey(), e.getValue()); - } - } - - // create the internal context that we can evaluate expressions against - - jContext = new MapContext(infoMap); - } - } - - /** - * @return the size of the internal data structure - */ - public int size() { - return jexl.size(); - } - - /** - * @return true if we're empty - */ - public boolean isEmpty() { return this.jexl.isEmpty(); } - - /** - * do we contain the specified key - * @param o the key - * @return true if we have a value for that key - */ - public boolean containsKey(Object o) { return jexl.containsKey(o); } - - public Boolean get(Object o) { - // if we've already determined the value, return it - if (jexl.containsKey(o) && jexl.get(o) != null) return jexl.get(o); - - // try and cast the expression - VariantContextUtils.JexlVCMatchExp e = (VariantContextUtils.JexlVCMatchExp) o; - evaluateExpression(e); - return jexl.get(e); - } - - /** - * get the keyset of map - * @return a set of keys of type JexlVCMatchExp - */ - public Set keySet() { - return jexl.keySet(); - } - - /** - * get all the values of the map. This is an expensive call, since it evaluates all keys that haven't - * been evaluated yet. This is fine if you truely want all the keys, but if you only want a portion, or know - * the keys you want, you would be better off using get() to get them by name. - * @return a collection of boolean values, representing the results of all the variants evaluated - */ - public Collection values() { - // this is an expensive call - for (VariantContextUtils.JexlVCMatchExp exp : jexl.keySet()) - if (jexl.get(exp) == null) - evaluateExpression(exp); - return jexl.values(); - } - - /** - * evaulate a JexlVCMatchExp's expression, given the current context (and setup the context if it's null) - * @param exp the JexlVCMatchExp to evaluate - */ - private void evaluateExpression(VariantContextUtils.JexlVCMatchExp exp) { - // if the context is null, we need to create it to evaluate the JEXL expression - if (this.jContext == null) createContext(); - try { - jexl.put (exp, (Boolean) exp.exp.evaluate(jContext)); - } catch (Exception e) { - throw new UserException.CommandLineException(String.format("Invalid JEXL expression detected for %s with message %s", exp.name, e.getMessage())); - } - } - - /** - * helper function: adds the list of attributes to the information map we're building - * @param infoMap the map - * @param attributes the attributes - */ - private static void addAttributesToMap(Map infoMap, Map attributes ) { - for (Map.Entry e : attributes.entrySet()) { - infoMap.put(e.getKey(), String.valueOf(e.getValue())); - } - } - - public Boolean put(VariantContextUtils.JexlVCMatchExp jexlVCMatchExp, Boolean aBoolean) { - return jexl.put(jexlVCMatchExp,aBoolean); - } - - public void putAll(Map map) { - jexl.putAll(map); - } - - // ////////////////////////////////////////////////////////////////////////////////////// - // The Following are unsupported at the moment - // ////////////////////////////////////////////////////////////////////////////////////// - - // this doesn't make much sense to implement, boolean doesn't offer too much variety to deal - // with evaluating every key in the internal map. - public boolean containsValue(Object o) { - throw new UnsupportedOperationException("containsValue() not supported on a JEXLMap"); - } - - // this doesn't make much sense - public Boolean remove(Object o) { - throw new UnsupportedOperationException("remove() not supported on a JEXLMap"); - } - - - public Set> entrySet() { - throw new UnsupportedOperationException("clear() not supported on a JEXLMap"); - } - - // nope - public void clear() { - throw new UnsupportedOperationException("clear() not supported on a JEXLMap"); - } -} From aa0610ea929f98a4ee4e8a56eec195be8e37abd2 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 16:24:05 -0500 Subject: [PATCH 042/113] GenotypeCollection renamed to GenotypesContext --- .../gatk/refdata/VariantContextAdaptors.java | 6 +-- .../gatk/walkers/annotator/AlleleBalance.java | 4 +- .../gatk/walkers/annotator/HardyWeinberg.java | 4 +- .../walkers/annotator/InbreedingCoeff.java | 4 +- .../gatk/walkers/annotator/QualByDepth.java | 4 +- .../gatk/walkers/annotator/RankSumTest.java | 4 +- .../annotator/VariantAnnotatorEngine.java | 6 +-- .../beagle/BeagleOutputToVCFWalker.java | 4 +- .../beagle/ProduceBeagleInputWalker.java | 4 +- .../filters/VariantFiltrationWalker.java | 6 +-- .../AlleleFrequencyCalculationModel.java | 8 ++-- .../genotyper/ExactAFCalculationModel.java | 14 +++--- .../walkers/genotyper/UGCallVariants.java | 8 ++-- .../genotyper/UnifiedGenotyperEngine.java | 6 +-- .../indels/SomaticIndelDetectorWalker.java | 6 +-- .../walkers/phasing/PhaseByTransmission.java | 6 +-- .../gatk/walkers/phasing/PhasingUtils.java | 2 +- .../phasing/ReadBackedPhasingWalker.java | 6 +-- .../evaluators/GenotypePhasingEvaluator.java | 6 +-- .../variantutils/LeftAlignVariants.java | 4 +- .../walkers/variantutils/SelectVariants.java | 2 +- .../walkers/variantutils/VariantsToVCF.java | 2 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 4 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 6 +-- .../sting/utils/codecs/vcf/VCFCodec.java | 6 +-- .../sting/utils/codecs/vcf/VCFParser.java | 4 +- .../broadinstitute/sting/utils/gcf/GCF.java | 8 ++-- ...eCollection.java => GenotypesContext.java} | 48 +++++++++---------- .../utils/variantcontext/VariantContext.java | 35 +++++++------- .../variantcontext/VariantContextUtils.java | 16 +++---- .../utils/genotype/vcf/VCFWriterUnitTest.java | 4 +- .../VariantContextBenchmark.java | 2 +- .../VariantContextUtilsUnitTest.java | 4 +- 33 files changed, 124 insertions(+), 129 deletions(-) rename public/java/src/org/broadinstitute/sting/utils/variantcontext/{GenotypeCollection.java => GenotypesContext.java} (86%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index c5b8b628a..081f86ab9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -10,8 +10,6 @@ import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.classloader.PluginManager; import org.broadinstitute.sting.utils.codecs.hapmap.RawHapMapFeature; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -195,7 +193,7 @@ public class VariantContextAdaptors { return null; // we weren't given enough reference context to create the VariantContext Byte refBaseForIndel = new Byte(ref.getBases()[index]); - GenotypeCollection genotypes = null; + GenotypesContext genotypes = null; VariantContext vc = new VariantContext(name, dbsnp.getRsID(), dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0), alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attributes, refBaseForIndel); return vc; } else @@ -316,7 +314,7 @@ public class VariantContextAdaptors { String[] samples = hapmap.getSampleIDs(); String[] genotypeStrings = hapmap.getGenotypes(); - GenotypeCollection genotypes = GenotypeCollection.create(samples.length); + GenotypesContext genotypes = GenotypesContext.create(samples.length); for ( int i = 0; i < samples.length; i++ ) { // ignore bad genotypes if ( genotypeStrings[i].contains("N") ) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java index c345c8741..4a13fccc6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java @@ -35,7 +35,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -55,7 +55,7 @@ public class AlleleBalance extends InfoFieldAnnotation { if ( !vc.isBiallelic() ) return null; - final GenotypeCollection genotypes = vc.getGenotypes(); + final GenotypesContext genotypes = vc.getGenotypes(); if ( !vc.hasGenotypes() ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java index 164c77d1c..33f2f1dd3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java @@ -11,7 +11,7 @@ import org.broadinstitute.sting.utils.QualityUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -31,7 +31,7 @@ public class HardyWeinberg extends InfoFieldAnnotation implements WorkInProgress public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - final GenotypeCollection genotypes = vc.getGenotypes(); + final GenotypesContext genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() < MIN_SAMPLES ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java index 917a75294..640ab036b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/InbreedingCoeff.java @@ -10,7 +10,7 @@ import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -33,7 +33,7 @@ public class InbreedingCoeff extends InfoFieldAnnotation implements StandardAnno public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - final GenotypeCollection genotypes = vc.getGenotypes(); + final GenotypesContext genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() < MIN_SAMPLES ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java index 3a1f2cc87..0653b6015 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java @@ -9,7 +9,7 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.StandardAnnota import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Arrays; @@ -29,7 +29,7 @@ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotati if ( stratifiedContexts.size() == 0 ) return null; - final GenotypeCollection genotypes = vc.getGenotypes(); + final GenotypesContext genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java index 97e014373..ebf33496f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java @@ -13,7 +13,7 @@ import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.ArrayList; @@ -33,7 +33,7 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar if ( stratifiedContexts.size() == 0 ) return null; - final GenotypeCollection genotypes = vc.getGenotypes(); + final GenotypesContext genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index 48fcdec10..9f0353eb9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -34,7 +34,7 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.*; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; @@ -225,11 +225,11 @@ public class VariantAnnotatorEngine { } } - private GenotypeCollection annotateGenotypes(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { + private GenotypesContext annotateGenotypes(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { if ( requestedGenotypeAnnotations.size() == 0 ) return vc.getGenotypes(); - GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); + GenotypesContext genotypes = GenotypesContext.create(vc.getNSamples()); for ( final Genotype genotype : vc.getGenotypes() ) { AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 297203aec..649b7621b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -187,7 +187,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { byte refByte = ref.getBase(); // make new Genotypes based on Beagle results - GenotypeCollection genotypes = GenotypeCollection.create(vc_input.getGenotypes().size()); + GenotypesContext genotypes = GenotypesContext.create(vc_input.getGenotypes().size()); // for each genotype, create a new object with Beagle information on it @@ -196,7 +196,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { Double alleleFrequencyH = 0.0; int beagleVarCounts = 0; - GenotypeCollection hapmapGenotypes = null; + GenotypesContext hapmapGenotypes = null; if (vc_comp != null) { hapmapGenotypes = vc_comp.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java index f7a84ee08..1d6eb4b64 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java @@ -242,8 +242,8 @@ public class ProduceBeagleInputWalker extends RodWalker { } if ( markers != null ) markers.append("\n"); - GenotypeCollection preferredGenotypes = preferredVC.getGenotypes(); - GenotypeCollection otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; + GenotypesContext preferredGenotypes = preferredVC.getGenotypes(); + GenotypesContext otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; for ( String sample : samples ) { boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSample(sample).getGender() == Gender.MALE; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index 6f482b6f2..409e180ae 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -37,7 +37,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -283,11 +283,11 @@ public class VariantFiltrationWalker extends RodWalker { VariantContext vc = context.getVariantContext(); // make new Genotypes based on filters - GenotypeCollection genotypes; + GenotypesContext genotypes; if ( genotypeFilterExps.size() == 0 ) { genotypes = null; } else { - genotypes = GenotypeCollection.create(vc.getGenotypes().size()); + genotypes = GenotypesContext.create(vc.getGenotypes().size()); // for each genotype, check filters then create a new object for ( final Genotype g : vc.getGenotypes() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java index b81c1d4c3..a8ce98945 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationModel.java @@ -27,13 +27,11 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; import java.util.List; -import java.util.Map; /** @@ -69,7 +67,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * @param log10AlleleFrequencyPriors priors * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store results */ - protected abstract void getLog10PNonRef(GenotypeCollection GLs, List Alleles, + protected abstract void getLog10PNonRef(GenotypesContext GLs, List Alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors); @@ -81,7 +79,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * * @return calls */ - protected abstract GenotypeCollection assignGenotypes(VariantContext vc, + protected abstract GenotypesContext assignGenotypes(VariantContext vc, double[] log10AlleleFrequencyPosteriors, int AFofMaxLikelihood); } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index f0c73cd5f..980088305 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -31,7 +31,7 @@ import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; @@ -50,7 +50,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { super(UAC, N, logger, verboseWriter); } - public void getLog10PNonRef(GenotypeCollection GLs, List alleles, + public void getLog10PNonRef(GenotypesContext GLs, List alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); @@ -94,7 +94,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - private static final ArrayList getGLs(GenotypeCollection GLs) { + private static final ArrayList getGLs(GenotypesContext GLs) { ArrayList genotypeLikelihoods = new ArrayList(); genotypeLikelihoods.add(new double[]{0.0,0.0,0.0}); // dummy @@ -154,7 +154,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - public int linearExact(GenotypeCollection GLs, + public int linearExact(GenotypesContext GLs, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors, int idxAA, int idxAB, int idxBB) { final ArrayList genotypeLikelihoods = getGLs(GLs); @@ -267,14 +267,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { * * @return calls */ - public GenotypeCollection assignGenotypes(VariantContext vc, + public GenotypesContext assignGenotypes(VariantContext vc, double[] log10AlleleFrequencyPosteriors, int AFofMaxLikelihood) { if ( !vc.isVariant() ) throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); - GenotypeCollection GLs = vc.getGenotypes(); + GenotypesContext GLs = vc.getGenotypes(); double[][] pathMetricArray = new double[GLs.size()+1][AFofMaxLikelihood+1]; int[][] tracebackArray = new int[GLs.size()+1][AFofMaxLikelihood+1]; @@ -341,7 +341,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - GenotypeCollection calls = GenotypeCollection.create(); + GenotypesContext calls = GenotypesContext.create(); int startIdx = AFofMaxLikelihood; for (int k = sampleIdx; k > 0; k--) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index 00317eec6..6dc31edb8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -36,7 +36,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -129,7 +129,7 @@ public class UGCallVariants extends RodWalker { return null; VariantContext variantVC = null; - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypesContext genotypes = GenotypesContext.create(); for ( VariantContext vc : VCs ) { if ( variantVC == null && vc.isVariant() ) variantVC = vc; @@ -143,8 +143,8 @@ public class UGCallVariants extends RodWalker { return new VariantContext("VCwithGLs", VCFConstants.EMPTY_ID_FIELD, variantVC.getChr(), variantVC.getStart(), variantVC.getEnd(), variantVC.getAlleles(), genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null); } - private static GenotypeCollection getGenotypesWithGLs(GenotypeCollection genotypes) { - GenotypeCollection genotypesWithGLs = GenotypeCollection.create(genotypes.size()); + private static GenotypesContext getGenotypesWithGLs(GenotypesContext genotypes) { + GenotypesContext genotypesWithGLs = GenotypesContext.create(genotypes.size()); for ( final Genotype g : genotypes ) { if ( g.hasLikelihoods() && g.getLikelihoods().getAsVector() != null ) genotypesWithGLs.add(g); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index 4f87f5eb0..5692c2525 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -265,7 +265,7 @@ public class UnifiedGenotyperEngine { alleles.add(refAllele); boolean addedAltAlleles = false; - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypesContext genotypes = GenotypesContext.create(); for ( MultiallelicGenotypeLikelihoods GL : GLs.values() ) { if ( !addedAltAlleles ) { addedAltAlleles = true; @@ -354,7 +354,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypeCollection genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); // print out stats if we have a writer if ( verboseWriter != null ) @@ -491,7 +491,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypeCollection genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); // *** note that calculating strand bias involves overwriting data structures, so we do that last HashMap attributes = new HashMap(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index df1d081c6..e3dc59b19 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -60,7 +60,7 @@ import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -1058,7 +1058,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { stop += event_length; } - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypesContext genotypes = GenotypesContext.create(); for ( String sample : normalSamples ) { Map attrs = call.makeStatsAttributes(null); @@ -1147,7 +1147,7 @@ public class SomaticIndelDetectorWalker extends ReadWalker { homRefAlleles.add( alleles.get(0)); homRefAlleles.add( alleles.get(0)); - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypesContext genotypes = GenotypesContext.create(); for ( String sample : normalSamples ) { genotypes.add(new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsNormal,false)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 2a3e353ef..0b28459d4 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -293,7 +293,7 @@ public class PhaseByTransmission extends RodWalker { if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); - GenotypeCollection genotypeCollection = GenotypeCollection.create(vc.getGenotypes().size()); + GenotypesContext genotypesContext = GenotypesContext.create(vc.getGenotypes().size()); for (Trio trio : trios) { Genotype mother = vc.getGenotype(trio.getMother()); @@ -306,10 +306,10 @@ public class PhaseByTransmission extends RodWalker { Genotype phasedFather = trioGenotypes.get(1); Genotype phasedChild = trioGenotypes.get(2); - genotypeCollection.add(phasedMother, phasedFather, phasedChild); + genotypesContext.add(phasedMother, phasedFather, phasedChild); } - VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypeCollection); + VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypesContext); vcfWriter.add(newvc); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java index ce35baf15..fddef5129 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java @@ -88,7 +88,7 @@ class PhasingUtils { } MergedAllelesData mergeData = new MergedAllelesData(intermediateBases, vc1, vc2); // ensures that the reference allele is added - GenotypeCollection mergedGenotypes = GenotypeCollection.create(); + GenotypesContext mergedGenotypes = GenotypesContext.create(); for (final Genotype gt1 : vc1.getGenotypes()) { Genotype gt2 = vc2.getGenotype(gt1.getSampleName()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index f2d870068..155b37af2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -352,7 +352,7 @@ public class ReadBackedPhasingWalker extends RodWalker samplePhaseStats = new TreeMap(); for (final Genotype gt : sampGenotypes) { String samp = gt.getSampleName(); @@ -1122,7 +1122,7 @@ public class ReadBackedPhasingWalker extends RodWalker alleles; - private GenotypeCollection genotypes; + private GenotypesContext genotypes; private double negLog10PError; private Set filters; private Map attributes; @@ -1135,7 +1135,7 @@ public class ReadBackedPhasingWalker extends RodWalker(vc.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java index 08d62154d..ea12ada48 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypePhasingEvaluator.java @@ -14,7 +14,7 @@ import org.broadinstitute.sting.gatk.walkers.varianteval.util.TableType; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.HashMap; @@ -92,13 +92,13 @@ public class GenotypePhasingEvaluator extends VariantEvaluator { Set allSamples = new HashSet(); - GenotypeCollection compSampGenotypes = null; + GenotypesContext compSampGenotypes = null; if (isRelevantToPhasing(comp)) { allSamples.addAll(comp.getSampleNames()); compSampGenotypes = comp.getGenotypes(); } - GenotypeCollection evalSampGenotypes = null; + GenotypesContext evalSampGenotypes = null; if (isRelevantToPhasing(eval)) { allSamples.addAll(eval.getSampleNames()); evalSampGenotypes = eval.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java index 71e475fe0..4b3271ba6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java @@ -40,7 +40,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; @@ -211,7 +211,7 @@ public class LeftAlignVariants extends RodWalker { } // create new Genotype objects - GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); + GenotypesContext newGenotypes = GenotypesContext.create(vc.getNSamples()); for ( final Genotype genotype : vc.getGenotypes() ) { List newAlleles = new ArrayList(); for ( Allele allele : genotype.getAlleles() ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 6fec0fac2..7f6e605e7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -556,7 +556,7 @@ public class SelectVariants extends RodWalker { return (compVCs == null || compVCs.isEmpty()); // check if we find it in the variant rod - GenotypeCollection genotypes = vc.getGenotypes(samples); + GenotypesContext genotypes = vc.getGenotypes(samples); for (final Genotype g : genotypes) { if (sampleHasVariant(g)) { // There is a variant called (or filtered with not exclude filtered option set) that is not HomRef for at least one of the samples. diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java index 89322e9f9..78cfde1bd 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java @@ -128,7 +128,7 @@ public class VariantsToVCF extends RodWalker { // set the appropriate sample name if necessary if ( sampleName != null && vc.hasGenotypes() && vc.hasGenotype(variants.getName()) ) { Genotype g = Genotype.modifyName(vc.getGenotype(variants.getName()), sampleName); - GenotypeCollection genotypes = GenotypeCollection.create(g); + GenotypesContext genotypes = GenotypesContext.create(g); vc = VariantContext.modifyGenotypes(vc, genotypes); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index 816863b5e..725cc8109 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -11,7 +11,7 @@ import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -76,7 +76,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, * @param pos position * @return a mapping of sample name to genotype object */ - public abstract GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos); + public abstract GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos); /** diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index 4d6f26e87..971400ca0 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -5,7 +5,7 @@ import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; @@ -118,13 +118,13 @@ public class VCF3Codec extends AbstractVCFCodec { * @param pos position * @return a mapping of sample name to genotype object */ - public GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos) { + public GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - GenotypeCollection genotypes = GenotypeCollection.create(nParts); + GenotypesContext genotypes = GenotypesContext.create(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index 696b35050..53b3d5fd4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -5,7 +5,7 @@ import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; @@ -145,13 +145,13 @@ public class VCFCodec extends AbstractVCFCodec { * @param alleles the list of alleles * @return a mapping of sample name to genotype object */ - public GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos) { + public GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - GenotypeCollection genotypes = GenotypeCollection.create(nParts); + GenotypesContext genotypes = GenotypesContext.create(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java index 86dd5d4f7..8903a176a 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java @@ -1,7 +1,7 @@ package org.broadinstitute.sting.utils.codecs.vcf; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import java.util.List; @@ -19,6 +19,6 @@ public interface VCFParser { * @param pos position * @return a mapping of sample name to genotype object */ - public GenotypeCollection createGenotypeMap(String str, List alleles, String chr, int pos); + public GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos); } diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index 20cadc469..b8672a7bd 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -29,7 +29,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -147,16 +147,16 @@ public class GCF { Map attributes = new HashMap(); attributes.put("INFO", info); Byte refPadByte = refPad == 0 ? null : refPad; - GenotypeCollection genotypes = decodeGenotypes(header); + GenotypesContext genotypes = decodeGenotypes(header); return new VariantContext(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); } - private GenotypeCollection decodeGenotypes(final GCFHeader header) { + private GenotypesContext decodeGenotypes(final GCFHeader header) { if ( genotypes.isEmpty() ) return VariantContext.NO_GENOTYPES; else { - GenotypeCollection map = GenotypeCollection.create(genotypes.size()); + GenotypesContext map = GenotypesContext.create(genotypes.size()); for ( int i = 0; i < genotypes.size(); i++ ) { final String sampleName = header.getSample(i); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java similarity index 86% rename from public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java rename to public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 4dbc23e63..3b2de4769 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeCollection.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -29,9 +29,9 @@ import java.util.*; /** * */ -public class GenotypeCollection implements List { - public final static GenotypeCollection NO_GENOTYPES = - new GenotypeCollection(new ArrayList(0), new HashMap(0), new HashSet(0), true); +public class GenotypesContext implements List { + public final static GenotypesContext NO_GENOTYPES = + new GenotypesContext(new ArrayList(0), new HashMap(0), new HashSet(0), true); Set sampleNamesInOrder = null; Map sampleNameToOffset = null; @@ -45,25 +45,25 @@ public class GenotypeCollection implements List { // // --------------------------------------------------------------------------- - private GenotypeCollection() { + private GenotypesContext() { this(10, false); } - private GenotypeCollection(final int n, final boolean immutable) { + private GenotypesContext(final int n, final boolean immutable) { this(new ArrayList(n), immutable); } - private GenotypeCollection(final ArrayList genotypes, final boolean immutable) { + private GenotypesContext(final ArrayList genotypes, final boolean immutable) { this.genotypes = genotypes; this.immutable = immutable; this.sampleNameToOffset = null; this.cacheIsInvalid = true; } - private GenotypeCollection(final ArrayList genotypes, - final Map sampleNameToOffset, - final Set sampleNamesInOrder, - final boolean immutable) { + private GenotypesContext(final ArrayList genotypes, + final Map sampleNameToOffset, + final Set sampleNamesInOrder, + final boolean immutable) { this.genotypes = genotypes; this.immutable = immutable; this.sampleNameToOffset = sampleNameToOffset; @@ -77,27 +77,27 @@ public class GenotypeCollection implements List { // // --------------------------------------------------------------------------- - public static final GenotypeCollection create() { - return new GenotypeCollection(); + public static final GenotypesContext create() { + return new GenotypesContext(); } - public static final GenotypeCollection create(final int nGenotypes) { - return new GenotypeCollection(nGenotypes, false); + public static final GenotypesContext create(final int nGenotypes) { + return new GenotypesContext(nGenotypes, false); } - public static final GenotypeCollection create(final ArrayList genotypes) { - return genotypes == null ? NO_GENOTYPES : new GenotypeCollection(genotypes, false); + public static final GenotypesContext create(final ArrayList genotypes) { + return genotypes == null ? NO_GENOTYPES : new GenotypesContext(genotypes, false); } - public static final GenotypeCollection create(final Genotype... genotypes) { - return new GenotypeCollection(new ArrayList(Arrays.asList(genotypes)), false); + public static final GenotypesContext create(final Genotype... genotypes) { + return new GenotypesContext(new ArrayList(Arrays.asList(genotypes)), false); } - public static final GenotypeCollection copy(final GenotypeCollection toCopy) { + public static final GenotypesContext copy(final GenotypesContext toCopy) { return create(new ArrayList(toCopy.genotypes)); } - public static final GenotypeCollection copy(final Collection toCopy) { + public static final GenotypesContext copy(final Collection toCopy) { return toCopy == null ? NO_GENOTYPES : create(new ArrayList(toCopy)); } @@ -123,7 +123,7 @@ public class GenotypeCollection implements List { // // --------------------------------------------------------------------------- - public final GenotypeCollection immutable() { + public final GenotypesContext immutable() { this.genotypes = Collections.unmodifiableList(genotypes); immutable = true; return this; @@ -369,17 +369,17 @@ public class GenotypeCollection implements List { return getSampleNames().containsAll(samples); } - public GenotypeCollection subsetToSamples( final Collection samples ) { + public GenotypesContext subsetToSamples( final Collection samples ) { return subsetToSamples(new HashSet(samples)); } - public GenotypeCollection subsetToSamples( final Set samples ) { + public GenotypesContext subsetToSamples( final Set samples ) { if ( samples.size() == genotypes.size() ) return this; else if ( samples.isEmpty() ) return NO_GENOTYPES; else { - GenotypeCollection subset = create(samples.size()); + GenotypesContext subset = create(samples.size()); for ( final Genotype g : genotypes ) { if ( samples.contains(g.getSampleName()) ) { subset.add(g); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index d0f88e2ec..0a193f59a 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -7,7 +7,6 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import java.lang.reflect.Array; import java.util.*; /** @@ -188,12 +187,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati final protected List alleles; /** A mapping from sampleName -> genotype objects for all genotypes associated with this context */ - protected GenotypeCollection genotypes = null; + protected GenotypesContext genotypes = null; /** Counts for each of the possible Genotype types in this context */ protected int[] genotypeCounts = null; - public final static GenotypeCollection NO_GENOTYPES = GenotypeCollection.NO_GENOTYPES; + public final static GenotypesContext NO_GENOTYPES = GenotypesContext.NO_GENOTYPES; // a fast cached access point to the ref / alt alleles for biallelic case private Allele REF = null; @@ -226,12 +225,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); } @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel); } @@ -249,12 +248,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes) { + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes) { this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); } @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypeCollection genotypes, double negLog10PError, Set filters, Map attributes) { + public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes) { this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); } @@ -299,14 +298,14 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { this(source, ID, contig, start, stop, alleles, - GenotypeCollection.copy(genotypes), + GenotypesContext.copy(genotypes), negLog10PError, filters, attributes, null, false, true); } @Deprecated public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, - GenotypeCollection.copy(genotypes), + GenotypesContext.copy(genotypes), negLog10PError, filters, attributes); } @@ -374,7 +373,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ private VariantContext(String source, String ID, String contig, long start, long stop, - Collection alleles, GenotypeCollection genotypes, + Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed, boolean performValidation ) { @@ -445,7 +444,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - public static VariantContext modifyGenotypes(VariantContext vc, GenotypeCollection genotypes) { + public static VariantContext modifyGenotypes(VariantContext vc, GenotypesContext genotypes) { VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), false, false); modifiedVC.validateGenotypes(); return modifiedVC; @@ -544,7 +543,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati public VariantContext subContextFromSamples(Set sampleNames, Collection alleles) { loadGenotypes(); - GenotypeCollection newGenotypes = genotypes.subsetToSamples(sampleNames); + GenotypesContext newGenotypes = genotypes.subsetToSamples(sampleNames); return new VariantContext(getSource(), getID(), contig, start, stop, alleles, newGenotypes, getNegLog10PError(), @@ -555,7 +554,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati public VariantContext subContextFromSamples(Set sampleNames) { loadGenotypes(); - GenotypeCollection newGenotypes = genotypes.subsetToSamples(sampleNames); + GenotypesContext newGenotypes = genotypes.subsetToSamples(sampleNames); return new VariantContext(getSource(), getID(), contig, start, stop, allelesOfGenotypes(newGenotypes), newGenotypes, getNegLog10PError(), @@ -981,7 +980,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati /** * @return set of all Genotypes associated with this context */ - public GenotypeCollection getGenotypes() { + public GenotypesContext getGenotypes() { loadGenotypes(); return genotypes; } @@ -999,7 +998,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public GenotypeCollection getGenotypes(String sampleName) { + public GenotypesContext getGenotypes(String sampleName) { return getGenotypes(Arrays.asList(sampleName)); } @@ -1011,11 +1010,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public GenotypeCollection getGenotypes(Collection sampleNames) { + public GenotypesContext getGenotypes(Collection sampleNames) { return getGenotypes().subsetToSamples(sampleNames); } - public GenotypeCollection getGenotypes(Set sampleNames) { + public GenotypesContext getGenotypes(Set sampleNames) { return getGenotypes().subsetToSamples(sampleNames); } @@ -1567,7 +1566,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati } // now we can recreate new genotypes with trimmed alleles - GenotypeCollection genotypes = GenotypeCollection.create(inputVC.getNSamples()); + GenotypesContext genotypes = GenotypesContext.create(inputVC.getNSamples()); for (final Genotype g : inputVC.getGenotypes() ) { List inAlleles = g.getAlleles(); List newGenotypeAlleles = new ArrayList(g.getAlleles().size()); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 238cf4b3b..b77f606f5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -294,7 +294,7 @@ public class VariantContextUtils { final Map attributes = subsetAttributes(vc.commonInfo, keysToPreserve); // Genotypes - final GenotypeCollection genotypes = GenotypeCollection.create(vc.getNSamples()); + final GenotypesContext genotypes = GenotypesContext.create(vc.getNSamples()); for ( final Genotype g : vc.getGenotypes() ) { Map genotypeAttributes = subsetAttributes(g.commonInfo, keysToPreserve); genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.getFilters(), @@ -402,7 +402,7 @@ public class VariantContextUtils { double negLog10PError = -1; VariantContext vcWithMaxAC = null; Set addedSamples = new HashSet(first.getNSamples()); - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypesContext genotypes = GenotypesContext.create(); // counting the number of filtered and variant VCs int nFiltered = 0; @@ -590,7 +590,7 @@ public class VariantContextUtils { // nothing to do if we don't need to trim bases if (trimVC) { List alleles = new ArrayList(); - GenotypeCollection genotypes = GenotypeCollection.create(); + GenotypesContext genotypes = GenotypesContext.create(); // set the reference base for indels in the attributes Map attributes = new TreeMap(inputVC.getAttributes()); @@ -644,8 +644,8 @@ public class VariantContextUtils { return inputVC; } - public static GenotypeCollection stripPLs(GenotypeCollection genotypes) { - GenotypeCollection newGs = GenotypeCollection.create(genotypes.size()); + public static GenotypesContext stripPLs(GenotypesContext genotypes) { + GenotypesContext newGs = GenotypesContext.create(genotypes.size()); for ( final Genotype g : genotypes ) { newGs.add(g.hasLikelihoods() ? removePLs(g) : g); @@ -825,7 +825,7 @@ public class VariantContextUtils { } } - private static void mergeGenotypes(GenotypeCollection mergedGenotypes, Set addedSamples, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { + private static void mergeGenotypes(GenotypesContext mergedGenotypes, Set addedSamples, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { for ( Genotype g : oneVC.getGenotypes() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); if ( ! addedSamples.contains(name) ) { @@ -866,7 +866,7 @@ public class VariantContextUtils { } // create new Genotype objects - GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); + GenotypesContext newGenotypes = GenotypesContext.create(vc.getNSamples()); for ( final Genotype genotype : vc.getGenotypes() ) { List newAlleles = new ArrayList(); for ( Allele allele : genotype.getAlleles() ) { @@ -886,7 +886,7 @@ public class VariantContextUtils { if ( allowedAttributes == null ) return vc; - GenotypeCollection newGenotypes = GenotypeCollection.create(vc.getNSamples()); + GenotypesContext newGenotypes = GenotypesContext.create(vc.getNSamples()); for ( final Genotype genotype : vc.getGenotypes() ) { Map attrs = new HashMap(); for ( Map.Entry attr : genotype.getAttributes().entrySet() ) { diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index ad38b46e3..4b21e09e3 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -4,7 +4,7 @@ import org.broad.tribble.Tribble; import org.broad.tribble.readers.AsciiLineReader; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypeCollection; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -122,7 +122,7 @@ public class VCFWriterUnitTest extends BaseTest { List alleles = new ArrayList(); Set filters = null; Map attributes = new HashMap(); - GenotypeCollection genotypes = GenotypeCollection.create(header.getGenotypeSamples().size()); + GenotypesContext genotypes = GenotypesContext.create(header.getGenotypeSamples().size()); alleles.add(Allele.create("-",true)); alleles.add(Allele.create("CC",false)); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index eda74a965..273b8fdf7 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -226,7 +226,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { List toMerge = new ArrayList(); for ( int i = 0; i < dupsToMerge; i++ ) { - GenotypeCollection gc = GenotypeCollection.create(vc.getNSamples()); + GenotypesContext gc = GenotypesContext.create(vc.getNSamples()); for ( final Genotype g : vc.getGenotypes() ) { gc.add(new Genotype(g.getSampleName()+"_"+i, g)); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index dbe131a14..805781fe0 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -100,7 +100,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { int start = 10; int stop = start; // alleles.contains(ATC) ? start + 3 : start; return new VariantContext(source, VCFConstants.EMPTY_ID_FIELD, "1", start, stop, alleles, - GenotypeCollection.copy(genotypes), 1.0, filters, null, Cref.getBases()[0]); + GenotypesContext.copy(genotypes), 1.0, filters, null, Cref.getBases()[0]); } // -------------------------------------------------------------------------------- @@ -508,7 +508,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { } // necessary to not overload equals for genotypes - private void assertGenotypesAreMostlyEqual(GenotypeCollection actual, GenotypeCollection expected) { + private void assertGenotypesAreMostlyEqual(GenotypesContext actual, GenotypesContext expected) { if (actual == expected) { return; } From 7e666777691896edb76fa0eb95ae06ef5e66a50e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 20:45:15 -0500 Subject: [PATCH 043/113] Expanded UnitTests for VariantContext Tests for -- getGenotype and getGenotypes -- subContextBySample -- modify routines --- .../utils/variantcontext/VariantContext.java | 2 +- .../VariantContextUnitTest.java | 195 +++++++++++++++++- 2 files changed, 193 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 0a193f59a..75e3aac58 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -383,8 +383,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati this.stop = stop; // intern for efficiency. equals calls will generate NPE if ID is inappropriately passed in as null + if ( ID == null || ID.equals("") ) throw new IllegalArgumentException("ID field cannot be the null or the empty string"); this.ID = ID.equals(VCFConstants.EMPTY_ID_FIELD) ? VCFConstants.EMPTY_ID_FIELD : ID; - if ( this.ID.equals("") ) throw new IllegalArgumentException("ID field cannot be the empty string"); if ( !genotypesAreUnparsed && attributes != null ) { if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) { diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index f63209dc1..f2eb2dd57 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -12,6 +12,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.Assert; +import java.lang.reflect.Array; import java.util.*; @@ -173,7 +174,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingRefVariantContext() { - List alleles = Arrays.asList(Aref); + List alleles = Arrays.asList(Aref); VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); Assert.assertEquals(vc.getChr(), snpLoc); @@ -309,6 +310,16 @@ public class VariantContextUnitTest extends BaseTest { new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles); } + @Test (expectedExceptions = IllegalArgumentException.class) + public void testBadID1() { + new VariantContext("test", null, delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)); + } + + @Test (expectedExceptions = IllegalArgumentException.class) + public void testBadID2() { + new VariantContext("test", "", delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)); + } + @Test public void testAccessingSimpleSNPGenotypes() { List alleles = Arrays.asList(Aref, T); @@ -423,7 +434,7 @@ public class VariantContextUnitTest extends BaseTest { } @Test - public void testVCromGenotypes() { + public void testVCFfromGenotypes() { List alleles = Arrays.asList(Aref, T, del); Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); @@ -479,6 +490,29 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertEquals(0, vc5.getChromosomeCount(Aref)); } + public void testGetGenotypeMethods() { + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + GenotypesContext gc = GenotypesContext.create(g1, g2, g3); + VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + + Assert.assertEquals(vc.getGenotype("AA"), g1); + Assert.assertEquals(vc.getGenotype("AT"), g2); + Assert.assertEquals(vc.getGenotype("TT"), g3); + Assert.assertEquals(vc.getGenotype("CC"), null); + + Assert.assertEquals(vc.getGenotypes(), gc); + Assert.assertEquals(vc.getGenotypes(Arrays.asList("AA", "AT")), Arrays.asList(g1, g2)); + Assert.assertEquals(vc.getGenotypes(Arrays.asList("AA", "TT")), Arrays.asList(g1, g3)); + Assert.assertEquals(vc.getGenotypes(Arrays.asList("AA", "AT", "TT")), Arrays.asList(g1, g2, g3)); + Assert.assertEquals(vc.getGenotypes(Arrays.asList("AA", "AT", "CC")), Arrays.asList(g1, g2)); + + Assert.assertEquals(vc.getGenotype(0), g1); + Assert.assertEquals(vc.getGenotype(1), g2); + Assert.assertEquals(vc.getGenotype(2), g3); + } + // -------------------------------------------------------------------------------- // // Test allele merging @@ -545,4 +579,159 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertFalse(vc.hasAllele(missingAllele)); Assert.assertFalse(vc.hasAllele(missingAllele, true)); } -} + + private class SitesAndGenotypesVC extends TestDataProvider { + VariantContext vc, copy; + + private SitesAndGenotypesVC(String name, VariantContext original) { + super(SitesAndGenotypesVC.class, name); + this.vc = original; + this.copy = new VariantContext(original); + } + + public String toString() { + return String.format("%s input=%s", super.toString(), vc); + } + } + + @DataProvider(name = "SitesAndGenotypesVC") + public Object[][] MakeSitesAndGenotypesVCs() { + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + + VariantContext sites = new VariantContext("sites", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)); + VariantContext genotypes = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T), Arrays.asList(g1, g2, g3)); + + new SitesAndGenotypesVC("sites", sites); + new SitesAndGenotypesVC("genotypes", genotypes); + + return SitesAndGenotypesVC.getTests(SitesAndGenotypesVC.class); + } + + // -------------------------------------------------------------------------------- + // + // Test modifying routines + // + // -------------------------------------------------------------------------------- + @Test(dataProvider = "SitesAndGenotypesVC") + public void runModifyVCTests(SitesAndGenotypesVC cfg) { + VariantContext modified = VariantContext.modifyLocation(cfg.vc, "chr2", 123, 123); + Assert.assertEquals(modified.getChr(), "chr2"); + Assert.assertEquals(modified.getStart(), 123); + Assert.assertEquals(modified.getEnd(), 123); + + modified = VariantContext.modifyID(cfg.vc, "newID"); + Assert.assertEquals(modified.getID(), "newID"); + + Set newFilters = Collections.singleton("newFilter"); + modified = VariantContext.modifyFilters(cfg.vc, newFilters); + Assert.assertEquals(modified.getFilters(), newFilters); + + modified = VariantContext.modifyAttribute(cfg.vc, "AC", 1); + Assert.assertEquals(modified.getAttribute("AC"), 1); + modified = VariantContext.modifyAttribute(modified, "AC", 2); + Assert.assertEquals(modified.getAttribute("AC"), 2); + modified = VariantContext.modifyAttributes(modified, null); + Assert.assertTrue(modified.getAttributes().isEmpty()); + + Genotype g1 = new Genotype("AA2", Arrays.asList(Aref, Aref), 10); + Genotype g2 = new Genotype("AT2", Arrays.asList(Aref, T), 10); + Genotype g3 = new Genotype("TT2", Arrays.asList(T, T), 10); + GenotypesContext gc = GenotypesContext.create(g1,g2,g3); + modified = VariantContext.modifyGenotypes(cfg.vc, gc); + Assert.assertEquals(modified.getGenotypes(), gc); + modified = VariantContext.modifyGenotypes(cfg.vc, null); + Assert.assertTrue(modified.getGenotypes().isEmpty()); + + // test that original hasn't changed + Assert.assertEquals(cfg.vc.getChr(), cfg.copy.getChr()); + Assert.assertEquals(cfg.vc.getStart(), cfg.copy.getStart()); + Assert.assertEquals(cfg.vc.getEnd(), cfg.copy.getEnd()); + Assert.assertEquals(cfg.vc.getAlleles(), cfg.copy.getAlleles()); + Assert.assertEquals(cfg.vc.getAttributes(), cfg.copy.getAttributes()); + Assert.assertEquals(cfg.vc.getID(), cfg.copy.getID()); + Assert.assertEquals(cfg.vc.getGenotypes(), cfg.copy.getGenotypes()); + Assert.assertEquals(cfg.vc.getNegLog10PError(), cfg.copy.getNegLog10PError()); + Assert.assertEquals(cfg.vc.getFilters(), cfg.copy.getFilters()); + } + + // -------------------------------------------------------------------------------- + // + // Test subcontext + // + // -------------------------------------------------------------------------------- + private class SubContextTest extends TestDataProvider { + Set samples; + boolean updateAlleles; + + private SubContextTest(Collection samples, boolean updateAlleles) { + super(SubContextTest.class); + this.samples = new HashSet(samples); + this.updateAlleles = updateAlleles; + } + + public String toString() { + return String.format("%s samples=%s updateAlleles=%b", super.toString(), samples, updateAlleles); + } + } + + @DataProvider(name = "SubContextTest") + public Object[][] MakeSubContextTest() { + for ( boolean updateAlleles : Arrays.asList(true, false)) { + new SubContextTest(Collections.emptySet(), updateAlleles); + new SubContextTest(Collections.singleton("AA"), updateAlleles); + new SubContextTest(Collections.singleton("AT"), updateAlleles); + new SubContextTest(Collections.singleton("TT"), updateAlleles); + new SubContextTest(Arrays.asList("AA", "AT"), updateAlleles); + new SubContextTest(Arrays.asList("AA", "AT", "TT"), updateAlleles); + } + + return SubContextTest.getTests(SubContextTest.class); + } + + private final static void SubContextTest() { + } + + @Test(dataProvider = "SubContextTest") + public void runSubContextTest(SubContextTest cfg) { + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + + GenotypesContext gc = GenotypesContext.create(g1, g2, g3); + VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + VariantContext sub = cfg.updateAlleles ? vc.subContextFromSamples(cfg.samples) : vc.subContextFromSamples(cfg.samples, vc.getAlleles()); + + // unchanged attributes should be the same + Assert.assertEquals(sub.getChr(), vc.getChr()); + Assert.assertEquals(sub.getStart(), vc.getStart()); + Assert.assertEquals(sub.getEnd(), vc.getEnd()); + Assert.assertEquals(sub.getNegLog10PError(), vc.getNegLog10PError()); + Assert.assertEquals(sub.getFilters(), vc.getFilters()); + Assert.assertEquals(sub.getID(), vc.getID()); + Assert.assertEquals(sub.getReferenceBaseForIndel(), vc.getReferenceBaseForIndel()); + Assert.assertEquals(sub.getAttributes(), vc.getAttributes()); + + Set expectedGenotypes = new HashSet(); + if ( cfg.samples.contains(g1.getSampleName()) ) expectedGenotypes.add(g1); + if ( cfg.samples.contains(g2.getSampleName()) ) expectedGenotypes.add(g2); + if ( cfg.samples.contains(g3.getSampleName()) ) expectedGenotypes.add(g3); + GenotypesContext expectedGC = GenotypesContext.copy(expectedGenotypes); + + // these values depend on the results of sub + if ( cfg.updateAlleles ) { + // do the work to see what alleles should be here, and which not + Set alleles = new HashSet(); + for ( final Genotype g : expectedGC ) alleles.addAll(g.getAlleles()); + if ( ! alleles.contains(Aref) ) alleles.add(Aref); // always have the reference + Assert.assertEquals(new HashSet(sub.getAlleles()), alleles); + } else { + // not updating alleles -- should be the same + Assert.assertEquals(sub.getAlleles(), vc.getAlleles()); + } + + // same sample names => success + Assert.assertEquals(sub.getGenotypes().getSampleNames(), expectedGC.getSampleNames()); + } +} \ No newline at end of file From e7d41d8d334221c12dc89a080a3ec55bb9cc4bfb Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 17 Nov 2011 12:00:28 -0500 Subject: [PATCH 044/113] Minor cleanup --- .../gatk/walkers/variantutils/SelectVariants.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 609593acc..b92b1cee0 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -24,10 +24,8 @@ package org.broadinstitute.sting.gatk.walkers.variantutils; -import org.apache.poi.hpsf.Variant; import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; -import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.text.XReadLines; @@ -275,8 +273,8 @@ public class SelectVariants extends RodWalker { private double MENDELIAN_VIOLATION_QUAL_THRESHOLD = 0; /** - * Variants are kept in memory to guarantee that exactly n variants will be chosen randomly, so use it only for a reasonable - * number of variants. Use --select_random_fraction for larger numbers of variants. + * Variants are kept in memory to guarantee that exactly n variants will be chosen randomly, so make sure you supply the program with enough memory + * given your input set. This option will NOT work well for large callsets; use --select_random_fraction for sets with a large numbers of variants. */ @Argument(fullName="select_random_number", shortName="number", doc="Selects a number of variants at random from the variant track", required=false) private int numRandom = 0; @@ -532,7 +530,7 @@ public class SelectVariants extends RodWalker { } } if (SELECT_RANDOM_NUMBER) { - randomlyAddVariant(++variantNumber, sub, ref.getBase()); + randomlyAddVariant(++variantNumber, sub); } else if (!SELECT_RANDOM_FRACTION || ( GenomeAnalysisEngine.getRandomGenerator().nextDouble() < fractionRandom)) { vcfWriter.add(sub); @@ -705,7 +703,7 @@ public class SelectVariants extends RodWalker { return sub; } - private void randomlyAddVariant(int rank, VariantContext vc, byte refBase) { + private void randomlyAddVariant(int rank, VariantContext vc) { if (nVariantsAdded < numRandom) variantArray[nVariantsAdded++] = new RandomVariantStructure(vc); From 16a021992bf51bb5bbac5a9ebb8f4206fd4c314c Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 17 Nov 2011 13:17:53 -0500 Subject: [PATCH 045/113] Updated header description for the INFO and FORMAT DP fields to be more accurate. --- .../walkers/annotator/DepthOfCoverage.java | 2 +- .../walkers/genotyper/UnifiedGenotyper.java | 2 +- .../VariantAnnotatorIntegrationTest.java | 22 ++++---- .../UnifiedGenotyperIntegrationTest.java | 50 +++++++++---------- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java index 8098de5b1..ab38b69cd 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java @@ -49,5 +49,5 @@ public class DepthOfCoverage extends InfoFieldAnnotation implements StandardAnno public List getKeyNames() { return Arrays.asList(VCFConstants.DEPTH_KEY); } - public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine(getKeyNames().get(0), 1, VCFHeaderLineType.Integer, "Filtered Depth")); } + public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine(getKeyNames().get(0), 1, VCFHeaderLineType.Integer, "Approximate read depth; some reads may have been filtered")); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java index bdd4e2c65..369c2d0c6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java @@ -258,7 +258,7 @@ public class UnifiedGenotyper extends LocusWalker result = new HashSet(); result.add(new VCFFormatHeaderLine(VCFConstants.GENOTYPE_KEY, 1, VCFHeaderLineType.String, "Genotype")); result.add(new VCFFormatHeaderLine(VCFConstants.GENOTYPE_QUALITY_KEY, 1, VCFHeaderLineType.Float, "Genotype Quality")); - result.add(new VCFFormatHeaderLine(VCFConstants.DEPTH_KEY, 1, VCFHeaderLineType.Integer, "Read Depth (only filtered reads used for calling)")); + result.add(new VCFFormatHeaderLine(VCFConstants.DEPTH_KEY, 1, VCFHeaderLineType.Integer, "Approximate read depth (reads with MQ=255 or with bad mates are filtered)")); result.add(new VCFFormatHeaderLine(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, VCFHeaderLineCount.G, VCFHeaderLineType.Integer, "Normalized, Phred-scaled likelihoods for genotypes as defined in the VCF specification")); return result; diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index 919e3d9bd..3bfb81dd0 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -32,7 +32,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testHasAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("8e7de435105499cd71ffc099e268a83e")); + Arrays.asList("9beb795536e95954f810835c6058f2ad")); executeTest("test file has annotations, asking for annotations, #1", spec); } @@ -40,7 +40,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testHasAnnotsAsking2() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000", 1, - Arrays.asList("64b6804cb1e27826e3a47089349be581")); + Arrays.asList("2977bb30c8b84a5f4094fe6090658561")); executeTest("test file has annotations, asking for annotations, #2", spec); } @@ -64,7 +64,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("fd1ffb669800c2e07df1e2719aa38e49")); + Arrays.asList("49d989f467b8d6d8f98f7c1b67cd4a05")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -72,7 +72,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoAnnotsAsking2() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000", 1, - Arrays.asList("09f8e840770a9411ff77508e0ed0837f")); + Arrays.asList("0948cd1dba7d61f283cc4cf2a7757d92")); executeTest("test file doesn't have annotations, asking for annotations, #2", spec); } @@ -80,7 +80,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testExcludeAnnotations() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard -XA FisherStrand -XA ReadPosRankSumTest --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("b49fe03aa4b675db80a9db38a3552c95")); + Arrays.asList("33062eccd6eb73bc49440365430454c4")); executeTest("test exclude annotations", spec); } @@ -88,7 +88,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testOverwritingHeader() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant " + validationDataLocation + "vcfexample4.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,001,292", 1, - Arrays.asList("78d2c19f8107d865970dbaf3e12edd92")); + Arrays.asList("062155edec46a8c52243475fbf3a2943")); executeTest("test overwriting header", spec); } @@ -96,7 +96,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoReads() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant " + validationDataLocation + "vcfexample3empty.vcf -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("16e3a1403fc376320d7c69492cad9345")); + Arrays.asList("06635f2dd91b539bfbce9bf7914d8e43")); executeTest("not passing it any reads", spec); } @@ -104,7 +104,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testDBTagWithDbsnp() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --dbsnp " + b36dbSNP129 + " -G Standard --variant " + validationDataLocation + "vcfexample3empty.vcf -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("3da8ca2b6bdaf6e92d94a8c77a71313d")); + Arrays.asList("820eeba1f6e3a0758a69d937c524a38e")); executeTest("getting DB tag with dbSNP", spec); } @@ -112,7 +112,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testDBTagWithHapMap() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --comp:H3 " + validationDataLocation + "fakeHM3.vcf -G Standard --variant " + validationDataLocation + "vcfexample3empty.vcf -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("1bc01c5b3bd0b7aef75230310c3ce688")); + Arrays.asList("31cc2ce157dd20771418c08d6b3be1fa")); executeTest("getting DB tag with HM3", spec); } @@ -120,7 +120,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testUsingExpression() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.AF -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("ae30a1ac7bfbc3d22a327f8b689cad31")); + Arrays.asList("074865f8f8c0ca7bfd58681f396c49e9")); executeTest("using expression", spec); } @@ -128,7 +128,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testUsingExpressionWithID() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.ID -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("1b4921085b26cbfe07d53b7c947de1e5")); + Arrays.asList("97b26db8135d083566fb585a677fbe8a")); executeTest("using expression with ID", spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index b80f214b1..0197f94e5 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -5,10 +5,8 @@ import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.utils.exceptions.UserException; import org.testng.annotations.Test; -import java.io.File; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; // ********************************************************************************** // @@ -30,7 +28,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testMultiSamplePilot1() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -o %s -L 1:10,022,000-10,025,000", 1, - Arrays.asList("b27939251539439a382538e507e03507")); + Arrays.asList("364a6112e6034571a896d4c68b6ff02a")); executeTest("test MultiSample Pilot1", spec); } @@ -38,12 +36,12 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithAllelesPassedIn() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("8de2602679ffc92388da0b6cb4325ef6")); + Arrays.asList("6cce55734ecd1a56c33f0ecba67d1258")); executeTest("test MultiSample Pilot2 with alleles passed in", spec1); WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("6458f3b8fe4954e2ffc2af972aaab19e")); + Arrays.asList("b2a00130a4b6f9a3c31288a236f5d159")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } @@ -51,7 +49,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testSingleSamplePilot2() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,100,000", 1, - Arrays.asList("6762b72ae60155ad71738d7c76b80e4b")); + Arrays.asList("3ccce5d909f8f128e496f6841836e5f7")); executeTest("test SingleSample Pilot2", spec); } @@ -61,7 +59,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // // -------------------------------------------------------------------------------------------------------------- - private final static String COMPRESSED_OUTPUT_MD5 = "bc71dba7bbdb23e7d5cc60461fdd897b"; + private final static String COMPRESSED_OUTPUT_MD5 = "890143b366050e78d6c6ba6b2c6b6864"; @Test public void testCompressedOutput() { @@ -82,7 +80,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // Note that we need to turn off any randomization for this to work, so no downsampling and no annotations - String md5 = "b9504e446b9313559c3ed97add7e8dc1"; + String md5 = "95614280c565ad90f8c000376fef822c"; WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " -dt NONE -G none -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,075,000", 1, @@ -113,8 +111,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testCallingParameters() { HashMap e = new HashMap(); - e.put( "--min_base_quality_score 26", "bb3f294eab3e2cf52c70e63b23aac5ee" ); - e.put( "--computeSLOD", "eb34979efaadba1e34bd82bcacf5c722" ); + e.put( "--min_base_quality_score 26", "7acb1a5aee5fdadb0cc0ea07a212efc6" ); + e.put( "--computeSLOD", "e9d23a08472e4e27b4f25e844f5bad57" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -127,9 +125,9 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testOutputParameter() { HashMap e = new HashMap(); - e.put( "-sites_only", "d40114aa201aa33ff5f174f15b6b73af" ); - e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "3c681b053fd2280f3c42041d24243752" ); - e.put( "--output_mode EMIT_ALL_SITES", "eafa6d71c5ecd64dfee5d7a3f60e392e" ); + e.put( "-sites_only", "44f3b5b40e6ad44486cddfdb7e0bfcd8" ); + e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "94e53320f14c5ff29d62f68d36b46fcd" ); + e.put( "--output_mode EMIT_ALL_SITES", "73ad1cc41786b12c5f0e6f3e9ec2b728" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -143,12 +141,12 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testConfidence() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,010,000 -stand_call_conf 10 ", 1, - Arrays.asList("c71ca370947739cb7d87b59452be7a07")); + Arrays.asList("902327e8a45fe585c8dfd1a7c4fcf60f")); executeTest("test confidence 1", spec1); WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,010,000 -stand_emit_conf 10 ", 1, - Arrays.asList("1c0a599d475cc7d5e745df6e9b6c0d29")); + Arrays.asList("9ef66764a0ecfe89c3e4e1a93a69e349")); executeTest("test confidence 2", spec2); } @@ -160,8 +158,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testHeterozyosity() { HashMap e = new HashMap(); - e.put( 0.01, "f84da90c310367bd51f2ab6e346fa3d8" ); - e.put( 1.0 / 1850, "5791e7fef40d4412b6d8f84e0a809c6c" ); + e.put( 0.01, "46243ecc2b9dc716f48ea280c9bb7e72" ); + e.put( 1.0 / 1850, "6b2a59dbc76984db6d4d6d6b5ee5d62c" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -185,7 +183,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("9cc9538ac83770e12bd0830d285bfbd0")); + Arrays.asList("f0fbe472f155baf594b1eeb58166edef")); executeTest(String.format("test multiple technologies"), spec); } @@ -204,7 +202,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -L 1:10,000,000-10,100,000" + " -baq CALCULATE_AS_NECESSARY", 1, - Arrays.asList("eaf8043edb46dfbe9f97ae03baa797ed")); + Arrays.asList("8c87c749a7bb5a76ed8504d4ec254272")); executeTest(String.format("test calling with BAQ"), spec); } @@ -223,7 +221,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("eeba568272f9b42d5450da75c7cc6d2d")); + Arrays.asList("a64d2e65b5927260e4ce0d948760cc5c")); executeTest(String.format("test indel caller in SLX"), spec); } @@ -238,7 +236,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -minIndelCnt 1" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("5fe98ee853586dc9db58f0bc97daea63")); + Arrays.asList("2ad52c2e75b3ffbfd8f03237c444e8e6")); executeTest(String.format("test indel caller in SLX with low min allele count"), spec); } @@ -251,7 +249,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("19ff9bd3139480bdf79dcbf117cf2b24")); + Arrays.asList("69107157632714150fc068d412e31939")); executeTest(String.format("test indel calling, multiple technologies"), spec); } @@ -261,7 +259,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("118918f2e9e56a3cfc5ccb2856d529c8")); + Arrays.asList("84f065e115004e4c9d9d62f1bf6b4f44")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -271,7 +269,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { baseCommandIndels + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("a20799237accd52c1b8c2ac096309c8f")); + Arrays.asList("7ea8372e4074f90ec942f964e1863351")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -281,7 +279,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2.20101123.indels.sites.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,080,000", 1, - Arrays.asList("18ef8181157b4ac3eb8492f538467f92")); + Arrays.asList("94afa8c011b47af7323c821bf19106b4")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -290,7 +288,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec4 = new WalkerTest.WalkerTestSpec( baseCommandIndelsb37 + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2_chr20_100_110K.20101123.indels.sites.vcf -I " + validationDataLocation + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("ad884e511a751b05e64db5314314365a")); + Arrays.asList("1e02f57fafaa41db71c531eb25e148e1")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } From c50274e02e8bd381a2f7995a77b22f0ac8e0b00b Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 17 Nov 2011 13:53:46 -0500 Subject: [PATCH 046/113] During flanking interval creation merging overlapping flanks so that on scatter the list doesn't accidentally genotype the same site twice. Moved flanking interval utilies to IntervalUtils with UnitTests. --- .../sting/utils/GenomeLocParser.java | 50 ++++ .../sting/utils/interval/IntervalUtils.java | 119 ++++++++-- .../sting/utils/GenomeLocParserUnitTest.java | 104 +++++++- .../utils/interval/IntervalUtilsUnitTest.java | 223 +++++++++++++++++- .../gatk/WriteFlankingIntervalsFunction.scala | 48 ++++ .../ipf/intervals/ExpandIntervals.scala | 135 ----------- .../ipf/intervals/IntersectIntervals.scala | 70 ------ 7 files changed, 519 insertions(+), 230 deletions(-) create mode 100644 public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala delete mode 100755 public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala delete mode 100755 public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java index e10bcbaa0..8cba183da 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java @@ -554,4 +554,54 @@ public class GenomeLocParser { return createGenomeLoc(contigName,contig.getSequenceIndex(),1,contig.getSequenceLength(), true); } + /** + * Creates a loc to the left (starting at the loc start + 1) of maxBasePairs size. + * @param loc The original loc + * @param maxBasePairs The maximum number of basePairs + * @return The contiguous loc of up to maxBasePairs length or null if the loc is already at the start of the contig. + */ + @Requires({"loc != null", "maxBasePairs > 0"}) + public GenomeLoc createGenomeLocAtStart(GenomeLoc loc, int maxBasePairs) { + if (GenomeLoc.isUnmapped(loc)) + return null; + String contigName = loc.getContig(); + SAMSequenceRecord contig = contigInfo.getSequence(contigName); + int contigIndex = contig.getSequenceIndex(); + + int start = loc.getStart() - maxBasePairs; + int stop = loc.getStart() - 1; + + if (start < 1) + start = 1; + if (stop < 1) + return null; + + return createGenomeLoc(contigName, contigIndex, start, stop, true); + } + + /** + * Creates a loc to the right (starting at the loc stop + 1) of maxBasePairs size. + * @param loc The original loc + * @param maxBasePairs The maximum number of basePairs + * @return The contiguous loc of up to maxBasePairs length or null if the loc is already at the end of the contig. + */ + @Requires({"loc != null", "maxBasePairs > 0"}) + public GenomeLoc createGenomeLocAtStop(GenomeLoc loc, int maxBasePairs) { + if (GenomeLoc.isUnmapped(loc)) + return null; + String contigName = loc.getContig(); + SAMSequenceRecord contig = contigInfo.getSequence(contigName); + int contigIndex = contig.getSequenceIndex(); + int contigLength = contig.getSequenceLength(); + + int start = loc.getStop() + 1; + int stop = loc.getStop() + maxBasePairs; + + if (start > contigLength) + return null; + if (stop > contigLength) + stop = contigLength; + + return createGenomeLoc(contigName, contigIndex, start, stop, true); + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java index f0e164c87..159b145a0 100644 --- a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java @@ -233,8 +233,12 @@ public class IntervalUtils { * * Returns a null string if there are no differences, otherwise returns a string describing the difference * (useful for UnitTests). Assumes both lists are sorted + * + * @param masterArg sorted master genome locs + * @param testArg sorted test genome locs + * @return null string if there are no difference, otherwise a string describing the difference */ - public static final String equateIntervals(List masterArg, List testArg) { + public static String equateIntervals(List masterArg, List testArg) { LinkedList master = new LinkedList(masterArg); LinkedList test = new LinkedList(testArg); @@ -317,23 +321,6 @@ public class IntervalUtils { return lengths; } - /** - * Counts the number of interval files an interval list can be split into using scatterIntervalArguments. - * @param locs The genome locs. - * @return The maximum number of parts the intervals can be split into. - */ - public static int countContigIntervals(List locs) { - int maxFiles = 0; - String contig = null; - for (GenomeLoc loc: locs) { - if (contig == null || !contig.equals(loc.getContig())) { - maxFiles++; - contig = loc.getContig(); - } - } - return maxFiles; - } - /** * Splits an interval list into multiple files. * @param fileHeader The sam file header. @@ -373,7 +360,6 @@ public class IntervalUtils { * @return A list of lists of genome locs, split according to splits */ public static List> splitIntervalsToSubLists(List locs, List splits) { - int locIndex = 1; int start = 0; List> sublists = new ArrayList>(splits.size()); for (Integer stop: splits) { @@ -465,7 +451,7 @@ public class IntervalUtils { @Requires({"remaining != null", "!remaining.isEmpty()", "idealSplitSize > 0"}) @Ensures({"result != null"}) - final static SplitLocusRecursive splitLocusIntervals1(LinkedList remaining, long idealSplitSize) { + static SplitLocusRecursive splitLocusIntervals1(LinkedList remaining, long idealSplitSize) { final List split = new ArrayList(); long size = 0; @@ -579,10 +565,101 @@ public class IntervalUtils { } } - public static final long intervalSize(final List locs) { + public static long intervalSize(final List locs) { long size = 0; for ( final GenomeLoc loc : locs ) size += loc.size(); return size; } + + public static void writeFlankingIntervals(File reference, File inputIntervals, File flankingIntervals, int basePairs) { + ReferenceDataSource referenceDataSource = new ReferenceDataSource(reference); + GenomeLocParser parser = new GenomeLocParser(referenceDataSource.getReference()); + List originalList = intervalFileToList(parser, inputIntervals.getAbsolutePath()); + + if (originalList.isEmpty()) + throw new UserException.MalformedFile(inputIntervals, "File contains no intervals"); + + List flankingList = getFlankingIntervals(parser, originalList, basePairs); + + if (flankingList.isEmpty()) + throw new UserException.MalformedFile(inputIntervals, "Unable to produce any flanks for the intervals"); + + SAMFileHeader samFileHeader = new SAMFileHeader(); + samFileHeader.setSequenceDictionary(referenceDataSource.getReference().getSequenceDictionary()); + IntervalList intervalList = new IntervalList(samFileHeader); + int i = 0; + for (GenomeLoc loc: flankingList) + intervalList.add(toInterval(loc, ++i)); + intervalList.write(flankingIntervals); + } + + /** + * Returns a list of intervals between the passed int locs. Does not extend UNMAPPED locs. + * @param parser A genome loc parser for creating the new intervals + * @param locs Original genome locs + * @param basePairs Number of base pairs on each side of loc + * @return The list of intervals between the locs + */ + public static List getFlankingIntervals(final GenomeLocParser parser, final List locs, final int basePairs) { + List sorted = sortAndMergeIntervals(parser, locs, IntervalMergingRule.ALL).toList(); + + if (sorted.size() == 0) + return Collections.emptyList(); + + LinkedHashMap> locsByContig = splitByContig(sorted); + List expanded = new ArrayList(); + for (String contig: locsByContig.keySet()) { + List contigLocs = locsByContig.get(contig); + int contigLocsSize = contigLocs.size(); + + GenomeLoc startLoc, stopLoc; + + // Create loc at start of the list + startLoc = parser.createGenomeLocAtStart(contigLocs.get(0), basePairs); + if (startLoc != null) + expanded.add(startLoc); + + // Create locs between each loc[i] and loc[i+1] + for (int i = 0; i < contigLocsSize - 1; i++) { + stopLoc = parser.createGenomeLocAtStop(contigLocs.get(i), basePairs); + startLoc = parser.createGenomeLocAtStart(contigLocs.get(i + 1), basePairs); + if (stopLoc.getStop() + 1 >= startLoc.getStart()) { + // NOTE: This is different than GenomeLoc.merge() + // merge() returns a loc which covers the entire range of stop and start, + // possibly returning positions inside loc(i) or loc(i+1) + // We want to make sure that the start of the stopLoc is used, and the stop of the startLoc + GenomeLoc merged = parser.createGenomeLoc( + stopLoc.getContig(), stopLoc.getStart(), startLoc.getStop()); + expanded.add(merged); + } else { + expanded.add(stopLoc); + expanded.add(startLoc); + } + } + + // Create loc at the end of the list + stopLoc = parser.createGenomeLocAtStop(contigLocs.get(contigLocsSize - 1), basePairs); + if (stopLoc != null) + expanded.add(stopLoc); + } + return expanded; + } + + private static LinkedHashMap> splitByContig(List sorted) { + LinkedHashMap> splits = new LinkedHashMap>(); + GenomeLoc last = null; + List contigLocs = null; + for (GenomeLoc loc: sorted) { + if (GenomeLoc.isUnmapped(loc)) + continue; + if (last == null || !last.onSameContig(loc)) { + contigLocs = new ArrayList(); + splits.put(loc.getContig(), contigLocs); + } + contigLocs.add(loc); + last = loc; + } + return splits; + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java index f1f849bf5..e9f138a0e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java @@ -2,7 +2,6 @@ package org.broadinstitute.sting.utils; import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMSequenceDictionary; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -11,6 +10,7 @@ import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** @@ -36,7 +36,6 @@ public class GenomeLocParserUnitTest extends BaseTest { @Test public void testGetContigIndexValid() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 10); assertEquals(genomeLocParser.getContigIndex("chr1"), 0); // should be in the reference } @@ -67,7 +66,6 @@ public class GenomeLocParserUnitTest extends BaseTest { @Test public void testGetContigInfoKnownContig() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 10); assertEquals(0, "chr1".compareTo(genomeLocParser.getContigInfo("chr1").getSequenceName())); // should be in the reference } @@ -191,4 +189,104 @@ public class GenomeLocParserUnitTest extends BaseTest { assertTrue(!genomeLocParser.isValidGenomeLoc("chr1",1,-2)); // bad stop assertTrue(!genomeLocParser.isValidGenomeLoc("chr1",10,11)); // bad start, past end } + + private static class FlankingGenomeLocTestData extends TestDataProvider { + final GenomeLocParser parser; + final int basePairs; + final GenomeLoc original, flankStart, flankStop; + + private FlankingGenomeLocTestData(String name, GenomeLocParser parser, int basePairs, String original, String flankStart, String flankStop) { + super(FlankingGenomeLocTestData.class, name); + this.parser = parser; + this.basePairs = basePairs; + this.original = parse(parser, original); + this.flankStart = flankStart == null ? null : parse(parser, flankStart); + this.flankStop = flankStop == null ? null : parse(parser, flankStop); + } + + private static GenomeLoc parse(GenomeLocParser parser, String str) { + return "unmapped".equals(str) ? GenomeLoc.UNMAPPED : parser.parseGenomeLoc(str); + } + } + + @DataProvider(name = "flankingGenomeLocs") + public Object[][] getFlankingGenomeLocs() { + int contigLength = 10000; + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, contigLength); + GenomeLocParser parser = new GenomeLocParser(header.getSequenceDictionary()); + + new FlankingGenomeLocTestData("atStartBase1", parser, 1, + "chr1:1", null, "chr1:2"); + + new FlankingGenomeLocTestData("atStartBase50", parser, 50, + "chr1:1", null, "chr1:2-51"); + + new FlankingGenomeLocTestData("atStartRange50", parser, 50, + "chr1:1-10", null, "chr1:11-60"); + + new FlankingGenomeLocTestData("atEndBase1", parser, 1, + "chr1:" + contigLength, "chr1:" + (contigLength - 1), null); + + new FlankingGenomeLocTestData("atEndBase50", parser, 50, + "chr1:" + contigLength, String.format("chr1:%d-%d", contigLength - 50, contigLength - 1), null); + + new FlankingGenomeLocTestData("atEndRange50", parser, 50, + String.format("chr1:%d-%d", contigLength - 10, contigLength), + String.format("chr1:%d-%d", contigLength - 60, contigLength - 11), + null); + + new FlankingGenomeLocTestData("nearStartBase1", parser, 1, + "chr1:2", "chr1:1", "chr1:3"); + + new FlankingGenomeLocTestData("nearStartRange50", parser, 50, + "chr1:21-30", "chr1:1-20", "chr1:31-80"); + + new FlankingGenomeLocTestData("nearEndBase1", parser, 1, + "chr1:" + (contigLength - 1), "chr1:" + (contigLength - 2), "chr1:" + contigLength); + + new FlankingGenomeLocTestData("nearEndRange50", parser, 50, + String.format("chr1:%d-%d", contigLength - 30, contigLength - 21), + String.format("chr1:%d-%d", contigLength - 80, contigLength - 31), + String.format("chr1:%d-%d", contigLength - 20, contigLength)); + + new FlankingGenomeLocTestData("beyondStartBase1", parser, 1, + "chr1:3", "chr1:2", "chr1:4"); + + new FlankingGenomeLocTestData("beyondStartRange50", parser, 50, + "chr1:101-200", "chr1:51-100", "chr1:201-250"); + + new FlankingGenomeLocTestData("beyondEndBase1", parser, 1, + "chr1:" + (contigLength - 3), + "chr1:" + (contigLength - 4), + "chr1:" + (contigLength - 2)); + + new FlankingGenomeLocTestData("beyondEndRange50", parser, 50, + String.format("chr1:%d-%d", contigLength - 200, contigLength - 101), + String.format("chr1:%d-%d", contigLength - 250, contigLength - 201), + String.format("chr1:%d-%d", contigLength - 100, contigLength - 51)); + + new FlankingGenomeLocTestData("unmapped", parser, 50, + "unmapped", null, null); + + new FlankingGenomeLocTestData("fullContig", parser, 50, + "chr1", null, null); + + return FlankingGenomeLocTestData.getTests(FlankingGenomeLocTestData.class); + } + + @Test(dataProvider = "flankingGenomeLocs") + public void testCreateGenomeLocAtStart(FlankingGenomeLocTestData data) { + GenomeLoc actual = data.parser.createGenomeLocAtStart(data.original, data.basePairs); + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.flankStart); + assertEquals(actual, data.flankStart, description); + } + + @Test(dataProvider = "flankingGenomeLocs") + public void testCreateGenomeLocAtStop(FlankingGenomeLocTestData data) { + GenomeLoc actual = data.parser.createGenomeLocAtStop(data.original, data.basePairs); + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.flankStop); + assertEquals(actual, data.flankStop, description); + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java index 9c3b905c2..03d33d2c5 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java @@ -1,8 +1,8 @@ package org.broadinstitute.sting.utils.interval; import net.sf.picard.reference.ReferenceSequenceFile; -import net.sf.picard.util.IntervalUtil; import net.sf.samtools.SAMFileHeader; +import org.apache.commons.io.FileUtils; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; import org.broadinstitute.sting.utils.GenomeLocSortedSet; @@ -762,4 +762,225 @@ public class IntervalUtilsUnitTest extends BaseTest { List merged = IntervalUtils.mergeIntervalLocations(locs, IntervalMergingRule.ALL); Assert.assertEquals(merged.size(), 1); } + + /* + Split into tests that can be written to files and tested by writeFlankingIntervals, + and lists that cannot but are still handled by getFlankingIntervals. + */ + private static abstract class FlankingIntervalsTestData extends TestDataProvider { + final public File referenceFile; + final public GenomeLocParser parser; + final int basePairs; + final List original; + final List expected; + + protected FlankingIntervalsTestData(Class clazz, String name, File referenceFile, GenomeLocParser parser, + int basePairs, List original, List expected) { + super(clazz, name); + this.referenceFile = referenceFile; + this.parser = parser; + this.basePairs = basePairs; + this.original = parse(parser, original); + this.expected = parse(parser, expected); + } + + private static List parse(GenomeLocParser parser, List locs) { + List parsed = new ArrayList(); + for (String loc: locs) + parsed.add("unmapped".equals(loc) ? GenomeLoc.UNMAPPED : parser.parseGenomeLoc(loc)); + return parsed; + } + } + + private static class FlankingIntervalsFile extends FlankingIntervalsTestData { + public FlankingIntervalsFile(String name, File referenceFile, GenomeLocParser parser, + int basePairs, List original, List expected) { + super(FlankingIntervalsFile.class, name, referenceFile, parser, basePairs, original, expected); + } + } + + private static class FlankingIntervalsList extends FlankingIntervalsTestData { + public FlankingIntervalsList(String name, File referenceFile, GenomeLocParser parser, + int basePairs, List original, List expected) { + super(FlankingIntervalsList.class, name, referenceFile, parser, basePairs, original, expected); + } + } + + /* Intervals where the original and the flanks can be written to files. */ + @DataProvider(name = "flankingIntervalsFiles") + public Object[][] getFlankingIntervalsFiles() { + File hg19ReferenceFile = new File(BaseTest.hg19Reference); + int hg19Length1 = hg19GenomeLocParser.getContigInfo("1").getSequenceLength(); + + new FlankingIntervalsFile("atStartBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:1"), + Arrays.asList("1:2")); + + new FlankingIntervalsFile("atStartBase50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:1"), + Arrays.asList("1:2-51")); + + new FlankingIntervalsFile("atStartRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:1-10"), + Arrays.asList("1:11-60")); + + new FlankingIntervalsFile("atEndBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:" + hg19Length1), + Arrays.asList("1:" + (hg19Length1 - 1))); + + new FlankingIntervalsFile("atEndBase50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:" + hg19Length1), + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 50, hg19Length1 - 1))); + + new FlankingIntervalsFile("atEndRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 10, hg19Length1)), + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 60, hg19Length1 - 11))); + + new FlankingIntervalsFile("nearStartBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:2"), + Arrays.asList("1:1", "1:3")); + + new FlankingIntervalsFile("nearStartRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:21-30"), + Arrays.asList("1:1-20", "1:31-80")); + + new FlankingIntervalsFile("nearEndBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:" + (hg19Length1 - 1)), + Arrays.asList("1:" + (hg19Length1 - 2), "1:" + hg19Length1)); + + new FlankingIntervalsFile("nearEndRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 30, hg19Length1 - 21)), + Arrays.asList( + String.format("1:%d-%d", hg19Length1 - 80, hg19Length1 - 31), + String.format("1:%d-%d", hg19Length1 - 20, hg19Length1))); + + new FlankingIntervalsFile("beyondStartBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:3"), + Arrays.asList("1:2", "1:4")); + + new FlankingIntervalsFile("beyondStartRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200"), + Arrays.asList("1:51-100", "1:201-250")); + + new FlankingIntervalsFile("beyondEndBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:" + (hg19Length1 - 3)), + Arrays.asList("1:" + (hg19Length1 - 4), "1:" + (hg19Length1 - 2))); + + new FlankingIntervalsFile("beyondEndRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 200, hg19Length1 - 101)), + Arrays.asList( + String.format("1:%d-%d", hg19Length1 - 250, hg19Length1 - 201), + String.format("1:%d-%d", hg19Length1 - 100, hg19Length1 - 51))); + + new FlankingIntervalsFile("betweenFar50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:401-500"), + Arrays.asList("1:51-100", "1:201-250", "1:351-400", "1:501-550")); + + new FlankingIntervalsFile("betweenSpan50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:301-400"), + Arrays.asList("1:51-100", "1:201-300", "1:401-450")); + + new FlankingIntervalsFile("betweenOverlap50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:271-400"), + Arrays.asList("1:51-100", "1:201-270", "1:401-450")); + + new FlankingIntervalsFile("betweenShort50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:221-400"), + Arrays.asList("1:51-100", "1:201-220", "1:401-450")); + + new FlankingIntervalsFile("betweenNone50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:121-400"), + Arrays.asList("1:51-100", "1:401-450")); + + new FlankingIntervalsFile("twoContigs", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "2:301-400"), + Arrays.asList("1:51-100", "1:201-250", "2:251-300", "2:401-450")); + + // Explicit testing a problematic agilent target pair + new FlankingIntervalsFile("badAgilent", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("2:74756257-74756411", "2:74756487-74756628"), + // wrong! ("2:74756206-74756256", "2:74756412-74756462", "2:74756436-74756486", "2:74756629-74756679") + Arrays.asList("2:74756207-74756256", "2:74756412-74756486", "2:74756629-74756678")); + + return TestDataProvider.getTests(FlankingIntervalsFile.class); + } + + /* Intervals where either the original and/or the flanks cannot be written to a file. */ + @DataProvider(name = "flankingIntervalsLists") + public Object[][] getFlankingIntervalsLists() { + File hg19ReferenceFile = new File(BaseTest.hg19Reference); + List empty = Collections.emptyList(); + + new FlankingIntervalsList("empty", hg19ReferenceFile, hg19GenomeLocParser, 50, + empty, + empty); + + new FlankingIntervalsList("unmapped", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("unmapped"), + empty); + + new FlankingIntervalsList("fullContig", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1"), + empty); + + new FlankingIntervalsList("fullContigs", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1", "2", "3"), + empty); + + new FlankingIntervalsList("betweenWithUnmapped", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:301-400", "unmapped"), + Arrays.asList("1:51-100", "1:201-300", "1:401-450")); + + return TestDataProvider.getTests(FlankingIntervalsList.class); + } + + @Test(dataProvider = "flankingIntervalsFiles") + public void testWriteFlankingIntervals(FlankingIntervalsTestData data) throws Exception { + File originalFile = createTempFile("original.", ".intervals"); + File flankingFile = createTempFile("flanking.", ".intervals"); + try { + List lines = new ArrayList(); + for (GenomeLoc loc: data.original) + lines.add(loc.toString()); + FileUtils.writeLines(originalFile, lines); + + IntervalUtils.writeFlankingIntervals(data.referenceFile, originalFile, flankingFile, data.basePairs); + + List actual = IntervalUtils.intervalFileToList(data.parser, flankingFile.getAbsolutePath()); + + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.expected); + Assert.assertEquals(actual, data.expected, description); + } finally { + FileUtils.deleteQuietly(originalFile); + FileUtils.deleteQuietly(flankingFile); + } + } + + @Test(dataProvider = "flankingIntervalsLists", expectedExceptions = UserException.class) + public void testWritingBadFlankingIntervals(FlankingIntervalsTestData data) throws Exception { + File originalFile = createTempFile("original.", ".intervals"); + File flankingFile = createTempFile("flanking.", ".intervals"); + try { + List lines = new ArrayList(); + for (GenomeLoc loc: data.original) + lines.add(loc.toString()); + FileUtils.writeLines(originalFile, lines); + + // Should throw a user exception on bad input if either the original + // intervals are empty or if the flanking intervals are empty + IntervalUtils.writeFlankingIntervals(data.referenceFile, originalFile, flankingFile, data.basePairs); + } finally { + FileUtils.deleteQuietly(originalFile); + FileUtils.deleteQuietly(flankingFile); + } + } + + @Test(dataProvider = "flankingIntervalsLists") + public void testGetFlankingIntervals(FlankingIntervalsTestData data) { + List actual = IntervalUtils.getFlankingIntervals(data.parser, data.original, data.basePairs); + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.expected); + Assert.assertEquals(actual, data.expected, description); + } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala new file mode 100644 index 000000000..d90db0de4 --- /dev/null +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.queue.extensions.gatk + +import org.broadinstitute.sting.queue.function.InProcessFunction +import org.broadinstitute.sting.commandline.{Output, Argument, Input} +import java.io.File +import org.broadinstitute.sting.utils.interval.IntervalUtils + +class WriteFlankingIntervalsFunction extends InProcessFunction { + @Input(doc="The reference sequence") + var reference : File = _ + + @Input(doc="The interval list to flank") + var inputIntervals : File = _ + + @Output(doc="The output intervals file to write to") + var outputIntervals: File = _ + + @Argument(doc="Number of base pair to flank the input intervals") + var flankSize : Int = _ + + def run() { + IntervalUtils.writeFlankingIntervals(reference, inputIntervals, outputIntervals, flankSize) + } +} diff --git a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala b/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala deleted file mode 100755 index 77eb3ccbc..000000000 --- a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala +++ /dev/null @@ -1,135 +0,0 @@ -package org.broadinstitute.sting.queue.library.ipf.intervals - -import org.broadinstitute.sting.queue.function.InProcessFunction -import org.broadinstitute.sting.commandline._ -import java.io.{PrintStream, File} -import collection.JavaConversions._ -import org.broadinstitute.sting.utils.text.XReadLines -import net.sf.picard.reference.FastaSequenceFile -import org.broadinstitute.sting.utils.{GenomeLoc, GenomeLocParser} -import collection.immutable.TreeSet - -// todo -- this is unsafe. Need to use a reference dictionary to ensure no off-contig targets are created -class ExpandIntervals(in : File, start: Int, size: Int, out: File, ref: File, ipType: String, opType: String) extends InProcessFunction { - @Input(doc="The interval list to expand") val inList : File = in - @Input(doc="The reference sequence") val refDict : File = ref - @Argument(doc="Number of basepair to start the expanded interval") val startInt : Int = start - @Argument(doc="Number of baispair to stop the expanded interval") val sizeInt : Int = size - @Output(doc="The output intervals file to write to") val outList : File = out - @Argument(doc="The output format for the intervals") val outTypeStr = opType - @Argument(doc="The input format for the intervals") val inTypeStr = ipType - - var output : PrintStream = _ - var parser : GenomeLocParser = _ - var xrl : XReadLines = _ - val outType = IntervalFormatType.convert(outTypeStr) - val inType = IntervalFormatType.convert(inTypeStr) - - var offsetIn : Int = 0 - var offsetOut : Int = 0 - - var first : Boolean = true - var lastTwo : (GenomeLoc,GenomeLoc) = _ - - var intervalCache : TreeSet[GenomeLoc] = _ - val LINES_TO_CACHE : Int = 1000 - - def run = { - output = new PrintStream(outList) - intervalCache = new TreeSet[GenomeLoc]()(new Ordering[GenomeLoc]{ - def compare(o1: GenomeLoc, o2: GenomeLoc) : Int = { o1.compareTo(o2) } - }) - parser = new GenomeLocParser(new FastaSequenceFile(ref,true)) - xrl = new XReadLines(inList) - offsetIn = if (isBed(inType)) 1 else 0 - offsetOut = if( isBed(outType)) 1 else 0 - var line : String = xrl.next - while ( line.startsWith("@") ) { - line = xrl.next - } - var prevLoc: GenomeLoc = null - var curLoc: GenomeLoc = null - var nextLoc : GenomeLoc = parseGenomeInterval(line) - var linesProcessed : Int = 1 - while ( prevLoc != null || curLoc != null || nextLoc != null ) { - prevLoc = curLoc - curLoc = nextLoc - nextLoc = if ( xrl.hasNext ) parseGenomeInterval(xrl.next) else null - if ( curLoc != null ) { - val left: GenomeLoc = refine(expandLeft(curLoc),prevLoc) - val right: GenomeLoc = refine(expandRight(curLoc),nextLoc) - if ( left != null ) { - intervalCache += left - } - if ( right != null ) { - intervalCache += right - } - } - linesProcessed += 1 - if ( linesProcessed % LINES_TO_CACHE == 0 ) { - val toPrint = intervalCache.filter( u => (u.isBefore(prevLoc) && u.distance(prevLoc) > startInt+sizeInt)) - intervalCache = intervalCache -- toPrint - toPrint.foreach(u => output.print("%s%n".format(repr(u)))) - } - //System.out.printf("%s".format(if ( curLoc == null ) "null" else repr(curLoc))) - } - - intervalCache.foreach(u => output.print("%s%n".format(repr(u)))) - - output.close() - } - - def expandLeft(g: GenomeLoc) : GenomeLoc = { - parser.createGenomeLoc(g.getContig,g.getStart-startInt-sizeInt,g.getStart-startInt) - } - - def expandRight(g: GenomeLoc) : GenomeLoc = { - parser.createGenomeLoc(g.getContig,g.getStop+startInt,g.getStop+startInt+sizeInt) - } - - def refine(newG: GenomeLoc, borderG: GenomeLoc) : GenomeLoc = { - if ( borderG == null || ! newG.overlapsP(borderG) ) { - return newG - } else { - if ( newG.getStart < borderG.getStart ) { - if ( borderG.getStart - startInt > newG.getStart ) { - return parser.createGenomeLoc(newG.getContig,newG.getStart,borderG.getStart-startInt) - } - } else { - if ( borderG.getStop + startInt < newG.getStop ){ - return parser.createGenomeLoc(newG.getContig,borderG.getStop+startInt,newG.getStop) - } - } - } - - null - } - - def repr(loc : GenomeLoc) : String = { - if ( loc == null ) return "null" - if ( outType == IntervalFormatType.INTERVALS ) { - return "%s:%d-%d".format(loc.getContig,loc.getStart,loc.getStop) - } else { - return "%s\t%d\t%d".format(loc.getContig,loc.getStart-offsetOut,loc.getStop+offsetOut) - } - } - - def isBed(t: IntervalFormatType.IntervalFormatType) : Boolean = { - t == IntervalFormatType.BED - } - - def parseGenomeInterval( s : String ) : GenomeLoc = { - val sp = s.split("\\s+") - // todo -- maybe specify whether the bed format [0,6) --> (1,2,3,4,5) is what's wanted - if ( s.contains(":") ) parser.parseGenomeLoc(s) else parser.createGenomeLoc(sp(0),sp(1).toInt+offsetIn,sp(2).toInt-offsetIn) - } - - object IntervalFormatType extends Enumeration("INTERVALS","BED","TDF") { - type IntervalFormatType = Value - val INTERVALS,BED,TDF = Value - - def convert(s : String) : IntervalFormatType = { - if ( s.equals("INTERVALS") ) INTERVALS else { if (s.equals("BED") ) BED else TDF} - } - } -} \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala b/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala deleted file mode 100755 index e929477a1..000000000 --- a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala +++ /dev/null @@ -1,70 +0,0 @@ -package org.broadinstitute.sting.queue.library.ipf.intervals - -import org.broadinstitute.sting.queue.function.InProcessFunction -import collection.JavaConversions._ -import org.broadinstitute.sting.commandline._ -import java.io.{PrintStream, File} -import net.sf.samtools.{SAMSequenceRecord, SAMFileHeader, SAMSequenceDictionary} -import org.broadinstitute.sting.utils.text.XReadLines -import org.broadinstitute.sting.utils.{GenomeLoc, GenomeLocParser} - -class IntersectIntervals(iVals: List[File], outFile: File, bed: Boolean) extends InProcessFunction { - @Input(doc="List of interval files to find the intersection of") val intervals : List[File] = iVals - @Output(doc="Output interval file to which to write") val output : File = outFile - @Argument(doc="Assume the input interval lists are sorted in the proper order") var assumeSorted = false - @Argument(doc="Is the tdf in bed file (0-based clopen: 0 5 for {1,2,3,4}?") var isBed = bed - - - var outStream : PrintStream = _ - var contigs : List[String] = Nil - var dict : SAMSequenceDictionary = _ - var parser : GenomeLocParser = _ - - def run = { - outStream = new PrintStream(output) - dict = new SAMSequenceDictionary - // note: memory hog - val sources : List[(List[(String,Int,Int)],Int)] = intervals.map(g => asScalaIterator(new XReadLines(g)).map(u => parse(u)).toList).zipWithIndex - sources.map(u => u._1).flatten.map(u => u._1).distinct.foreach(u => dict.addSequence(new SAMSequenceRecord(u,Integer.MAX_VALUE))) - parser = new GenomeLocParser(dict) - sources.map( (u: (List[(String,Int,Int)],Int)) => u._1.map(g => (newGenomeLoc(g),u._2))).flatten.sortWith( (a,b) => (a._1 compareTo b._1) < 0 ).foldLeft[List[List[(GenomeLoc,Int)]]](Nil)( (a,b) => overlapFold(a,b)).map(u => mapIntersect(u)).filter(h => h != null && h.size > 0).foreach(h => writeOut(h)) - outStream.close() - } - - def writeOut(g : GenomeLoc) : Unit = { - outStream.print("%s%n".format(g.toString)) - } - - def parse(s : String) : (String,Int,Int) = { - if ( s.contains(":") ) { - val split1 = s.split(":") - val split2 = split1(1).split("-") - return (split1(0),split2(0).toInt,split2(1).toInt) - } else { - val split = s.split("\\s+") - return (split(0),split(1).toInt + (if(isBed) 1 else 0) ,split(2).toInt - (if(isBed) 1 else 0) ) - } - } - - def newGenomeLoc(coords : (String,Int,Int) ) : GenomeLoc = { - parser.createGenomeLoc(coords._1,coords._2,coords._3) - } - - def overlapFold( a: List[List[(GenomeLoc,Int)]], b: (GenomeLoc,Int) ) : List[List[(GenomeLoc,Int)]] = { - if ( a.last.forall(u => u._1.overlapsP(b._1)) ) { - a.init :+ (a.last :+ b) - } else { - a :+ ( a.last.dropWhile(u => ! u._1.overlapsP(b._1)) :+ b) - } - } - - def mapIntersect( u: List[(GenomeLoc,Int)]) : GenomeLoc = { - if ( u.map(h => h._2).distinct.sum != range(1,intervals.size).sum ) { // if all sources not accounted for - null - } - u.map(h => h._1).reduceLeft[GenomeLoc]( (a,b) => a.intersect(b) ) - } - - def range(a: Int, b: Int) : Range = new Range(a,b+1,1) - -} \ No newline at end of file From 68b2a0968c7f512c92390104861e9730b13cb165 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Thu, 17 Nov 2011 13:16:22 -0500 Subject: [PATCH 047/113] Updating the HybridSelectionPipeline for SnpEff 2.0.4 RC3 This will have to be done again when the 2.0.4 release becomes official, but it's necessary to do now in order to re-enable the pipeline tests. --- ivy.xml | 2 +- .../{snpeff-2.0.2.jar => snpeff-2.0.4rc3.jar} | Bin 4113631 -> 4155641 bytes .../{snpeff-2.0.2.xml => snpeff-2.0.4rc3.xml} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename settings/repository/net.sf.snpeff/{snpeff-2.0.2.jar => snpeff-2.0.4rc3.jar} (88%) mode change 100755 => 100644 rename settings/repository/net.sf.snpeff/{snpeff-2.0.2.xml => snpeff-2.0.4rc3.xml} (77%) diff --git a/ivy.xml b/ivy.xml index 96c1de844..ee24bc367 100644 --- a/ivy.xml +++ b/ivy.xml @@ -76,7 +76,7 @@ - + diff --git a/settings/repository/net.sf.snpeff/snpeff-2.0.2.jar b/settings/repository/net.sf.snpeff/snpeff-2.0.4rc3.jar old mode 100755 new mode 100644 similarity index 88% rename from settings/repository/net.sf.snpeff/snpeff-2.0.2.jar rename to settings/repository/net.sf.snpeff/snpeff-2.0.4rc3.jar index bfd06f97f8c93dd980044ccea2021e1fe97e1040..ee5d02367293107c0e4383274daec7fc91d4d53f GIT binary patch delta 319310 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

FOU+lhy| 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 m?e~3sQ;c*q>SeOzwt1w$7vac#3l}`~x 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

-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

R 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

s!t&W|)iGy(E_CgQ0@H zf%l!+tSK%b?87Z1JzI;9hX#rMB0G!^MY!-a|6CEiDH_BwZyTL?<0n5A(fe;p8uerC zzu76s24hs=NJ9K0WsRSU?6+?7x{7a$Mzkp6fX3EBe4EtP5cx}sA@8{Cq&uSQjDJPo z_6!%7@4V8roI~Rkz5Ifh!g>AZP z#KXUAmjzwJ59@4o31$ud;i_8x zV^re#VmrHwAy&|b#cg+Sw)M*f3s+mEz_r%w5^T$WPp}4wnSqpfXau&zdstuL$Mp9} z(Dyn4opjGg4-eyWt+ujtLIw2dJv`ZoS8xg>X!kM#8hPJHuL$7NuC}sMBiLpDY{Y#Y zz|-%GjXNJt^h%Z?@O53y?K*^Q4TNpUtVM>msNlk8opp({ZJY6d*hvSHIH+Jl@sU_m zj_<|_y%&|@!a84a(KAIe6ZH>1A}at6Mi-WT(uCRz62+~?=u%jwE)e+ejJu=d30r(EC&}_HH z0+9P0`x$%=7Y~o)L%0IAwTTt;u=R2SFz`U^pTYV(HsYalw%8)0>+8Y}JT~GXdWLW2 z+Ky0P-&F_}`38UQ``%0OFJ$NiIPI?;6fVJ>#Pm-~J6^dC$a*cw#O`@hao9 z5|e7|-QEtdXmYiDv9zLZD;>J0?Wj@2wRQBnGf=~ zjP|?1)O(U^?WPbp_g$ptuXbTqt4dpiFC+`L$8UCF9g4B`pI_J2pQ*p_2H=y)0(x#M zN9&@s8!WU&J2)+zZE5W4%sv&DSbg9_>k2c`1y2!7w6YQYK(KqW3m?U9?y6=Dnz_2L zhA)Ne{Zg#u*lv!dSgJp00iq6YK$ZK7Cm?*BSuo4thzm2ecQx4dQ+o?&K8o8|9bC1x z;6@AB_Jj*N_sU2w$h^1EoX+6*qz6JO?O&mn{uQfvc$NcY*^+-t8aXqfqGCIm^Z@^) zp_y>eg)Qrc*uITVL!$zG1KPH?BQJ??-0PC8X}q>@C4;*%kkGZzDEyJjx?bk&l32u7 zq-`}CuW}XzJNKGT$oROl;2*E+BA|K00eom$0PJsyK&AlTqtgOVbV~&G2>?DoEdb^_ z@a7w16xyTuPo$0eM`UjOA6JiWQrnu<>b?kX8tv+0+Y9(+wJl$p$0FZ*L5=%cQGeT~ zB2XKPBvg!#TL-Z|Z^eXh`niZQ0bOGet69VWIkR0`HJ`IBeheIZ7s*X4y4Q|R0GnTm z?6kJBWU(@yP5zh;_wR&=l)cYukv-}THws_Wwrx!EeZNY(#??gcLp3B9& z7l9M+kzC&!@%9-TZlR6%C^CyHsgo9<8Xl8HY{{AJN-E@osLJOnM`i!xt~Je>za5Nv zL`%8@Xp~;!1DuyFdt(e>8&A5rvA|O%@;1KqZM!Pj9c9Dfd(72Ea%@NH6Uw#iMURHv z`A+@#=n~lq|Ksj8NuL;)+;=FXS9~Yk9&eL~#`)TY5k5O^YoI*d%I3zhPP@wZ)PRQI zv*fmX$_h4oH9iT?QjrCHFE1WZAGQC05Q&qx$G4Io()Juu*R-cRYvJSOwp!OEmT1kr zfH+(|@3pe&i z-`!-hTN`AGepvdIoJ_)bcE=l7MLd}HF#<(Q<@n4xKHB-k=**WVLz-II+}XJo$lsYl z#vlAetb120fn(DbV+aijZLf*t#vbhdIRXyqgf*9RqcrwsS79Uh^^0)KPV8~7e{l%| zJ00wQGkJ9q+3S8a(yM*=$h$B|YIhX@h7Y_0z<1sSpjtN$gj&XDotN(`KEN!OgUzPY zqkn?avy_|eyNNNY&C0)$;jm%2b0M|vTvwLtV2VSz zCzTK9yRsP$CYl7zRQ~*%E3@zA7QkLOm}p<(;t1kjJ49ltBPX^XV#{43afYKw4^DjY zUhBAScZs+wD|R%|zR0T-kdFIG5EoWI*hCxi)`ZkJD~U4j)=$3aiW3W!T|9D z5htE^Wd*(5!kD%r-=5TW0x|OfC#u=zVID4QOIeOACB*3x5GNB3sn`e3c}3*Ryyl6` zBkqj=Y3U5kxN96yvCq}HU2BNQo*~{Qx`py5j=|Y@3LaVVaWT0&Mip`PAU13YzT++` zs)rxKb9@QJ*VUwA&uj1mx|oP(Uy8&Ty*TkX5i?$MVj>jRc18K%exyIi3A&SB5oa%LbbQ9ges&A6IY-AwKdGH-@oHl>cg?S@*;dAl+9b@;x#%ANbgXA2lj?qGZpz@EF~(_@oV zCffgObF<_;3cPDU0=UuLM87j}%H5=O$dC~Y@9(mu`=KPt14@P%SfYmsFD0{42S6J^ zw4d;Hpr=X6HXm@qLRRR~i~^w3s{l5dzRN%S8L?c0>z|A{zDd z?^HY+O+^Kp@65Z?*$cG0sYDB4u~*%^StC5P0_K_5$-Zi+t!EVho#Msa)cA&^l~GmM z5ijnm7oq@{+Sc{8 z#$RUDOJ zbad$|68AZ{m$t-xydRL~-9=bGZF8< ziHPOV(+l_@FR>(+v$A-w8OPkM zS-toKEU2%cR>#<|i1~e%1{Rc!w*f|UZVA^eW zKg&pcc^9?a2taM8YYAw`KPB1hCW+0gEdba5L3R?uYwLY?+j(zVUG`Fk4-rOtbH|R* zL9AbWfwo`AX@x{{ZA7%n=#(fuu{04_Y-fPU1XOHBfSOMLVE&tEXFC%un5peOtXV$N z0O03t0?^UGqh>hKBDxDUw+hAQ32-*kpmWH_Svgg02y5{1k!aTZXr=Y#Gq1P8Dbe!%d!Sqy|wft{ECe zK>evGis0!sEbaXH%vDIThjMHH-*6Kh<~NR(7#=m-hNX8nw{L1VWM9m+0wk7K1+_&* zqqd>A2TP5CK~C&c6?BrgtcZz#nD1H}v|9vkM>YYMMPO%yi8emPwLEN-^QjH)>_vpB zERepfQBzD=EI8{QbZ0lpVp+KalLCB*boXI|fQJ5rz`rLG9Gl$`pitU`SgA_Ht&5<&eb)IWE5R30x*o8@A^k_6~ zSUEJU^?ebsUC94IGMYym!Bcs{LOw7W#Le$HF*tV~x=0+(T?wf1NdVX{!+7_^%_rxf zYaKm4yR+;VjK`u^p4PRgXAETSei4zv*FeN|X*(beUF_ND7(Uc@Q*pl|6*bIu!U%~4 zFc42gk!o7bqjS0u4bQlP7R)C0=3AiD9WbOY7BME13qbcB9+vqmcc&z7ugO_-9FLb4 zanLkZ!3yHA8_0>{+VhCET}L!paXh2quLdg0*$J7KH4tjV0|+lefQ*ff=dv@2mJuiD z+jI&uBISO|Y}8@$vuF9}=QZeOy`-P3#IvvE5v-)FEb^QVCu~^YcMRXMe zkwxuH{A~%!MfEE6yG(Cm4a1^G`fSB-l>}~ahPY84cDg1JjS_9;3MdvNQ&Gw4XY;1$ z9>SqZD!`$i>Jbpg_E+GZzC_HE8VWS8iaZSZJOQ(@BADwoVzn!p%39{Ky@xZd2-98F zCTvzE^dY5x+LDpIJ2wURH)H2JV@np+RDeeav%{@oD2%KGg}Z+gfHU5F-P5NMYS*s9DlvT*N=4<=k(PoH#3Pie7nL9_6$(V$2vuqTTp)EX@Be9kRWd|ZoobSO3 zDtN|QhzE#RXA$RAvHS#2Yu@uo0kUfYNAQ`u6yB=#M69=2Brd8ew#t(HT19rLK=0nQ z#QZ5;qWNs`V0G$wD%td=e7X9O6gm9n!PG_*-9btHjmL7mx_PM)P$TNCfPVFb55@eSc0o=!u}}#>&aRS~;V4NQ)Chp7IUn_!s-T7FiAEsW|lc7XYF!jNbC_F~Y#gzyMV4tgU59eQi1V6SImLvf6s`1R#{x1M+$57Bz_GC@2 zd8$h0T1%Shzhn;WNX%zkc-VlM4W6lf#|TH1v^)R@6^9gaY!qst;vgVZ#`{Sl^$(a zR!x4AvZf~Zy0jxPm1J%$RSSTo0|CNO8O=I$jzB`FYtc*@ph&$=cQC@seE|?9+yOvZC6U&2I!F zBh6ItU_MRA;E^1uU>Ojf_dOhP6OS*^>)X57RGLkCYr+TuSI^0v< z;S%IAklS_mWPdwCg6x^j5jpcITaqZ-M|t6XT3w2mnIfDM(fI zYns60rffqH4<8qazMZ_RQ}JLz#-8wGeto=@mZ|sz5jUL@iQ@U1yb*{4&Wc3a^RF7z-59rCKFH2xocB_K8@3eI1z2K#X+qf52*1ip7Dp}dNIIqn}RmZmD2)PVpBfs8xygnn-?3s!b@(MvKbM>J-t|m z)gX%DnYPZ$Hc`oaa+~pEm9S<=CeFSn1+q5HOmrvf<^~Dt?|y7vGZQ@vl)fF!z10je zfI`NeHZzs6q%OOrij+FY4QKHDoAlswQW;k9M;?;xendrH$!zqGJRNMJ;>#3vPAGn% z;`mgiZO*f&_jS~i*&H=JPA3T!OS@jO2{?Nb=t)A?RwGo&zTET@)NlHS=NxuMiNGTkyzg(gNO^*n$(Sw{ez%=uoF6)3w6VH2+^j4(;JB&GjpHz8$fjW)u9o3mqa}15X(IqPo%m8^8PQ@g1X}NFB`cM$1Qc~3fL5%HTA?@v zM-{D*rmuQ?Tlav z>qW$_G`nxg=Q6?UYl^os^YGwvqjycHq}xP}kJpK2B-Zp6wwtK27_j(MEsg9`>oHc_ znaCUGYfF#~8#$t2w2cJ3p&eS5wnZe0yTyVAs7#T8$`)@Ek;S+R1@`tCU~jgA6Xk44 z26x0pBCa~hiT*6KK9B8>M2tHj5=V3@@o?StK#GC%ZqJ=NkID~E;a!lf-gwDbxXE>& zi2KfPqLPW5#7Z4NtbR@;iW|fnBIf@o63=$vc0MIy(~F!a+%kgkm460t<^xU)U|BzN zJ0}s*|FK9s@H5YCSBQ84#Jr9eQQ}cyWJeI~pNT~An6P-V#J#6paG2JtSBSmkOObuc zG_n7IBy<8&?KMYKEXK9u4#NZ>4t_3dX-AB?1D&{mmx&hPF3@~CbNkA62CcLQtJj(5 zx8hakuc@7plulv2-vuelOE}m&N_?9TFGSl{hVPAnyMR{JSD-y@Vp3R+9){jMAD)HO zLcfeCeRF$FX@UF8MsMq#t3p!KIgkKa*{Zvu_@E3GgIG`mw>*<*1Ir4uRb9E|2Z&Y~ zKP1qV-*9ho9G0uPA=1i4xmd&UgQ+_3NGD2WEH*H z`9j2xcn|H|T`2C|9g5f2;_TQzbmvyxc>rWTA$#kIi08wS===a}4lC=ktR5y^$<6>R zY}f<9pA89!x43N2mNEHyxa={OXeH0W&;k+2W`T9i2-ToHD$H&j&)i}LGxyMA2r={Mfv?ae*DdU6mI|2Sx zP8H$qWndpY17KN`Oqmhmy#`B5+?=*~=7s#`GL6ec|Q>UED$-x1o;aQ z)BoT^>zUb6B2K@|i9zgZKW;*Be-QJoiNwbJxe48gIO)1Ko7SH{6SI%XgKl`Ut%H4p z4GXOC7yx4Po8ByY0M2u*M^ixB5i;gBN9fsxtyIqVN8}t8?`hpgc@G3~>^>YYkS{Cw`BYDxIF##X#d;&WyW(Z0%wMv49j72Bx< zmbN(d0Ik+w&@@`2srZvTv^=7n_5j!D<}90@I_VfsXb?L)xa8L7s)bi?2+yPxR}j#& zLLci^DRBstZm-C`;wni^2hJl(@`oV*FH8_n+tZ30Bvz?10d#_zJQT(J6oJq7?!W>9 zI;9d|y_c+h>JyYZ42s`3B6Q6w%I#Z&Gs zB8E-!Ve#2G+%krXtA4P%jt1g9MMT8R(~^ie(?nvAER({LS?{%V{?HpPJ&j3R>e72N zME2s*eA(=s4IN8naU$LKXhY@O%Xv9~#iW+FEGOL;pZvp}0mmLIMR-@buQe>2Q~^x) z0vyY&@?q*R{1~v-7-$PyzShP6W0G0AK}1e_ zn-rE!Men7mu}l+*O@;Gj5pGl0*SZ#2HWq@%wsJ(z3iUkK7Zb7BVNMKW5#xASXM7zYcPKu36($5lzR0a zk!R!t-k#hEu;tAIPAp@w;4u;JJoI6n6Z!J6U@XMzPlWj5M?P%xcwd{F2WZcviC`c2 zgd^6OL_G;I#U%6onJ8nMs}m+crb&^ASkD}wZ7v~RuQ;Nz94g!+VvM6NRUX7fE-i_z zN|S+XcM_4z$-F}*5^<2TFWb5d!6V)cI0vEwo}qd**+eh;EL{an$y1P;hTw)k4o1z) zso0xkEilpRNmHkQxrL7{FpWR=QB1)5(zd`n9-CXX`P$}`srW@LQKmG2ny0>BDogl2)lcl-TFOw8^g3a zFuSRmTUp9LtjkP@U1(*c1zJoyPC)N=R>0rljfhzQ^128>o|_*gIKksN1kCSY1Bf2a zA}06Qa8A8UE2^}#qs459-R)-u0xT8nBB0DL0cbY|H+`u^uvs?;K;S4VK+hXYBAPRP z_KN%m<`7Feo)b_s(F$05weeg4^=1k{UX-7;FSBA{?%BCy?pzU#^|SV+dLG2==Uc_B zeR(j^&o)jMEf!b~%;U4vJres1KZKQop3%-XIkOS<{j8&+K`zh>YraE;p|Q0V3_6($ zgQjixjzt(GGWP%a`U>!>j_2(JNQe=4y-^@Uu|PuZO>j@3xP{;pNhlPG2Z}qiWRZ{r zcWZIi0>w*lcZVRw<$Gt2+5W)UrWn@*NYj)%-+9P5p?;LUg0&Bf7Tt zwS2T>`OADLIPla2$o$R8Me$O%7=2j;mGYx@4rGVFDS}E_VfYQN8`qF15C6jLw?Np#J&rhrlmYVtatPn0~ex8qEuUrS$5l|^g z1!#87eIaNsV^x}3HHHx|wXzDR*?5b9i+bGji}DiV?Q9`Z7aOfncM;GF)r+8#N!AfM zwRRB{CQ%0Qqg#|7^b`a5o|wwR-5Ba`$*3|7v~pyz0gIuqD|*OkWPFzxoz4wHyP)@L zijwE4y##=NTP>ivpEZQ;PSc{Q4{}@z)TW0CnpWIdbwq;+z0*sJdSTIm+m2g=j?2)Z zs>$^Z&hDb^GL%BE0a_N-_<9ep>|vH+DvO(@4p}Wn7`H~KsM;!ZBjC5ODqtZpS75O~ zuJR`dojI{EDp&c8GR~%svi?*pA8j_Cy#fmE&C~*F3-pfAfO#fp&Pq6Zxny=&3DhrF zi_(VvLj;^(s0G}Wf?sA8(2QkTRBelH)`P9`SHYY+)=4Y{O`NgiLTdTWGy-a~8tCdj zOi($!?N&o`{uUL$J~VVVlrn0uapCU{R=D^B$9l9Es!!diir{FZ)D;O^16>6NB_Juy z8Rrb74v1rCL}Bs*MByo2OpfXV)L$tp??`OnaTV}YQk~XIlNa%4R2IAjB?Y->Eo4Vt zl7Q3wopI|Tf>egh)iUjtNga#f*HjiCCEN+?AY0*<3cv$O9iLYto!zt7Beo~*si21v zV*T}y`u}~eS?5pbFpy^^u5e?#-x+f_o%fIK%RvP1StWHSb~7| zZA3t=tyL5)uoO_KKOlyQWksK-5>Q_P%42GS0;;#k8TV0{^zr;RX|m@draNRlwJj<0 zC&e^C0$%KJ4pK_QR04hqRsnA%Qwj)hEv*8ouWGUhz%o4hL193Z>ZUdjFcT9W1f*<6 z4@{J(>(rNup^tnA1x|*FGw}XBf6Be*k_70=&BSb&?vqC4;`K~T_Yin zHA~9^XCZZ4ChUTO5AeHG3^?$}-Nqh40io~VcTG|17PQz6VC^C;pf*%H2(7(bi>hsu z?;fDD*PEcN_ap{F=}-dN!`rH%J+ANKh*#UWZM;ON!*&z2V=EVFLSgb$Z_JN0{qxs{nLKZCd*S0CMk}0CGGI5isMi3Ir*2B6lJpXgCN{41S@a zYBTbEri*E-f90LZQbI({a#1@y-)BP$kEE}BGG)njQHNk{4j~3JznP-5kh`+0P07^+ z6kc3x+yRd@BG*Ojz32`@L4R8jxE%#K^G_FVr2Az88 zY#N%N9UmgE<$g`lQ?TsV0v4ZCEel)_I>JiQ*I3M&MbD8f#`z*`H_E=5R!*(DH{U>Y zwc}8|1DiTjb349uQ5)9@#}V_<*mVsAPqWnt0Ap~d#Z*k~Y3?R8 z3I|wBQMIS3I|2x}wPv z#OTZll}GA?L+gaI5bnEH1=OzgGXl14RsjofTz-%%O~{vOD(EaoDvs-&Uvdv)r!-N z(A!^3Q2R^9;rkh&#s4vL#l(hd1tELWDwjY!Qi6$96^rJASZtHE*mo&0neO$b>C+#d zPFgV%p86!5tJYJyiR6>y5w9R9xfj;!GPFK-a}}e;yZVTv%jhDJr&0`b;;-Jl48nXZ z_At-#l9?Q@B+{H$+!aWM`5Psrnuo@mZ#Jas3B3aX@uF$BpSpI%-I%?){#4|81`&yO zlsnhgGIdVx+@t7C4kk^G$t{?4J;dNb;EdyvT}8hGuI?t!DbodyVl`4&ya&LID}3CGhRm|_ z0jGQWx{Bl15^=Y!6h2>Fae}n2^TqQ1bt58J=o_F>RsCE=V^fGyQarj0q(=LD6b+7J z)0yD?{{JI3^sIHU>@vdqMq-dA9>*bawp0LiSl%cckFS7cjt+1Y3rrz4UQ}6)@p@-; zFJe4SW!ti@BG6XnCVX$2Xpgvwq)b(y4mT4WaHkxVEzuUQ6&E{PR?N7`Tfes5q>5Np zT)9byyM^^Fe7*=503A8672V+`{~2{w?0U81uKQY5&ePn$XH%cDoJcVim-U z+lgVK{cT>T?@P3gm5D|yN9gmPc!|Z~4zuX)fcBu8MC&2Z1{2LPiD;o>-yQDipCQ_~ zT4KZ9#312%mzSv`?}9cE7Z?z&-(6nvokp}@>l2N437u^?Az|A7`$7LELsg2Eo4a`O04syxeO$$whuDkY@sMp9Z4E^K5Qt4*8R_+q*P17StN5u*CekWW zM+b0zCC-EaObifZA8`tzB7mek0%ASWRkVA=y9lN{g1BLpt62Xiu{^DGlT{Zv`>yYe zzDGMa%Q7ZDX341MvzgOR1U=@(z$%X+o;S}`w0+FwY9@XAFLV{YPcS3>_%R23N{@gY zo*>|riy5h{1oY;^tod|7Ph$k8*92s>k~w3=)+f9gd-@3!Ph8E!5OE?vPp8wuo`UH2 zhpVXfl()4EqVL4buHySs6q{X7`DoKMBA(pN#2QM#&;D+jQ*oDo%o)=b>f5zn`Jc--9NjE)NHphBHD>0vkO3b{DoJu8*jQGAJ&?wg`WX8gEo zR6XQZHRP9e1La#Bh<%NU(rB5RlW9cLKrA-NkwD%XMW2VzIJ`!Nt>5H^?PnYCpKq@@ z8dh(BcJMGk8@=J<;8~kA@oVd)ve=sjwx8saaj7e%{_7hamj}KD?ICV(C0hHpycgvc zqRj}BXq(=0$?msL6GX#M3C-IFPvY|*M=$h0h=r7qfb{>k<~2TyN;UOA^eoGUOTe(h zS__;asTmOxX!DLw2t~f5NX1CB_!Dl9;&^$Zm3Kbvlw*hLSP9iJTvHuqRggsXzT>9i zDyex;NupIfi&7Bro?}}1J%GAZBw+qIHwRNIO(t+ryaXP3&+e=7B_!kXdrC&41Pr?b zb%`HYUD5}VFi1e7>ux2*mn+D%$sZUExDMIXA0YcFS!HoH4e>e2x2!3Ft8Su4Co8V= z5x~~k5@2)3O>?c=0s`IYN#Ny=iQd%lqf%I@(gYNthAt9a^X0C$^7z?9EyrBy_$ z*;1lC_{;_13(?B7k!U$z5*@^(|J)puiq-K8(6Jpuzaxmx zLnXAa2V_r3=miq{I$Q!$z1;0)`?I-k`?$N$*6VGG?aE?o_C9^d*}e4GjWm0V9usS0 z6e`Zp%)SUp^YOCsF!xeIXHJ|#7lxoy%%SD;Bo%cLecwu}L{xSbxU}CBh5^Wr=&5LD zc9u<*L{6FOX|nabIRXissnV>)@CbJoe(MdWLo7ndD29-p%r;@kEvBa&-dzl`R`XQA z%CYeKdBUl?x1=e4?|L>I@fG#n0S70v9`;+Fq%V&o&BQ+ZQ>^Y% zm1ZIGliW4eTsxH@j^z?ZqY`?$R;@`b#CvE7#5-b@1dOZ&`>iX%nXru{;?_vOq&mp8 z?-Gz!4`Klp5Q|?&sF%o&<2Ej}J%AqxD0hGW4>8q3PZz;$uz;$Thg6!4aBIdTcw~2X z)7{%`PPmCMOFa$J8(8Xb`OaxK(XWqtnD`xEAZ{jN+&@f=;1@tZv@Z$byYpC+FR7;$ zfcEtL&n5nL6SGU|X&2)9fr#3kl6srslddx2DsF-gZLk%%T^`D`I4eC}=+SSiCRz=j zaELBqqLrSmG+RzGsV`(=w6)$=JhIZ$@sf{(M7}g4v~?@p8pIi|j6^m08-WzV(c*K~ z`WP^FSdB<}+rWNR9YvUpzMRrCN(~w`ei3~C1N@cKv;ppzY)f6|9%1TWSKA;wbIfr7 z2o)XIyZb7p^0EbT9)SP}5glzg*D{Hi=i@FeZvi`9^G(xT)a8tBHoo+as2=k%!ZKd{ zOcS3U{KY+6&Zj-Qp)SA5-qV5(;Alot!`zBmUmXGB#*#SRSBdaRRMkelOXZN*t2}HJ!st$CED&w z?%F#_|3O0k@!B+a6580lsB7AK_KQIhJADP!eSy^9drA6IstYFvJq@$!I^eTYP5KNF zJ8rsTd@1oQC*VMBQs5^ZIIyut_t}bwW1Y6YhAw$iqwg?4+kXUAHPAxCKrq_B-du zh@?c7gs$km!=GW=0rDW4!`Un>bvPnD_!f$t6JBXi=~03 zu7z*Rbh_ao8VvT-+?cuB4SMfC#H_fRzPh4fad?o`O%#{JA_Lr!Pna`S407jajdzFm zAJ3WSUDSmC5?t`wLxfNBj1f~eve{!jK%Dv3L#*{crFlHv(_UnGFfxgdjL(eFk;UBk zo~EtyU<^e(^mIBY+!N*FtEFh@$)AJibFYJ#@2MxTWB4q79eX+#Zi0~_*B}5#FAl)l z3rKe_Mzk;3kvllKGPvRqBstx|%uJ{8LJnA}skhTpa~t(W(&bv(Q#3x{skw^V+#9i} ziQ5^y^&TSLTTheF9f(#CO*ERZTaM4-bt>W6hBKZa;)ORy?gJ73jRWzrr@x3ehqyb~ zGSb=y$bkeIndhU&IUBK=h;{T#j5aNaPQCz6CCS`j*E}`-%oo=^OM$`_WccZ6OYT@AHmfHGv*sSGERP&+04Y|ZLAJlX zBKRu#gD;~I^ZAK~l_WRu1gV?vkK)@p1;`ms7qJt3VAnm<#P4&e4A*_;iNjM;391wT zl?$56NUs1rZOxlZ#Olpt;`EoEPVy*JTqDH3rKi|uftk~GueAn=)FHMhZA6Mrp9D2C z@L7CaJ7x|RwjVq-SB<>-sEOT)4l-Z)KyHO^enGO@1|nHI(q&|FAp4Cafl%wzMJD$B z?rE>N>Xte~J-gnu-H-8bFSJNnCmq^Nma7}2M~_gX1wmPb9x`!8aj#OQ`M->@)z z?BCy0q=x9Jdth+#GA(i51DV}h>146l&z3}vM7Y#yT(qqGA#Lvsi zRtyj2bou)NUO@22EKjj2l>LDr0L14+^d5tUYC`pKN=KvR`|d~nL1(iRwoQd-s<_*D zPmvPj6{?i6#*xr5FAO@$&hiw?!?@wU7>1;I%<&XWDtO^?%o7|1U1<<+GnW`;~0f-c3ow-s)!!sWhFX9v0#rV2)@6;h(Yn_{}R#c zE)&ZNU9^;xXb=PLGciaE&C=Gjq|uss;`31EI~&dQ1|k^b8N#rjo{?(M!1w%joAj|>e&TI)YJ0l zV6G`9oZ`^IbVSyPY587wKt9*YL-d&}-9S%CT2O0VLvvJ@k#K3Tnk1FC&V&GH}C~k8#q8zw>DO`eS;ASoZn#{w)N)vD$fcD;%L8Y|9mf}=J zw#ul@m?Kc)xnxv3Zxg<4o6&EPi(IWrh(fCX6LeA~y(bN8DAm16wjXy7{g}ge3nfg^ zhJTlgiV9Rk9F(T`J?UyuMgk%#bN~D0X-z!-u1Kh>@HhkCCbz*kmC;-|5_#e#IwPkRsP?lzK`$2%|W6XGQ1Xp`RRCVCCKP;<3Kd~0dX4S(|pW98aP~o z0pTVu@)AQ6y~B9;M&Z)fdCSyhxQZWdE}+|>J%L$%sh4m{Lirz=CaC>`mb#F!ZmC>xpO%Q;?cXk#Kv@Q?6GO(jl12F zIqS+K19`NU5igO@*4tTGpUEQR+df9T#qMM_!bu{oJ-|eraICHmQW7-&hEFCXXc_!` zs3!Xc{l)BIVpw$!a$I%j*mPW0Y}MV{UU^*PDj_FNc!_V-xk^>7f$s~ayhOh_i1x`E zdWVWd>VRa<33r@Ix}oqqMChh3S}qwi;fzrnPg!Nxgvw9njWl~vw_&KN zW{nq)Mb15`g`6Aq#K>YT0&45&dfSS%A)NJ^k@8|bTk)bP8}?gM1n|>QMq-uPI5{o6lOz1*Viav{OI!VKZ{AcSru?7} zRoH*O^WtF)?r@6sF5C-ScuHn}`UB78ST;jwZr5a@p_$QD_jP%Dj-IVr4+o%KChu|6 z$nGT8G~;ORB1Qe~$Vf_ay@NP2+{a3B1@6rOce!VTY4xTr5hpzI7Bidc`3XgGp_Y3# zCyYY4kC5CDjpNC9!khu(*g_7{wFSh>KbLjpx6qdmOOy`?Ar=;E48>tZ-TO%pX=kV^lyg~u(5;$Y-y$UrQQ!k z;`wj$ELI^BV^MJ$YHSl`C2`xe=9#3lWj>}pGakpLUnS3+l(mC%CIJ~kq$9e1+ZwF8h5r2>xFD-N-QN-VaS(0rVsC#4Yc z5qTY@O#~FTtLbyc|BAwT19dY=lPQn)*cO8Pn}^FDb_zNY1N$Uv3F zRYY@%RPKGqrcQnsI8?(`p_oK z2$3gEM6D#^o6$_f<(r*&u0XFt8hksGdl@SuiJiIlH3ccT$8>|B*GePeB!20}UV8!|%m4Hdw;TJGRou-DBKF_xBh0%STY^tXcW+k0+UO%} zbPUoI-s1aM+*9=Fu6I=$z4tlYZgs{C#$u1cjovSpW%k|OxeQ+Dj?e-g$dN16gImJf zUy&ev4-+$naMOKCFoi| z0?4)%8~f`+#K~@)TRB7vwwGvGD}6O@-I@0S*dF(wkid@BzAj>|U^mi*fH)TkSk;T& z!6BmI`9fy4>CIUhun7iA>W!NF33FqlC9^j#Q_t=V=0iRb?P_oCceL5 zxC;t0`anTKpakUgVb`~_5131Zsx(_+*H;?yldVP_8eOj~Y&8b9()N8?;zTILqi0`f z0*bi(>UxcBh*lyM?X-3(!#rH#?r_-GQPaSXn5)(0ZL|O}6oJ&$l(hwsl2~pA8WW3S zzCNOH2HS1MaU`d229nb&MrEPZvF#a1jt*x}DQ@30*q~mSpuNJeQ=+A1axcaH0u)Tn zgo4NMGV>K*3$ZVg%i~p&z*aCx%N3L&Ilq$nk@^}cU?C>=V>2!%pifN+c#aRm#r`MI z0_vzVYtjBE9>cu4r*Wev>PakR@5A#nn#UV`|0UV4^(DZ&zn))4B4hbH`)O@O7^@nL zrClu=*GS_2rN5pQi>CL7hLSky+FxH2pYQ0i5w0sBBUBq8?ZqL5S(8p~Zh}5d3C!wf zGcBcs#J%R#|Hw3bg_URO;G*72)y>RGYcj0>mmBog2a8jwzLw(a0M4(4Zhp8Q@@G`= zVMDNUkv7fif9As4{%2HH=P^w57r8(4w8CW~jvj9$mKAkhO3%fS7ylt5qEiIs5lUjI zTYFq7agtHSO{^Tqo?`nzXxsanjM)F8w-Fu-xoveH1Z2!?Q>5~6mgz@G@*E>#D|QcJ z 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 - + From 473b860312fb3df70d7e9baa9d421450640edcd6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 17 Nov 2011 15:31:45 -0500 Subject: [PATCH 048/113] Major determinism fix for UG and RankSumTest -- Now these routines all iterate in sample name order (genotypes.iterateInSampleNameOrder) so that the results of UG and the annotator do not depend on the particular order of samples we see for the exact model and the RankSumTest --- .../sting/gatk/walkers/annotator/RankSumTest.java | 4 ++-- .../gatk/walkers/genotyper/ExactAFCalculationModel.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java index ebf33496f..c5a2df1fd 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/RankSumTest.java @@ -43,7 +43,7 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar if (vc.isSNP() && vc.isBiallelic()) { // todo - no current support for multiallelic snps - for ( final Genotype genotype : genotypes ) { + for ( final Genotype genotype : genotypes.iterateInSampleNameOrder() ) { final AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) { continue; @@ -53,7 +53,7 @@ public abstract class RankSumTest extends InfoFieldAnnotation implements Standar } else if (vc.isIndel() || vc.isMixed()) { - for ( final Genotype genotype : genotypes ) { + for ( final Genotype genotype : genotypes.iterateInSampleNameOrder() ) { final AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); if ( context == null ) { continue; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index 980088305..a7240ae88 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -98,7 +98,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { ArrayList genotypeLikelihoods = new ArrayList(); genotypeLikelihoods.add(new double[]{0.0,0.0,0.0}); // dummy - for ( Genotype sample : GLs ) { + for ( Genotype sample : GLs.iterateInSampleNameOrder() ) { if ( sample.hasLikelihoods() ) { double[] gls = sample.getLikelihoods().getAsVector(); @@ -290,12 +290,12 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // todo = can't deal with optimal dynamic programming solution with multiallelic records if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { - sampleIndices.addAll(GLs.getSampleNames()); + sampleIndices.addAll(GLs.getSampleNamesOrderedByName()); sampleIdx = GLs.size(); } else { - for ( final Genotype genotype : GLs ) { + for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { if ( !genotype.hasLikelihoods() ) continue; @@ -419,7 +419,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } - for ( final Genotype genotype : GLs ) { + for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { if ( !genotype.hasLikelihoods() ) continue; Genotype g = GLs.get(genotype.getSampleName()); From 23359d1c6c797f2374484cfdb3d23722bb1331f2 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 17 Nov 2011 15:32:52 -0500 Subject: [PATCH 049/113] Bugfix for pruneVariantContext, which was dropping the ref base for padding --- .../sting/utils/variantcontext/VariantContextUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index b77f606f5..5d2c86f84 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -302,7 +302,7 @@ public class VariantContextUtils { } return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), - vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.getFilters(), attributes); + vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.getFilters(), attributes, vc.getReferenceBaseForIndel()); } public enum GenotypeMergeType { From 02f22cc9f8b451d71c8f04c9fffc7e56a50880a1 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 17 Nov 2011 15:33:09 -0500 Subject: [PATCH 050/113] No more VC integration tests. All tests are now unit tests --- .../VariantContextIntegrationTest.java | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100755 public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextIntegrationTest.java diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextIntegrationTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextIntegrationTest.java deleted file mode 100755 index 67fe7d012..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextIntegrationTest.java +++ /dev/null @@ -1,65 +0,0 @@ - - -package org.broadinstitute.sting.utils.variantcontext; - -import org.broadinstitute.sting.WalkerTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.HashMap; -import java.util.Map; -import java.util.Arrays; - -public class VariantContextIntegrationTest extends WalkerTest { - private static String cmdRoot = "-T TestVariantContext" + - " -R " + b36KGReference; - - private static String root = cmdRoot + - " -L 1:1-1,000,000 -V " + b36dbSNP129; - - private static final class VCITTest extends TestDataProvider { - String args, md5; - - private VCITTest(final String args, final String md5) { - super(VCITTest.class); - this.args = args; - this.md5 = md5; - } - } - - @DataProvider(name = "VCITTestData") - public Object[][] createVCITTestData() { - new VCITTest("--printPerLocus", "e9d0f1fe80659bb55b40aa6c3a2e921e"); - new VCITTest("--printPerLocus --onlyContextsOfType SNP", "0e620db3e45771df42c54a9c0ae4a29f"); - new VCITTest("--printPerLocus --onlyContextsOfType INDEL", "b725c204fefe3814644d50e7c20f9dfe"); - new VCITTest("--printPerLocus --onlyContextsOfType MIXED", "3ccc33f496a1718df55722d11cc14334"); - new VCITTest("--printPerLocus --onlyContextsOfType NO_VARIATION", "39335acdb34c8a2af433dc50d619bcbc"); - new VCITTest("--printPerLocus --takeFirstOnly", "3a45561da042b2b44b6a679744f16103"); - new VCITTest("--printPerLocus --onlyContextsOfType INDEL --onlyContextsStartinAtCurrentPosition", "4746f269ecc377103f83eb61cc162c39"); - new VCITTest("--printPerLocus --onlyContextsStartinAtCurrentPosition", "2749e3fae458650a85a2317e346dc44c"); - new VCITTest("--printPerLocus --takeFirstOnly --onlyContextsStartinAtCurrentPosition", "9bd48c2a40813023e29ffaa23d59d382"); - - return VCITTest.getTests(VCITTest.class); - } - - @Test(dataProvider = "VCITTestData") - public void testConversionSelection(VCITTest test) { - String extraArgs = test.args; - String md5 = test.md5; - - WalkerTestSpec spec = new WalkerTestSpec( root + " " + extraArgs + " -o %s", - 1, // just one output file - Arrays.asList(md5)); - executeTest("testSelectors", spec); - } - - @Test - public void testToVCF() { - // this really just tests that we are seeing the same number of objects over all of chr1 - - WalkerTestSpec spec = new WalkerTestSpec( cmdRoot + " -NO_HEADER -V:VCF3 " + validationDataLocation + "yri.trio.gatk_glftrio.intersection.annotated.filtered.chr1.500.vcf -L 1:1-1000000 -o %s --outputVCF %s", - 2, // just one output file - Arrays.asList("e3c35d0c4b5d4935c84a270f9df0951f", "ff91731213fd0bbdc200ab6fd1c93e63")); - executeTest("testToVCF", spec); - } -} From fa454c88bbc4b366c493ff18dc9f18b295fc2025 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 17 Nov 2011 20:37:22 -0500 Subject: [PATCH 051/113] UnitTests for VariantContext for chrCount, getSampleNames, Order function -- Major change to how chromosomeCounts is computed. Now NO_CALL alleles are always excluded. So ChromosomeCounts(A/.) is 1, the previous result would have been 2. -- Naming changes for getSamplesNameInOrder() --- .../gatk/walkers/annotator/SampleList.java | 2 +- .../variantcontext/GenotypesContext.java | 11 +- .../utils/variantcontext/VariantContext.java | 27 ++-- .../VariantContextBenchmark.java | 4 +- .../VariantContextUnitTest.java | 118 +++++++++++++++++- 5 files changed, 135 insertions(+), 27 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java index ee08cfa3b..84a4a3120 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java @@ -51,7 +51,7 @@ public class SampleList extends InfoFieldAnnotation { return null; StringBuffer samples = new StringBuffer(); - for ( Genotype genotype : vc.getGenotypesSortedByName() ) { + for ( Genotype genotype : vc.getGenotypesOrderedByName() ) { if ( genotype.isCalled() && !genotype.isHomRef() ){ if ( samples.length() > 0 ) samples.append(","); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 3b2de4769..77a02874d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -31,9 +31,9 @@ import java.util.*; */ public class GenotypesContext implements List { public final static GenotypesContext NO_GENOTYPES = - new GenotypesContext(new ArrayList(0), new HashMap(0), new HashSet(0), true); + new GenotypesContext(new ArrayList(0), new HashMap(0), Collections.emptyList(), true); - Set sampleNamesInOrder = null; + List sampleNamesInOrder = null; Map sampleNameToOffset = null; boolean cacheIsInvalid = true; List genotypes; @@ -62,7 +62,7 @@ public class GenotypesContext implements List { private GenotypesContext(final ArrayList genotypes, final Map sampleNameToOffset, - final Set sampleNamesInOrder, + final List sampleNamesInOrder, final boolean immutable) { this.genotypes = genotypes; this.immutable = immutable; @@ -152,7 +152,7 @@ public class GenotypesContext implements List { private void buildCache() { cacheIsInvalid = false; - sampleNamesInOrder = new TreeSet(); + sampleNamesInOrder = new ArrayList(genotypes.size()); sampleNameToOffset = new HashMap(genotypes.size()); for ( int i = 0; i < genotypes.size(); i++ ) { @@ -160,6 +160,7 @@ public class GenotypesContext implements List { sampleNamesInOrder.add(g.getSampleName()); sampleNameToOffset.put(g.getSampleName(), i); } + Collections.sort(sampleNamesInOrder); } @@ -354,7 +355,7 @@ public class GenotypesContext implements List { return sampleNameToOffset.keySet(); } - public Set getSampleNamesOrderedByName() { + public List getSampleNamesOrderedByName() { buildCache(); return sampleNamesInOrder; } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 75e3aac58..7798e259c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -985,11 +985,16 @@ public class VariantContext implements Feature { // to enable tribble intergrati return genotypes; } - public Iterable getGenotypesSortedByName() { + public Iterable getGenotypesOrderedByName() { loadGenotypes(); return genotypes.iterateInSampleNameOrder(); } + public Iterable getGenotypesOrderedBy(Iterable sampleOrdering) { + loadGenotypes(); + return genotypes.iterateInSampleNameOrder(sampleOrdering); + } + /** * Returns a map from sampleName -> Genotype for the genotype associated with sampleName. Returns a map * for consistency with the multi-get function. @@ -1026,7 +1031,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati return getGenotypes().getSampleNames(); } - public Set getSampleNamesOrderedByName() { + public List getSampleNamesOrderedByName() { return getGenotypes().getSampleNamesOrderedByName(); } @@ -1049,7 +1054,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati /** - * Returns the number of chromosomes carrying any allele in the genotypes (i.e., excluding NO_CALLS + * Returns the number of chromosomes carrying any allele in the genotypes (i.e., excluding NO_CALLS) * * @return chromosome count */ @@ -1057,7 +1062,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati int n = 0; for ( final Genotype g : getGenotypes() ) { - n += g.isNoCall() ? 0 : g.getPloidy(); + for ( final Allele a : g.getAlleles() ) + n += a.isNoCall() ? 0 : 1; } return n; @@ -1086,7 +1092,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return true if it's monomorphic */ public boolean isMonomorphic() { - return ! isVariant() || (hasGenotypes() && getHomRefCount() + getNoCallCount() == getNSamples()); + return ! isVariant() || (hasGenotypes() && getChromosomeCount(getReference()) == getChromosomeCount()); } /** @@ -1104,16 +1110,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati genotypeCounts = new int[Genotype.Type.values().length]; for ( final Genotype g : getGenotypes() ) { - if ( g.isNoCall() ) - genotypeCounts[Genotype.Type.NO_CALL.ordinal()]++; - else if ( g.isHomRef() ) - genotypeCounts[Genotype.Type.HOM_REF.ordinal()]++; - else if ( g.isHet() ) - genotypeCounts[Genotype.Type.HET.ordinal()]++; - else if ( g.isHomVar() ) - genotypeCounts[Genotype.Type.HOM_VAR.ordinal()]++; - else - genotypeCounts[Genotype.Type.MIXED.ordinal()]++; + genotypeCounts[g.getType().ordinal()]++; } } } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index 273b8fdf7..fae7cb05a 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -209,7 +209,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { public void run(final VariantContext vc) { ; // TODO - TEST IS BROKEN // int n = 0; -// for ( final Genotype g: vc.getGenotypesSortedByName() ) n++; +// for ( final Genotype g: vc.getGenotypesOrderedByName() ) n++; } }; @@ -335,7 +335,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { // return new FunctionToBenchmark() { // public void run(final org.broadinstitute.sting.utils.variantcontext.v13.VariantContext vc) { // ; // TODO - TEST IS BROKEN -// //vc.getGenotypesSortedByName(); +// //vc.getGenotypesOrderedByName(); // } // }; // diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index f2eb2dd57..5bc72e132 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -263,7 +263,7 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertFalse(vc.isMonomorphic()); Assert.assertTrue(vc.isPolymorphic()); Assert.assertEquals(vc.getGenotype("foo"), g); - Assert.assertEquals(vc.getChromosomeCount(), 2); // we know that there are 2 chromosomes, even though one isn't called + Assert.assertEquals(vc.getChromosomeCount(), 1); // we only have 1 called chromosomes, we exclude the NO_CALL one isn't called Assert.assertEquals(vc.getChromosomeCount(Aref), 0); Assert.assertEquals(vc.getChromosomeCount(C), 1); Assert.assertFalse(vc.getGenotype("foo").isHet()); @@ -690,9 +690,6 @@ public class VariantContextUnitTest extends BaseTest { return SubContextTest.getTests(SubContextTest.class); } - private final static void SubContextTest() { - } - @Test(dataProvider = "SubContextTest") public void runSubContextTest(SubContextTest cfg) { Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); @@ -734,4 +731,117 @@ public class VariantContextUnitTest extends BaseTest { // same sample names => success Assert.assertEquals(sub.getGenotypes().getSampleNames(), expectedGC.getSampleNames()); } + + // -------------------------------------------------------------------------------- + // + // Test sample name functions + // + // -------------------------------------------------------------------------------- + private class SampleNamesTest extends TestDataProvider { + List sampleNames; + List sampleNamesInOrder; + + private SampleNamesTest(List sampleNames, List sampleNamesInOrder) { + super(SampleNamesTest.class); + this.sampleNamesInOrder = sampleNamesInOrder; + this.sampleNames = sampleNames; + } + + public String toString() { + return String.format("%s samples=%s order=%s", super.toString(), sampleNames, sampleNamesInOrder); + } + } + + @DataProvider(name = "SampleNamesTest") + public Object[][] MakeSampleNamesTest() { + new SampleNamesTest(Arrays.asList("1"), Arrays.asList("1")); + new SampleNamesTest(Arrays.asList("2", "1"), Arrays.asList("1", "2")); + new SampleNamesTest(Arrays.asList("1", "2"), Arrays.asList("1", "2")); + new SampleNamesTest(Arrays.asList("1", "2", "3"), Arrays.asList("1", "2", "3")); + new SampleNamesTest(Arrays.asList("2", "1", "3"), Arrays.asList("1", "2", "3")); + new SampleNamesTest(Arrays.asList("2", "3", "1"), Arrays.asList("1", "2", "3")); + new SampleNamesTest(Arrays.asList("3", "1", "2"), Arrays.asList("1", "2", "3")); + new SampleNamesTest(Arrays.asList("3", "2", "1"), Arrays.asList("1", "2", "3")); + new SampleNamesTest(Arrays.asList("NA2", "NA1"), Arrays.asList("NA1", "NA2")); + return SampleNamesTest.getTests(SampleNamesTest.class); + } + + private final static void assertGenotypesAreInOrder(Iterable gIt, List names) { + int i = 0; + for ( final Genotype g : gIt ) { + Assert.assertEquals(g.getSampleName(), names.get(i), "Unexpected genotype ordering"); + i++; + } + } + + + @Test(dataProvider = "SampleNamesTest") + public void runSampleNamesTest(SampleNamesTest cfg) { + GenotypesContext gc = GenotypesContext.create(cfg.sampleNames.size()); + for ( final String name : cfg.sampleNames ) { + gc.add(new Genotype(name, Arrays.asList(Aref, T))); + } + + VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, + snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + + // same sample names => success + Assert.assertEquals(vc.getSampleNames(), new HashSet(cfg.sampleNames), "vc.getSampleNames() = " + vc.getSampleNames()); + Assert.assertEquals(vc.getSampleNamesOrderedByName(), cfg.sampleNamesInOrder, "vc.getSampleNamesOrderedByName() = " + vc.getSampleNamesOrderedByName()); + + assertGenotypesAreInOrder(vc.getGenotypesOrderedByName(), cfg.sampleNamesInOrder); + assertGenotypesAreInOrder(vc.getGenotypesOrderedBy(cfg.sampleNames), cfg.sampleNames); + } + + @Test + public void testGenotypeCounting() { + Genotype noCall = new Genotype("nocall", Arrays.asList(Allele.NO_CALL)); + Genotype mixed = new Genotype("mixed", Arrays.asList(Aref, Allele.NO_CALL)); + Genotype homRef = new Genotype("homRef", Arrays.asList(Aref, Aref)); + Genotype het = new Genotype("het", Arrays.asList(Aref, T)); + Genotype homVar = new Genotype("homVar", Arrays.asList(T, T)); + + List allGenotypes = Arrays.asList(noCall, mixed, homRef, het, homVar); + final int nCycles = allGenotypes.size() * 10; + + for ( int i = 0; i < nCycles; i++ ) { + int nNoCall = 0, nNoCallAlleles = 0, nA = 0, nT = 0, nMixed = 0, nHomRef = 0, nHet = 0, nHomVar = 0; + int nSamples = 0; + GenotypesContext gc = GenotypesContext.create(); + for ( int j = 0; j < i; j++ ) { + nSamples++; + Genotype g = allGenotypes.get(j % allGenotypes.size()); + gc.add(g); + switch ( g.getType() ) { + case NO_CALL: nNoCall++; nNoCallAlleles++; break; + case HOM_REF: nA += 2; nHomRef++; break; + case HET: nA++; nT++; nHet++; break; + case HOM_VAR: nT += 2; nHomVar++; break; + case MIXED: nA++; nNoCallAlleles++; nMixed++; break; + default: throw new RuntimeException("Unexpected genotype type " + g.getType()); + } + + } + + VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, + snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + + Assert.assertEquals(vc.getNSamples(), nSamples); + if ( nSamples > 0 ) { + Assert.assertEquals(vc.isPolymorphic(), nT > 0); + Assert.assertEquals(vc.isMonomorphic(), nT == 0); + } + Assert.assertEquals(vc.getChromosomeCount(), nA + nT); + + Assert.assertEquals(vc.getChromosomeCount(Allele.NO_CALL), nNoCallAlleles); + Assert.assertEquals(vc.getChromosomeCount(Aref), nA); + Assert.assertEquals(vc.getChromosomeCount(T), nT); + + Assert.assertEquals(vc.getNoCallCount(), nNoCall); + Assert.assertEquals(vc.getHomRefCount(), nHomRef); + Assert.assertEquals(vc.getHetCount(), nHet); + Assert.assertEquals(vc.getHomVarCount(), nHomVar); + Assert.assertEquals(vc.getMixedCount(), nMixed); + } + } } \ No newline at end of file From f48d4cfa79cdae7bc02abf913cbd1ff3b8716786 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Fri, 18 Nov 2011 00:19:59 -0500 Subject: [PATCH 052/113] Bug fix: fully clipping GATKSAMRecords and flushing ops Reads that are emptied after clipping become new GATKSAMRecords. When applying ClippingOps, the ops are cleared after the clipping --- .../broadinstitute/sting/utils/clipreads/ReadClipper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java index 6e4ddddc4..e4806838d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -121,7 +121,7 @@ public class ReadClipper { public GATKSAMRecord hardClipSoftClippedBases () { if (read.isEmpty()) - return read; + return new GATKSAMRecord(read.getHeader()); int readIndex = 0; int cutLeft = -1; // first position to hard clip (inclusive) @@ -171,6 +171,9 @@ public class ReadClipper { clippedRead = op.apply(algorithm, clippedRead); } wasClipped = true; + ops.clear(); + if ( clippedRead.isEmpty() ) + return new GATKSAMRecord( clippedRead.getHeader() ); return clippedRead; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); // this should never happen From 7490dbb6eb5b47efb63f30f8b37c53904f63cfe9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 11:06:15 -0500 Subject: [PATCH 055/113] First version of VariantContextBuilder --- .../gatk/refdata/VariantContextAdaptors.java | 2 +- .../annotator/VariantAnnotatorEngine.java | 7 +- .../beagle/BeagleOutputToVCFWalker.java | 2 +- .../beagle/ProduceBeagleInputWalker.java | 2 +- .../filters/VariantFiltrationWalker.java | 11 +- .../walkers/genotyper/UGCallVariants.java | 7 +- .../genotyper/UnifiedGenotyperEngine.java | 5 +- ...eSegregatingAlternateAllelesVCFWriter.java | 7 +- .../walkers/phasing/PhaseByTransmission.java | 2 +- .../gatk/walkers/phasing/PhasingUtils.java | 2 +- .../validation/GenotypeAndValidateWalker.java | 3 +- .../varianteval/VariantEvalWalker.java | 5 +- .../varianteval/util/VariantEvalUtils.java | 3 +- .../ApplyRecalibration.java | 15 +- .../walkers/variantutils/CombineVariants.java | 3 +- .../variantutils/LeftAlignVariants.java | 7 +- .../variantutils/LiftoverVariants.java | 12 +- .../walkers/variantutils/SelectVariants.java | 31 +- .../VariantValidationAssessor.java | 21 +- .../walkers/variantutils/VariantsToVCF.java | 10 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 29 +- .../utils/codecs/vcf/StandardVCFWriter.java | 14 +- .../utils/variantcontext/CommonInfo.java | 29 +- .../sting/utils/variantcontext/Genotype.java | 5 +- .../utils/variantcontext/VariantContext.java | 307 +++--------------- .../variantcontext/VariantContextBuilder.java | 245 ++++++++++++++ .../variantcontext/VariantContextUtils.java | 82 ++++- .../walkers/qc/TestVariantContextWalker.java | 137 -------- .../VariantContextBenchmark.java | 2 +- .../VariantContextUnitTest.java | 55 ++-- .../VariantContextUtilsUnitTest.java | 2 +- 31 files changed, 511 insertions(+), 553 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java delete mode 100755 public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index 081f86ab9..7953edd7f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -255,7 +255,7 @@ public class VariantContextAdaptors { genotypes.add(call); alleles.add(refAllele); GenomeLoc loc = ref.getGenomeLocParser().createGenomeLoc(geli.getChr(),geli.getStart()); - return new VariantContext(name, VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, geli.getLODBestToReference(), null, attributes); + return new VariantContextBuilder(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles).genotypes(genotypes).negLog10PError(geli.getLODBestToReference()).attributes(attributes).make(); } else return null; // can't handle anything else } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index 9f0353eb9..b782de15f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -36,6 +36,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import java.util.*; @@ -179,10 +180,10 @@ public class VariantAnnotatorEngine { } // generate a new annotated VC - final VariantContext annotatedVC = VariantContext.modifyAttributes(vc, infoAnnotations); + VariantContextBuilder builder = new VariantContextBuilder(vc).attributes(infoAnnotations); // annotate genotypes, creating another new VC in the process - return VariantContext.modifyGenotypes(annotatedVC, annotateGenotypes(tracker, ref, stratifiedContexts, vc)); + return builder.genotypes(annotateGenotypes(tracker, ref, stratifiedContexts, vc)).make(); } private VariantContext annotateDBs(RefMetaDataTracker tracker, ReferenceContext ref, VariantContext vc, Map infoAnnotations) { @@ -192,7 +193,7 @@ public class VariantAnnotatorEngine { infoAnnotations.put(VCFConstants.DBSNP_KEY, rsID != null); // annotate dbsnp id if available and not already there if ( rsID != null && vc.emptyID() ) - vc = VariantContext.modifyID(vc, rsID); + vc = new VariantContextBuilder(vc).id(rsID).make(); } else { boolean overlapsComp = false; for ( VariantContext comp : tracker.getValues(dbSet.getKey(), ref.getLocus()) ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 649b7621b..d4aa21097 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -358,7 +358,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { } - vcfWriter.add(VariantContext.modifyAttributes(filteredVC,attributes)); + vcfWriter.add(new VariantContextBuilder(filteredVC).attributes(attributes).make()); return 1; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java index 1d6eb4b64..aa71f4399 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/ProduceBeagleInputWalker.java @@ -201,7 +201,7 @@ public class ProduceBeagleInputWalker extends RodWalker { logger.debug(String.format("boot: %d, test: %d, total: %d", bootstrapSetSize, testSetSize, bootstrapSetSize+testSetSize+1)); if ( (bootstrapSetSize+1.0)/(1.0+bootstrapSetSize+testSetSize) <= bootstrap ) { if ( bootstrapVCFOutput != null ) { - bootstrapVCFOutput.add(VariantContext.modifyFilters(validation, BOOTSTRAP_FILTER)); + bootstrapVCFOutput.add(new VariantContextBuilder(validation).filters(BOOTSTRAP_FILTER).make()); } bootstrapSetSize++; return true; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index 409e180ae..049b92084 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -36,10 +36,7 @@ import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -225,7 +222,7 @@ public class VariantFiltrationWalker extends RodWalker { (vc.getFilters() == null || !vc.getFilters().contains(MASK_NAME)) ) { // the filter hasn't already been applied Set filters = new LinkedHashSet(vc.getFilters()); filters.add(MASK_NAME); - vc = VariantContext.modifyFilters(vc, filters); + vc = new VariantContextBuilder(vc).filters(filters).make(); } FiltrationContext varContext = new FiltrationContext(ref, vc); @@ -268,7 +265,7 @@ public class VariantFiltrationWalker extends RodWalker { (vc.getFilters() == null || !vc.getFilters().contains(MASK_NAME)) ) { // the filter hasn't already been applied Set filters = new LinkedHashSet(vc.getFilters()); filters.add(MASK_NAME); - vc = VariantContext.modifyFilters(vc, filters); + vc = new VariantContextBuilder(vc).filters(filters).make(); } return vc; @@ -325,7 +322,7 @@ public class VariantFiltrationWalker extends RodWalker { VariantContext filteredVC; if ( genotypes == null ) - filteredVC = VariantContext.modifyFilters(vc, filters); + filteredVC = new VariantContextBuilder(vc).filters(filters).make(); else filteredVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), filters, vc.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index 6dc31edb8..60513ca5f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -35,10 +35,7 @@ import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -111,7 +108,7 @@ public class UGCallVariants extends RodWalker { try { Map attrs = new HashMap(value.getAttributes()); VariantContextUtils.calculateChromosomeCounts(value, attrs, true); - writer.add(VariantContext.modifyAttributes(value, attrs)); + writer.add(new VariantContextBuilder(value).attributes(attrs).make()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(e.getMessage() + "; this is often caused by using the --assume_single_sample_reads argument with the wrong sample name"); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index 5692c2525..0fe7e1fc2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -229,8 +229,7 @@ public class UnifiedGenotyperEngine { VariantContext vcInput = UnifiedGenotyperEngine.getVCFromAllelesRod(tracker, ref, rawContext.getLocation(), false, logger, UAC.alleles); if ( vcInput == null ) return null; - vc = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, vcInput.getChr(), vcInput.getStart(), vcInput.getEnd(), vcInput.getAlleles(), VariantContext.NO_NEG_LOG_10PERROR, null, null, ref.getBase()); - + vc = new VariantContextBuilder(vcInput).source("UG_call").noID().referenceBaseForIndel(ref.getBase()).make(); } else { // deal with bad/non-standard reference bases if ( !Allele.acceptableAlleleBases(new byte[]{ref.getBase()}) ) @@ -238,7 +237,7 @@ public class UnifiedGenotyperEngine { Set alleles = new HashSet(); alleles.add(Allele.create(ref.getBase(), true)); - vc = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, ref.getLocus().getContig(), ref.getLocus().getStart(), ref.getLocus().getStart(), alleles); + vc = new VariantContextBuilder("UG_call", ref.getLocus().getContig(), ref.getLocus().getStart(), ref.getLocus().getStart(), alleles).make(); } if ( annotationEngine != null ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java index b935600b2..2f15c165f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeSegregatingAlternateAllelesVCFWriter.java @@ -33,10 +33,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.File; import java.io.FileNotFoundException; @@ -186,7 +183,7 @@ class MergeSegregatingAlternateAllelesVCFWriter implements VCFWriter { Map addedAttribs = vcMergeRule.addToMergedAttributes(vcfrWaitingToMerge.vc, vc); addedAttribs.putAll(mergedVc.getAttributes()); - mergedVc = VariantContext.modifyAttributes(mergedVc, addedAttribs); + mergedVc = new VariantContextBuilder(mergedVc).attributes(addedAttribs).make(); vcfrWaitingToMerge = new VCFRecord(mergedVc, true); numMergedRecords++; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 0b28459d4..088ff4c71 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -309,7 +309,7 @@ public class PhaseByTransmission extends RodWalker { genotypesContext.add(phasedMother, phasedFather, phasedChild); } - VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypesContext); + VariantContext newvc = new VariantContextBuilder(vc).genotypes(genotypesContext).make(); vcfWriter.add(newvc); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java index fddef5129..cac171948 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java @@ -135,7 +135,7 @@ class PhasingUtils { mergedAttribs = new HashMap(mergedVc.getAttributes()); VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); - mergedVc = VariantContext.modifyAttributes(mergedVc, mergedAttribs); + mergedVc = new VariantContextBuilder(mergedVc).attributes(mergedAttribs).make(); return mergedVc; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java index 8f9f3f1af..f370e2818 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/GenotypeAndValidateWalker.java @@ -40,6 +40,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; import org.broadinstitute.sting.utils.codecs.vcf.VCFUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; import java.util.Map; @@ -465,7 +466,7 @@ public class GenotypeAndValidateWalker extends RodWalker implements Tr for ( VariantContext eval : evalSetBySample ) { // deal with ancestral alleles if requested if ( eval != null && aastr != null ) { - HashMap newAts = new HashMap(eval.getAttributes()); - newAts.put("ANCESTRALALLELE", aastr); - eval = VariantContext.modifyAttributes(eval, newAts); + eval = new VariantContextBuilder(eval).attribute("ANCESTRALALLELE", aastr).make(); } // for each comp track diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index 24caed549..6da693c7a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -16,6 +16,7 @@ import org.broadinstitute.sting.utils.classloader.PluginManager; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; import java.lang.reflect.Field; @@ -289,7 +290,7 @@ public class VariantEvalUtils { } VariantContextUtils.calculateChromosomeCounts(vcsub, newAts, true); - vcsub = VariantContext.modifyAttributes(vcsub, newAts); + vcsub = new VariantContextBuilder(vcsub).attributes(newAts).make(); //VariantEvalWalker.logger.debug(String.format("VC %s subset to %s AC%n", vc.getSource(), vc.getAttributeAsString(VCFConstants.ALLELE_COUNT_KEY))); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/ApplyRecalibration.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/ApplyRecalibration.java index 1d5493daf..b1b8fa46d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/ApplyRecalibration.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/ApplyRecalibration.java @@ -41,6 +41,7 @@ import org.broadinstitute.sting.utils.collections.NestedHashMap; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.text.XReadLines; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import java.io.File; import java.io.FileNotFoundException; @@ -203,8 +204,9 @@ public class ApplyRecalibration extends RodWalker { for( VariantContext vc : tracker.getValues(input, context.getLocation()) ) { if( vc != null ) { if( VariantRecalibrator.checkRecalibrationMode( vc, MODE ) && (vc.isNotFiltered() || ignoreInputFilterSet.containsAll(vc.getFilters())) ) { + VariantContextBuilder builder = new VariantContextBuilder(vc); String filterString = null; - final Map attrs = new HashMap(vc.getAttributes()); + final Double lod = (Double) lodMap.get( vc.getChr(), vc.getStart(), vc.getEnd() ); final String worstAnnotation = (String) annotationMap.get( vc.getChr(), vc.getStart(), vc.getEnd() ); if( lod == null ) { @@ -212,8 +214,8 @@ public class ApplyRecalibration extends RodWalker { } // Annotate the new record with its VQSLOD and the worst performing annotation - attrs.put(VariantRecalibrator.VQS_LOD_KEY, String.format("%.4f", lod)); - attrs.put(VariantRecalibrator.CULPRIT_KEY, worstAnnotation); + builder.attribute(VariantRecalibrator.VQS_LOD_KEY, String.format("%.4f", lod)); + builder.attribute(VariantRecalibrator.CULPRIT_KEY, worstAnnotation); for( int i = tranches.size() - 1; i >= 0; i-- ) { final Tranche tranche = tranches.get(i); @@ -232,11 +234,10 @@ public class ApplyRecalibration extends RodWalker { } if( !filterString.equals(VCFConstants.PASSES_FILTERS_v4) ) { - final Set filters = new HashSet(); - filters.add(filterString); - vc = VariantContext.modifyFilters(vc, filters); + builder.filters(filterString); } - vcfWriter.add( VariantContext.modifyPErrorFiltersAndAttributes(vc, vc.getNegLog10PError(), vc.getFilters(), attrs) ); + + vcfWriter.add( builder.make() ); } else { // valid VC but not compatible with this mode, so just emit the variant untouched vcfWriter.add( vc ); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java index 573e15971..d74f5a269 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java @@ -38,6 +38,7 @@ import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; import java.util.*; @@ -252,7 +253,7 @@ public class CombineVariants extends RodWalker { HashMap attributes = new HashMap(mergedVC.getAttributes()); // re-compute chromosome counts VariantContextUtils.calculateChromosomeCounts(mergedVC, attributes, false); - VariantContext annotatedMergedVC = VariantContext.modifyAttributes(mergedVC, attributes); + VariantContext annotatedMergedVC = new VariantContextBuilder(mergedVC).attributes(attributes).make(); if ( minimalVCF ) annotatedMergedVC = VariantContextUtils.pruneVariantContext(annotatedMergedVC, Arrays.asList(SET_KEY)); vcfWriter.add(annotatedMergedVC); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java index 4b3271ba6..f357f8a40 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java @@ -38,10 +38,7 @@ import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.sam.AlignmentUtils; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -161,7 +158,7 @@ public class LeftAlignVariants extends RodWalker { // update if necessary and write if ( !newCigar.equals(originalCigar) && newCigar.numCigarElements() > 1 ) { int difference = originalIndex - newCigar.getCigarElement(0).getLength(); - VariantContext newVC = VariantContext.modifyLocation(vc, vc.getChr(), vc.getStart()-difference, vc.getEnd()-difference); + VariantContext newVC = new VariantContextBuilder(vc).start(vc.getStart()-difference).stop(vc.getEnd()-difference).make(); //System.out.println("Moving record from " + vc.getChr()+":"+vc.getStart() + " to " + vc.getChr()+":"+(vc.getStart()-difference)); int indelIndex = originalIndex-difference; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LiftoverVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LiftoverVariants.java index a932d44ed..50fafa202 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LiftoverVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LiftoverVariants.java @@ -39,6 +39,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; import java.io.File; @@ -117,16 +118,15 @@ public class LiftoverVariants extends RodWalker { vc = VariantContextUtils.reverseComplement(vc); } - vc = VariantContext.modifyLocation(vc, toInterval.getSequence(), toInterval.getStart(), toInterval.getStart() + length); + vc = new VariantContextBuilder(vc).loc(toInterval.getSequence(), toInterval.getStart(), toInterval.getStart() + length).make(); if ( RECORD_ORIGINAL_LOCATION ) { - HashMap attrs = new HashMap(vc.getAttributes()); - attrs.put("OriginalChr", fromInterval.getSequence()); - attrs.put("OriginalStart", fromInterval.getStart()); - vc = VariantContext.modifyAttributes(vc, attrs); + vc = new VariantContextBuilder(vc) + .attribute("OriginalChr", fromInterval.getSequence()) + .attribute("OriginalStart", fromInterval.getStart()).make(); } - VariantContext newVC = VariantContext.createVariantContextWithPaddedAlleles(vc, false); + VariantContext newVC = VariantContextUtils.createVariantContextWithPaddedAlleles(vc, false); if ( originalVC.isSNP() && originalVC.isBiallelic() && VariantContextUtils.getSNPSubstitutionType(originalVC) != VariantContextUtils.getSNPSubstitutionType(newVC) ) { logger.warn(String.format("VCF at %s / %d => %s / %d is switching substitution type %s/%s to %s/%s", originalVC.getChr(), originalVC.getStart(), newVC.getChr(), newVC.getStart(), diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 7f6e605e7..be9a193d3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -654,16 +654,12 @@ public class SelectVariants extends RodWalker { if ( samples == null || samples.isEmpty() ) return vc; -// logger.info("Genotypes in full vc: " + vc.getGenotypes()); -// logger.info("My own sub : " + vc.getGenotypes().subsetToSamples(samples)); - VariantContext sub = vc.subContextFromSamples(samples, vc.getAlleles()); -// logger.info("Genotypes in sub vc: " + sub.getGenotypes()); + final VariantContext sub = vc.subContextFromSamples(samples, vc.getAlleles()); + VariantContextBuilder builder = new VariantContextBuilder(sub); // if we have fewer alternate alleles in the selected VC than in the original VC, we need to strip out the GL/PLs (because they are no longer accurate) if ( vc.getAlleles().size() != sub.getAlleles().size() ) - sub = VariantContext.modifyGenotypes(sub, VariantContextUtils.stripPLs(vc.getGenotypes())); - - HashMap attributes = new HashMap(sub.getAttributes()); + builder.genotypes(VariantContextUtils.stripPLs(vc.getGenotypes())); int depth = 0; for (String sample : sub.getSampleNames()) { @@ -680,22 +676,19 @@ public class SelectVariants extends RodWalker { if (KEEP_ORIGINAL_CHR_COUNTS) { - if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) - attributes.put("AC_Orig",attributes.get(VCFConstants.ALLELE_COUNT_KEY)); - if ( attributes.containsKey(VCFConstants.ALLELE_FREQUENCY_KEY) ) - attributes.put("AF_Orig",attributes.get(VCFConstants.ALLELE_FREQUENCY_KEY)); - if ( attributes.containsKey(VCFConstants.ALLELE_NUMBER_KEY) ) - attributes.put("AN_Orig",attributes.get(VCFConstants.ALLELE_NUMBER_KEY)); - + if ( sub.hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) + builder.attribute("AC_Orig",sub.getAttribute(VCFConstants.ALLELE_COUNT_KEY)); + if ( sub.hasAttribute(VCFConstants.ALLELE_FREQUENCY_KEY) ) + builder.attribute("AF_Orig",sub.getAttribute(VCFConstants.ALLELE_FREQUENCY_KEY)); + if ( sub.hasAttribute(VCFConstants.ALLELE_NUMBER_KEY) ) + builder.attribute("AN_Orig",sub.getAttribute(VCFConstants.ALLELE_NUMBER_KEY)); } - VariantContextUtils.calculateChromosomeCounts(sub,attributes,false); + Map attributes = new HashMap(builder.make().getAttributes()); + VariantContextUtils.calculateChromosomeCounts(sub, attributes, false); attributes.put("DP", depth); - sub = VariantContext.modifyAttributes(sub, attributes); - -// logger.info("Genotypes in final vc: " + sub.getGenotypes()); - return sub; + return new VariantContextBuilder(builder.make()).attributes(attributes).make(); } private void randomlyAddVariant(int rank, VariantContext vc, byte refBase) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java index 4e6cc722d..79bbea29d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java @@ -36,6 +36,7 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; import java.util.*; @@ -227,24 +228,24 @@ public class VariantValidationAssessor extends RodWalker numHomVarViolations++; isViolation = true; } - vContext = VariantContext.modifyFilters(vContext, filters); + + VariantContextBuilder builder = new VariantContextBuilder(vContext).filters(filters); numRecords++; // add the info fields - HashMap infoMap = new HashMap(); - infoMap.put("NoCallPct", String.format("%.1f", 100.0*noCallProp)); - infoMap.put("HomRefPct", String.format("%.1f", 100.0*homRefProp)); - infoMap.put("HomVarPct", String.format("%.1f", 100.0*homVarProp)); - infoMap.put("HetPct", String.format("%.1f", 100.0*hetProp)); - infoMap.put("HW", String.format("%.2f", hwScore)); + builder.attribute("NoCallPct", String.format("%.1f", 100.0*noCallProp)); + builder.attribute("HomRefPct", String.format("%.1f", 100.0*homRefProp)); + builder.attribute("HomVarPct", String.format("%.1f", 100.0*homVarProp)); + builder.attribute("HetPct", String.format("%.1f", 100.0*hetProp)); + builder.attribute("HW", String.format("%.2f", hwScore)); Collection altAlleles = vContext.getAlternateAlleles(); int altAlleleCount = altAlleles.size() == 0 ? 0 : vContext.getChromosomeCount(altAlleles.iterator().next()); if ( !isViolation && altAlleleCount > 0 ) numTrueVariants++; - infoMap.put(VCFConstants.ALLELE_COUNT_KEY, String.format("%d", altAlleleCount)); - infoMap.put(VCFConstants.ALLELE_NUMBER_KEY, String.format("%d", vContext.getChromosomeCount())); + builder.attribute(VCFConstants.ALLELE_COUNT_KEY, String.format("%d", altAlleleCount)); + builder.attribute(VCFConstants.ALLELE_NUMBER_KEY, String.format("%d", vContext.getChromosomeCount())); - return VariantContext.modifyAttributes(vContext, infoMap); + return builder.make(); } private double hardyWeinbergCalculation(VariantContext vc) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java index 78cfde1bd..f5928b723 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToVCF.java @@ -121,22 +121,22 @@ public class VariantsToVCF extends RodWalker { Collection contexts = getVariantContexts(tracker, ref); for ( VariantContext vc : contexts ) { + VariantContextBuilder builder = new VariantContextBuilder(vc); if ( rsID != null && vc.emptyID() ) { - vc = VariantContext.modifyID(vc, rsID); + builder.id(rsID).make(); } // set the appropriate sample name if necessary if ( sampleName != null && vc.hasGenotypes() && vc.hasGenotype(variants.getName()) ) { Genotype g = Genotype.modifyName(vc.getGenotype(variants.getName()), sampleName); - GenotypesContext genotypes = GenotypesContext.create(g); - vc = VariantContext.modifyGenotypes(vc, genotypes); + builder.genotypes(g); } if ( fixReferenceBase ) { - vc = VariantContext.modifyReferencePadding(vc, ref.getBase()); + builder.referenceBaseForIndel(ref.getBase()); } - writeRecord(vc, tracker, ref.getLocus()); + writeRecord(builder.make(), tracker, ref.getLocus()); } return 1; diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index 725cc8109..ba138a9da 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -13,6 +13,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; import java.io.*; import java.util.*; @@ -252,29 +253,30 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, * @return a variant context object */ private VariantContext parseVCFLine(String[] parts) { + VariantContextBuilder builder = new VariantContextBuilder(); // increment the line count lineNo++; // parse out the required fields - String contig = getCachedString(parts[0]); + builder.chr(getCachedString(parts[0])); int pos = Integer.valueOf(parts[1]); - String id = null; + builder.start(pos); + if ( parts[2].length() == 0 ) generateException("The VCF specification requires a valid ID field"); else if ( parts[2].equals(VCFConstants.EMPTY_ID_FIELD) ) - id = VCFConstants.EMPTY_ID_FIELD; + builder.noID(); else - id = parts[2]; + builder.id(parts[2]); + String ref = getCachedString(parts[3].toUpperCase()); String alts = getCachedString(parts[4].toUpperCase()); - Double qual = parseQual(parts[5]); - String filter = getCachedString(parts[6]); - String info = new String(parts[7]); + builder.negLog10PError(parseQual(parts[5])); + builder.filters(parseFilters(getCachedString(parts[6]))); + builder.attributes(parseInfo(parts[7])); // get our alleles, filters, and setup an attribute map List alleles = parseAlleles(ref, alts, lineNo); - Set filters = parseFilters(filter); - Map attributes = parseInfo(info); // find out our current location, and clip the alleles down to their minimum length int loc = pos; @@ -286,16 +288,19 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, loc = clipAlleles(pos, ref, alleles, newAlleles, lineNo); alleles = newAlleles; } + builder.stop(loc); + builder.alleles(alleles); // do we have genotyping data if (parts.length > NUM_STANDARD_FIELDS) { - attributes.put(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, new String(parts[8])); - attributes.put(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY, this); + builder.attribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, new String(parts[8])); + builder.attribute(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY, this); } VariantContext vc = null; try { - vc = new VariantContext(name, id, contig, pos, loc, alleles, qual, filters, attributes, ref.getBytes()[0]); + builder.referenceBaseForIndel(ref.getBytes()[0]); + vc = builder.make(); } catch (Exception e) { generateException(e.getMessage()); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java index 63c61cfaa..37137f716 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java @@ -25,18 +25,10 @@ package org.broadinstitute.sting.utils.codecs.vcf; import net.sf.samtools.SAMSequenceDictionary; -import org.broad.tribble.Tribble; import org.broad.tribble.TribbleException; -import org.broad.tribble.index.DynamicIndexCreator; -import org.broad.tribble.index.Index; -import org.broad.tribble.index.IndexFactory; -import org.broad.tribble.util.LittleEndianOutputStream; import org.broad.tribble.util.ParsingUtils; -import org.broad.tribble.util.PositionalStream; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.*; import java.lang.reflect.Array; @@ -164,10 +156,10 @@ public class StandardVCFWriter extends IndexingVCFWriter { throw new IllegalStateException("The VCF Header must be written before records can be added: " + getStreamName()); if ( doNotWriteGenotypes ) - vc = VariantContext.modifyGenotypes(vc, null); + vc = new VariantContextBuilder(vc).noGenotypes().make(); try { - vc = VariantContext.createVariantContextWithPaddedAlleles(vc, false); + vc = VariantContextUtils.createVariantContextWithPaddedAlleles(vc, false); super.add(vc); Map alleleMap = new HashMap(vc.getAlleles().size()); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java index 57edbbfcc..4ffc8e966 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java @@ -14,12 +14,12 @@ import java.util.*; final class CommonInfo { public static final double NO_NEG_LOG_10PERROR = -1.0; - private static Set NO_FILTERS = Collections.unmodifiableSet(new HashSet()); + private static Set NO_FILTERS = Collections.emptySet(); private static Map NO_ATTRIBUTES = Collections.unmodifiableMap(new HashMap()); private double negLog10PError = NO_NEG_LOG_10PERROR; private String name = null; - private Set filters = NO_FILTERS; + private Set filters = null; private Map attributes = NO_ATTRIBUTES; public CommonInfo(String name, double negLog10PError, Set filters, Map attributes) { @@ -56,12 +56,20 @@ final class CommonInfo { // // --------------------------------------------------------------------------------------------------------- + public Set getFiltersMaybeNull() { + return filters; + } + public Set getFilters() { - return Collections.unmodifiableSet(filters); + return filters == null ? NO_FILTERS : Collections.unmodifiableSet(filters); + } + + public boolean filtersWereApplied() { + return filters != null; } public boolean isFiltered() { - return filters.size() > 0; + return filters == null ? false : filters.size() > 0; } public boolean isNotFiltered() { @@ -69,8 +77,8 @@ final class CommonInfo { } public void addFilter(String filter) { - if ( filters == NO_FILTERS ) // immutable -> mutable - filters = new HashSet(filters); + if ( filters == null ) // immutable -> mutable + filters = new HashSet(); if ( filter == null ) throw new IllegalArgumentException("BUG: Attempting to add null filter " + this); if ( getFilters().contains(filter) ) throw new IllegalArgumentException("BUG: Attempting to add duplicate filter " + filter + " at " + this); @@ -83,15 +91,6 @@ final class CommonInfo { addFilter(f); } - public void clearFilters() { - filters = new HashSet(); - } - - public void setFilters(Collection filters) { - clearFilters(); - addFilters(filters); - } - // --------------------------------------------------------------------------------------------------------- // // Working with log error rates diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java index 28f2d85fc..f1574cec2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -23,7 +23,6 @@ public class Genotype { protected Type type = null; protected boolean isPhased = false; - protected boolean filtersWereAppliedToContext; public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased) { this(sampleName, alleles, negLog10PError, filters, attributes, isPhased, null); @@ -35,7 +34,6 @@ public class Genotype { commonInfo = new CommonInfo(sampleName, negLog10PError, filters, attributes); if ( log10Likelihoods != null ) commonInfo.putAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); - filtersWereAppliedToContext = filters != null; this.isPhased = isPhased; validate(); } @@ -333,9 +331,10 @@ public class Genotype { // --------------------------------------------------------------------------------------------------------- public String getSampleName() { return commonInfo.getName(); } public Set getFilters() { return commonInfo.getFilters(); } + public Set getFiltersMaybeNull() { return commonInfo.getFiltersMaybeNull(); } public boolean isFiltered() { return commonInfo.isFiltered(); } public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } - public boolean filtersWereApplied() { return filtersWereAppliedToContext; } + public boolean filtersWereApplied() { return commonInfo.filtersWereApplied(); } public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 7798e259c..455a9b997 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -1,5 +1,6 @@ package org.broadinstitute.sting.utils.variantcontext; +import org.apache.commons.lang.Validate; import org.broad.tribble.Feature; import org.broad.tribble.TribbleException; import org.broad.tribble.util.ParsingUtils; @@ -200,15 +201,29 @@ public class VariantContext implements Feature { // to enable tribble intergrati // set to the alt allele when biallelic, otherwise == null private Allele ALT = null; - // were filters applied? - final private boolean filtersWereAppliedToContext; + /* cached monomorphic value: null -> not yet computed, False, True */ + private Boolean monomorphic = null; // --------------------------------------------------------------------------------------------------------- // - // constructors + // validation mode // // --------------------------------------------------------------------------------------------------------- + public enum Validation { + REF_PADDING, + ALLELES, + GENOTYPES + } + + private final static EnumSet ALL_VALIDATION = EnumSet.allOf(Validation.class); + private final static EnumSet NO_VALIDATION = EnumSet.noneOf(Validation.class); + + // --------------------------------------------------------------------------------------------------------- + // + // constructors: see VariantContextBuilder + // + // --------------------------------------------------------------------------------------------------------- /** * the complete constructor. Makes a complete VariantContext from its arguments @@ -224,14 +239,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes * @param referenceBaseForIndel padded reference base + * + * @deprecated replaced by {@link VariantContextBuilder} */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, true); - } - @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel); + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, ALL_VALIDATION); } @@ -247,66 +260,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param negLog10PError qual * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes + * + * @deprecated replaced by {@link VariantContextBuilder} */ + @Deprecated public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, true); - } - - @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); - } - - /** - * Makes a VariantContext from its arguments without parsing the genotypes. - * Note that this constructor assumes that if there is genotype data, then it's been put into - * the attributes with the UNPARSED_GENOTYPE_MAP_KEY and that the codec has been added with the - * UNPARSED_GENOTYPE_PARSER_KEY. It doesn't validate that this is the case because it's possible - * that there is no genotype data. - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @param referenceBaseForIndel padded reference base - */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, negLog10PError, filters, attributes, referenceBaseForIndel, true, true); - } - - @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, negLog10PError, filters, attributes, referenceBaseForIndel); - } - - /** - * Create a new VariantContext - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes set - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, ID, contig, start, stop, alleles, - GenotypesContext.copy(genotypes), - negLog10PError, filters, attributes, null, false, true); - } - - @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, - GenotypesContext.copy(genotypes), - negLog10PError, filters, attributes); + this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, ALL_VALIDATION); } /** @@ -317,33 +276,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param start the start base (one based) * @param stop the stop reference base (one based) * @param alleles alleles - */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles) { - this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null, null, false, true); - } - - @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles) { - this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles); - } - - /** - * Create a new variant context with genotypes but without Perror, filters, and attributes * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes + * @deprecated replaced by {@link VariantContextBuilder} */ - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, Collection genotypes) { - this(source, ID, contig, start, stop, alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); - } - @Deprecated - public VariantContext(String source, String contig, long start, long stop, Collection alleles, Collection genotypes) { - this(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleles, genotypes, CommonInfo.NO_NEG_LOG_10PERROR, null, null); + public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles) { + this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null, null, false, ALL_VALIDATION); } /** @@ -351,8 +289,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati * * @param other the VariantContext to copy */ - public VariantContext(VariantContext other) { - this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, true); + protected VariantContext(VariantContext other) { + this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, NO_VALIDATION); } /** @@ -369,14 +307,14 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param attributes attributes * @param referenceBaseForIndel padded reference base * @param genotypesAreUnparsed true if the genotypes have not yet been parsed - * @param performValidation if true, call validate() as the final step in construction + * @param validationToPerform set of validation steps to take */ - private VariantContext(String source, String ID, + protected VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed, - boolean performValidation ) { + EnumSet validationToPerform ) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } this.contig = contig; this.start = start; @@ -398,7 +336,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati } this.commonInfo = new CommonInfo(source, negLog10PError, filters, attributes); - filtersWereAppliedToContext = filters != null; REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; // todo -- remove me when this check is no longer necessary @@ -426,69 +363,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati } } - if ( performValidation ) { - validate(); + if ( ! validationToPerform.isEmpty() ) { + validate(validationToPerform); } } - // --------------------------------------------------------------------------------------------------------- - // - // Partial-cloning routines (because Variant Context is immutable). - // - // IMPORTANT: These routines assume that the VariantContext on which they're called is already valid. - // Due to this assumption, they explicitly tell the constructor NOT to perform validation by - // calling validate(), and instead perform validation only on the data that's changed. - // - // Note that we don't call vc.getGenotypes() because that triggers the lazy loading. - // Also note that we need to create a new attributes map because it's unmodifiable and the constructor may try to modify it. - // - // --------------------------------------------------------------------------------------------------------- - - public static VariantContext modifyGenotypes(VariantContext vc, GenotypesContext genotypes) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), false, false); - modifiedVC.validateGenotypes(); - return modifiedVC; - } - - public static VariantContext modifyLocation(VariantContext vc, String chr, int start, int end) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), chr, start, end, vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), vc.getReferenceBaseForIndel(), true, false); - - // Since start and end have changed, we need to call both validateAlleles() and validateReferencePadding(), - // since those validation routines rely on the values of start and end: - modifiedVC.validateAlleles(); - modifiedVC.validateReferencePadding(); - - return modifiedVC; - } - - public static VariantContext modifyFilters(VariantContext vc, Set filters) { - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), filters, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); - } - - public static VariantContext modifyAttributes(VariantContext vc, Map attributes) { - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); - } - - public static VariantContext modifyAttribute(VariantContext vc, final String key, final Object value) { - Map attributes = new HashMap(vc.getAttributes()); - attributes.put(key, value); - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, attributes, vc.getReferenceBaseForIndel(), true, false); - } - - public static VariantContext modifyReferencePadding(VariantContext vc, Byte b) { - VariantContext modifiedVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), b, true, false); - modifiedVC.validateReferencePadding(); - return modifiedVC; - } - - public static VariantContext modifyPErrorFiltersAndAttributes(VariantContext vc, double negLog10PError, Set filters, Map attributes) { - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), vc.genotypes, negLog10PError, filters, attributes, vc.getReferenceBaseForIndel(), true, false); - } - - public static VariantContext modifyID(final VariantContext vc, final String id) { - return new VariantContext(vc.getSource(), id, vc.getChr(), vc.getStart(), vc.getEnd() , vc.getAlleles(), vc.genotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, new HashMap(vc.getAttributes()), vc.getReferenceBaseForIndel(), true, false); - } - // --------------------------------------------------------------------------------------------------------- // // Selectors @@ -771,10 +650,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- public String getSource() { return commonInfo.getName(); } + public Set getFiltersMaybeNull() { return commonInfo.getFiltersMaybeNull(); } public Set getFilters() { return commonInfo.getFilters(); } public boolean isFiltered() { return commonInfo.isFiltered(); } public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } - public boolean filtersWereApplied() { return filtersWereAppliedToContext; } + public boolean filtersWereApplied() { return commonInfo.filtersWereApplied(); } public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } @@ -1092,7 +972,9 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return true if it's monomorphic */ public boolean isMonomorphic() { - return ! isVariant() || (hasGenotypes() && getChromosomeCount(getReference()) == getChromosomeCount()); + if ( monomorphic == null ) + monomorphic = ! isVariant() || (hasGenotypes() && getChromosomeCount(getReference()) == getChromosomeCount()); + return monomorphic; } /** @@ -1301,23 +1183,14 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - /** - * To be called by any modifying routines - */ - private boolean validate() { - return validate(true); - } - - private boolean validate(boolean throwException) { - try { - validateReferencePadding(); - validateAlleles(); - validateGenotypes(); - } catch ( IllegalArgumentException e ) { - if ( throwException ) - throw e; - else - return false; + private boolean validate(final EnumSet validationToPerform) { + for (final Validation val : validationToPerform ) { + switch (val) { + case ALLELES: validateAlleles(); break; + case REF_PADDING: validateReferencePadding(); break; + case GENOTYPES: validateGenotypes(); break; + default: throw new IllegalArgumentException("Unexpected validation mode " + val); + } } return true; @@ -1512,8 +1385,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati return (int)stop; } - private boolean hasSymbolicAlleles() { - for (Allele a: getAlleles()) { + public boolean hasSymbolicAlleles() { + for (final Allele a: getAlleles()) { if (a.isSymbolic()) { return true; } @@ -1521,84 +1394,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati return false; } - public static VariantContext createVariantContextWithPaddedAlleles(VariantContext inputVC, boolean refBaseShouldBeAppliedToEndOfAlleles) { - - // see if we need to pad common reference base from all alleles - boolean padVC; - - // We need to pad a VC with a common base if the length of the reference allele is less than the length of the VariantContext. - // This happens because the position of e.g. an indel is always one before the actual event (as per VCF convention). - long locLength = (inputVC.getEnd() - inputVC.getStart()) + 1; - if (inputVC.hasSymbolicAlleles()) - padVC = true; - else if (inputVC.getReference().length() == locLength) - padVC = false; - else if (inputVC.getReference().length() == locLength-1) - padVC = true; - else throw new IllegalArgumentException("Badly formed variant context at location " + String.valueOf(inputVC.getStart()) + - " in contig " + inputVC.getChr() + ". Reference length must be at most one base shorter than location size"); - - // nothing to do if we don't need to pad bases - if (padVC) { - - if ( !inputVC.hasReferenceBaseForIndel() ) - throw new ReviewedStingException("Badly formed variant context at location " + inputVC.getChr() + ":" + inputVC.getStart() + "; no padded reference base is available."); - - Byte refByte = inputVC.getReferenceBaseForIndel(); - - List alleles = new ArrayList(); - - for (Allele a : inputVC.getAlleles()) { - // get bases for current allele and create a new one with trimmed bases - if (a.isSymbolic()) { - alleles.add(a); - } else { - String newBases; - if ( refBaseShouldBeAppliedToEndOfAlleles ) - newBases = a.getBaseString() + new String(new byte[]{refByte}); - else - newBases = new String(new byte[]{refByte}) + a.getBaseString(); - alleles.add(Allele.create(newBases,a.isReference())); - } - } - - // now we can recreate new genotypes with trimmed alleles - GenotypesContext genotypes = GenotypesContext.create(inputVC.getNSamples()); - for (final Genotype g : inputVC.getGenotypes() ) { - List inAlleles = g.getAlleles(); - List newGenotypeAlleles = new ArrayList(g.getAlleles().size()); - for (Allele a : inAlleles) { - if (a.isCalled()) { - if (a.isSymbolic()) { - newGenotypeAlleles.add(a); - } else { - String newBases; - if ( refBaseShouldBeAppliedToEndOfAlleles ) - newBases = a.getBaseString() + new String(new byte[]{refByte}); - else - newBases = new String(new byte[]{refByte}) + a.getBaseString(); - newGenotypeAlleles.add(Allele.create(newBases,a.isReference())); - } - } - else { - // add no-call allele - newGenotypeAlleles.add(Allele.NO_CALL); - } - } - genotypes.add(new Genotype(g.getSampleName(), newGenotypeAlleles, g.getNegLog10PError(), - g.getFilters(), g.getAttributes(), g.isPhased())); - - } - - // Do not change the filter state if filters were not applied to this context - Set inputVCFilters = inputVC.filtersWereAppliedToContext ? inputVC.getFilters() : null; - return new VariantContext(inputVC.getSource(), inputVC.getID(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); - } - else - return inputVC; - - } - public Allele getAltAlleleWithHighestAlleleCount() { // first idea: get two alleles with highest AC Allele best = null; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java new file mode 100644 index 000000000..fb92f60a2 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext; + +import com.google.java.contract.Requires; +import org.broad.tribble.Feature; +import org.broad.tribble.TribbleException; +import org.broad.tribble.util.ParsingUtils; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.*; + +/** + * Builder class for VariantContext + * + * @author depristo + */ +public class VariantContextBuilder { + // required fields + private String source = null; + private String contig = null; + private long start = -1; + private long stop = -1; + private Collection alleles = null; + + // optional -> these are set to the appropriate default value + private String ID = VCFConstants.EMPTY_ID_FIELD; + private GenotypesContext genotypes = GenotypesContext.NO_GENOTYPES; + private double negLog10PError = VariantContext.NO_NEG_LOG_10PERROR; + private Set filters = null; + private Map attributes = null; + private boolean attributesCanBeModified = false; + private Byte referenceBaseForIndel = null; + private boolean genotypesAreUnparsed = false; + + /** enum of what must be validated */ + final private EnumSet toValidate = EnumSet.noneOf(VariantContext.Validation.class); + + public VariantContextBuilder() { + + } + + public VariantContextBuilder(String source, String contig, long start, long stop, Collection alleles) { + this.source = source; + this.contig = contig; + this.start = start; + this.stop = stop; + this.alleles = alleles; + toValidate.add(VariantContext.Validation.ALLELES); + } + + /** + * Returns a new builder based on parent -- the new VC will have all fields initialized + * to their corresponding values in parent. This is the best way to create a derived VariantContext + * + * @param parent + */ + public VariantContextBuilder(VariantContext parent) { + this.alleles = parent.alleles; + this.attributes = parent.getAttributes(); + this.attributesCanBeModified = false; + this.contig = parent.contig; + this.filters = parent.getFiltersMaybeNull(); + this.genotypes = parent.genotypes; + this.genotypesAreUnparsed = parent.hasAttribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY); + this.ID = parent.getID(); + this.negLog10PError = parent.getNegLog10PError(); + this.referenceBaseForIndel = parent.getReferenceBaseForIndel(); + this.source = parent.getSource(); + this.start = parent.getStart(); + this.stop = parent.getEnd(); + } + + @Requires({"alleles != null", "!alleles.isEmpty()"}) + public VariantContextBuilder alleles(final Collection alleles) { + this.alleles = alleles; + toValidate.add(VariantContext.Validation.ALLELES); + return this; + } + + /** + * Attributes can be null -> meaning there are no attributes. After + * calling this routine the builder assumes it can modify the attributes + * object here, if subsequent calls are made to set attribute values + * @param attributes + */ + public VariantContextBuilder attributes(final Map attributes) { + this.attributes = attributes; + this.attributesCanBeModified = true; + return this; + } + + public VariantContextBuilder attribute(final String key, final Object value) { + if ( ! attributesCanBeModified ) { + this.attributesCanBeModified = true; + this.attributes = new HashMap(); + } + attributes.put(key, value); + return this; + } + + /** + * filters can be null -> meaning there are no filters + * @param filters + */ + public VariantContextBuilder filters(final Set filters) { + this.filters = filters; + return this; + } + + public VariantContextBuilder filters(final String ... filters) { + filters(new HashSet(Arrays.asList(filters))); + return this; + } + + public VariantContextBuilder passFilters() { + return filters(VariantContext.PASSES_FILTERS); + } + + public VariantContextBuilder unfiltered() { + this.filters = null; + return this; + } + + /** + * genotypes can be null -> meaning there are no genotypes + * @param genotypes + */ + public VariantContextBuilder genotypes(final GenotypesContext genotypes) { + this.genotypes = genotypes; + if ( genotypes != null ) + toValidate.add(VariantContext.Validation.GENOTYPES); + return this; + } + + public VariantContextBuilder genotypes(final Collection genotypes) { + return genotypes(GenotypesContext.copy(genotypes)); + } + + public VariantContextBuilder genotypes(final Genotype ... genotypes) { + return genotypes(GenotypesContext.copy(Arrays.asList(genotypes))); + } + + public VariantContextBuilder noGenotypes() { + this.genotypes = null; + return this; + } + + public VariantContextBuilder genotypesAreUnparsed(final boolean genotypesAreUnparsed) { + this.genotypesAreUnparsed = genotypesAreUnparsed; + return this; + } + + @Requires("ID != null") + public VariantContextBuilder id(final String ID) { + this.ID = ID; + return this; + } + + public VariantContextBuilder noID() { + return id(VCFConstants.EMPTY_ID_FIELD); + } + + @Requires("negLog10PError <= 0") + public VariantContextBuilder negLog10PError(final double negLog10PError) { + this.negLog10PError = negLog10PError; + return this; + } + + /** + * Null means no refBase is available + * @param referenceBaseForIndel + */ + public VariantContextBuilder referenceBaseForIndel(final Byte referenceBaseForIndel) { + this.referenceBaseForIndel = referenceBaseForIndel; + toValidate.add(VariantContext.Validation.REF_PADDING); + return this; + } + + @Requires("source != null") + public VariantContextBuilder source(final String source) { + this.source = source; + return this; + } + + @Requires({"contig != null", "start >= 0", "stop >= 0"}) + public VariantContextBuilder loc(final String contig, final long start, final long stop) { + this.contig = contig; + this.start = start; + this.stop = stop; + toValidate.add(VariantContext.Validation.ALLELES); + toValidate.add(VariantContext.Validation.REF_PADDING); + return this; + } + + @Requires({"contig != null", "start >= 0", "stop >= 0"}) + public VariantContextBuilder chr(final String contig) { + this.contig = contig; + return this; + } + + @Requires({"start >= 0"}) + public VariantContextBuilder start(final long start) { + this.start = start; + toValidate.add(VariantContext.Validation.ALLELES); + toValidate.add(VariantContext.Validation.REF_PADDING); + return this; + } + + @Requires({"stop >= 0"}) + public VariantContextBuilder stop(final long stop) { + this.stop = stop; + return this; + } + + public VariantContext make() { + return new VariantContext(source, ID, contig, start, stop, alleles, + genotypes, negLog10PError, filters, attributes, + referenceBaseForIndel, genotypesAreUnparsed, toValidate); + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 5d2c86f84..d9057ea8f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -116,6 +116,82 @@ public class VariantContextUtils { return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attrs, g.isPhased()); } + public static VariantContext createVariantContextWithPaddedAlleles(VariantContext inputVC, boolean refBaseShouldBeAppliedToEndOfAlleles) { + // see if we need to pad common reference base from all alleles + boolean padVC; + + // We need to pad a VC with a common base if the length of the reference allele is less than the length of the VariantContext. + // This happens because the position of e.g. an indel is always one before the actual event (as per VCF convention). + long locLength = (inputVC.getEnd() - inputVC.getStart()) + 1; + if (inputVC.hasSymbolicAlleles()) + padVC = true; + else if (inputVC.getReference().length() == locLength) + padVC = false; + else if (inputVC.getReference().length() == locLength-1) + padVC = true; + else throw new IllegalArgumentException("Badly formed variant context at location " + String.valueOf(inputVC.getStart()) + + " in contig " + inputVC.getChr() + ". Reference length must be at most one base shorter than location size"); + + // nothing to do if we don't need to pad bases + if (padVC) { + if ( !inputVC.hasReferenceBaseForIndel() ) + throw new ReviewedStingException("Badly formed variant context at location " + inputVC.getChr() + ":" + inputVC.getStart() + "; no padded reference base is available."); + + Byte refByte = inputVC.getReferenceBaseForIndel(); + + List alleles = new ArrayList(); + + for (Allele a : inputVC.getAlleles()) { + // get bases for current allele and create a new one with trimmed bases + if (a.isSymbolic()) { + alleles.add(a); + } else { + String newBases; + if ( refBaseShouldBeAppliedToEndOfAlleles ) + newBases = a.getBaseString() + new String(new byte[]{refByte}); + else + newBases = new String(new byte[]{refByte}) + a.getBaseString(); + alleles.add(Allele.create(newBases,a.isReference())); + } + } + + // now we can recreate new genotypes with trimmed alleles + GenotypesContext genotypes = GenotypesContext.create(inputVC.getNSamples()); + for (final Genotype g : inputVC.getGenotypes() ) { + List inAlleles = g.getAlleles(); + List newGenotypeAlleles = new ArrayList(g.getAlleles().size()); + for (Allele a : inAlleles) { + if (a.isCalled()) { + if (a.isSymbolic()) { + newGenotypeAlleles.add(a); + } else { + String newBases; + if ( refBaseShouldBeAppliedToEndOfAlleles ) + newBases = a.getBaseString() + new String(new byte[]{refByte}); + else + newBases = new String(new byte[]{refByte}) + a.getBaseString(); + newGenotypeAlleles.add(Allele.create(newBases,a.isReference())); + } + } + else { + // add no-call allele + newGenotypeAlleles.add(Allele.NO_CALL); + } + } + genotypes.add(new Genotype(g.getSampleName(), newGenotypeAlleles, g.getNegLog10PError(), + g.getFilters(), g.getAttributes(), g.isPhased())); + + } + + // Do not change the filter state if filters were not applied to this context + Set inputVCFilters = inputVC.getFiltersMaybeNull(); + return new VariantContext(inputVC.getSource(), inputVC.getID(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); + } + else + return inputVC; + + } + /** * A simple but common wrapper for matching VariantContext objects using JEXL expressions */ @@ -257,7 +333,7 @@ public class VariantContextUtils { @Requires("vc != null") @Ensures("result != null") public static VariantContext sitesOnlyVariantContext(VariantContext vc) { - return VariantContext.modifyGenotypes(vc, null); + return new VariantContextBuilder(vc).noGenotypes().make(); } /** @@ -378,7 +454,7 @@ public class VariantContextUtils { for (VariantContext vc : prepaddedVCs) { // also a reasonable place to remove filtered calls, if needed if ( ! filteredAreUncalled || vc.isNotFiltered() ) - VCs.add(VariantContext.createVariantContextWithPaddedAlleles(vc, false)); + VCs.add(createVariantContextWithPaddedAlleles(vc, false)); } if ( VCs.size() == 0 ) // everything is filtered out and we're filteredAreUncalled return null; @@ -896,7 +972,7 @@ public class VariantContextUtils { newGenotypes.add(Genotype.modifyAttributes(genotype, attrs)); } - return VariantContext.modifyGenotypes(vc, newGenotypes); + return new VariantContextBuilder(vc).genotypes(newGenotypes).make(); } public static BaseUtils.BaseSubstitutionType getSNPSubstitutionType(VariantContext context) { diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java deleted file mode 100755 index 6bb764f44..000000000 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/qc/TestVariantContextWalker.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2010 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR - * THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.qc; - -import org.broadinstitute.sting.commandline.Argument; -import org.broadinstitute.sting.commandline.ArgumentCollection; -import org.broadinstitute.sting.commandline.Output; -import org.broadinstitute.sting.gatk.arguments.DbsnpArgumentCollection; -import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; -import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; -import org.broadinstitute.sting.gatk.refdata.VariantContextAdaptors; -import org.broadinstitute.sting.gatk.walkers.Reference; -import org.broadinstitute.sting.gatk.walkers.RodWalker; -import org.broadinstitute.sting.gatk.walkers.Window; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLine; -import org.broadinstitute.sting.utils.codecs.vcf.VCFWriter; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; - -import java.io.PrintStream; -import java.util.*; - -/** - * Test routine for new VariantContext object - */ -@Reference(window=@Window(start=-20,stop=1)) -public class TestVariantContextWalker extends RodWalker { - @Output - PrintStream out; - - @ArgumentCollection - protected StandardVariantContextInputArgumentCollection variantCollection = new StandardVariantContextInputArgumentCollection(); - - @Argument(fullName="takeFirstOnly", doc="Only take the first second at a locus, as opposed to all", required=false) - boolean takeFirstOnly = false; - - @Argument(fullName="onlyContextsOfType", doc="Only take variant contexts of this type", required=false) - VariantContext.Type onlyOfThisType = null; - - @Argument(fullName="onlyContextsStartinAtCurrentPosition", doc="Only take variant contexts at actually start at the current position, excluding those at span to the current location but start earlier", required=false) - boolean onlyContextsStartinAtCurrentPosition = false; - - @Argument(fullName="printPerLocus", doc="If true, we'll print the variant contexts, in addition to counts", required=false) - boolean printContexts = false; - - @Argument(fullName="outputVCF", doc="If provided, we'll convert the first input context into a VCF", required=false) - VCFWriter writer = null; - - private boolean wroteHeader = false; - - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if ( ref == null ) - return 0; - else { - EnumSet allowedTypes = onlyOfThisType == null ? null : EnumSet.of(onlyOfThisType); - - int n = 0; - List contexts; - if ( onlyContextsStartinAtCurrentPosition ) - contexts = tracker.getValues(variantCollection.variants, context.getLocation()); - else // ! onlyContextsStartinAtCurrentPosition - contexts = tracker.getValues(variantCollection.variants); - - for ( VariantContext vc : contexts ) { - if ( allowedTypes == null || allowedTypes.contains(vc.getType()) ) { - // we need to trigger decoding of the genotype string to pass integration tests - vc.getGenotypes(); - - if ( writer != null && n == 0 ) { - if ( ! wroteHeader ) { - writer.writeHeader(createVCFHeader(vc)); - wroteHeader = true; - } - - writer.add(vc); - } - - n++; - if ( printContexts ) out.printf(" %s%n", vc); - if ( takeFirstOnly ) break; - } - } - - if ( n > 0 && printContexts ) { - out.printf("%s => had %d variant context objects%n", context.getLocation(), n); - out.printf("---------------------------------------------%n"); - } - - return n; - } - } - - private static VCFHeader createVCFHeader(VariantContext vc) { - return new VCFHeader(new HashSet(), vc.getGenotypes().getSampleNamesOrderedByName()); - } - - public Integer reduceInit() { - return 0; - } - - public Integer reduce(Integer point, Integer sum) { - return point + sum; - } - - @Override - public void onTraversalDone(Integer result) { - // Double check traversal result to make count is the same. - // TODO: Is this check necessary? - out.println("[REDUCE RESULT] Traversal result is: " + result); - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index fae7cb05a..6bc71a76d 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -230,7 +230,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { for ( final Genotype g : vc.getGenotypes() ) { gc.add(new Genotype(g.getSampleName()+"_"+i, g)); } - toMerge.add(VariantContext.modifyGenotypes(vc, gc)); + toMerge.add(new VariantContextBuilder(vc).genotypes(gc).make()); } VariantContextUtils.simpleMerge(b37GenomeLocParser, toMerge, null, diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 5bc72e132..38c4f84ab 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -255,7 +255,7 @@ public class VariantContextUnitTest extends BaseTest { public void testCreatingPartiallyCalledGenotype() { List alleles = Arrays.asList(Aref, C); Genotype g = new Genotype("foo", Arrays.asList(C, Allele.NO_CALL), 10); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, alleles, Arrays.asList(g)); + VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g).make(); Assert.assertTrue(vc.isSNP()); Assert.assertEquals(vc.getNAlleles(), 2); @@ -328,7 +328,8 @@ public class VariantContextUnitTest extends BaseTest { Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3)); + VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles) + .genotypes(g1, g2, g3).make(); Assert.assertTrue(vc.hasGenotypes()); Assert.assertFalse(vc.isMonomorphic()); @@ -367,7 +368,8 @@ public class VariantContextUnitTest extends BaseTest { Genotype g5 = new Genotype("dd", Arrays.asList(del, del), 10); Genotype g6 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3, g4, g5, g6)); + VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles) + .genotypes(g1, g2, g3, g4, g5, g6).make(); Assert.assertTrue(vc.hasGenotypes()); Assert.assertFalse(vc.isMonomorphic()); @@ -392,7 +394,8 @@ public class VariantContextUnitTest extends BaseTest { Genotype g1 = new Genotype("AA1", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AA2", Arrays.asList(Aref, Aref), 10); Genotype g3 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1, g2, g3)); + VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles) + .genotypes(g1, g2, g3).make(); Assert.assertTrue(vc.hasGenotypes()); Assert.assertTrue(vc.isMonomorphic()); @@ -412,21 +415,20 @@ public class VariantContextUnitTest extends BaseTest { Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2)); + VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g1, g2).make(); Assert.assertTrue(vc.isNotFiltered()); Assert.assertFalse(vc.isFiltered()); Assert.assertEquals(0, vc.getFilters().size()); - Set filters = new HashSet(Arrays.asList("BAD_SNP_BAD!")); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); + vc = new VariantContextBuilder(vc).filters("BAD_SNP_BAD!").make(); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); Assert.assertEquals(1, vc.getFilters().size()); - filters = new HashSet(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, Arrays.asList(g1,g2), VariantContext.NO_NEG_LOG_10PERROR, filters, null); + Set filters = new HashSet(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); + vc = new VariantContextBuilder(vc).filters(filters).make(); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); @@ -441,7 +443,7 @@ public class VariantContextUnitTest extends BaseTest { Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); Genotype g4 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); Genotype g5 = new Genotype("--", Arrays.asList(del, del), 10); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop , alleles, Arrays.asList(g1,g2,g3,g4,g5)); + VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g1,g2,g3,g4,g5).make(); VariantContext vc12 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g2.getSampleName()))); VariantContext vc1 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName()))); @@ -495,7 +497,7 @@ public class VariantContextUnitTest extends BaseTest { Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); GenotypesContext gc = GenotypesContext.create(g1, g2, g3); - VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); Assert.assertEquals(vc.getGenotype("AA"), g1); Assert.assertEquals(vc.getGenotype("AT"), g2); @@ -586,7 +588,7 @@ public class VariantContextUnitTest extends BaseTest { private SitesAndGenotypesVC(String name, VariantContext original) { super(SitesAndGenotypesVC.class, name); this.vc = original; - this.copy = new VariantContext(original); + this.copy = new VariantContextBuilder(original).make(); } public String toString() { @@ -600,8 +602,8 @@ public class VariantContextUnitTest extends BaseTest { Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); - VariantContext sites = new VariantContext("sites", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)); - VariantContext genotypes = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T), Arrays.asList(g1, g2, g3)); + VariantContext sites = new VariantContextBuilder("sites", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).make(); + VariantContext genotypes = new VariantContextBuilder(sites).source("genotypes").genotypes(g1, g2, g3).make(); new SitesAndGenotypesVC("sites", sites); new SitesAndGenotypesVC("genotypes", genotypes); @@ -616,32 +618,32 @@ public class VariantContextUnitTest extends BaseTest { // -------------------------------------------------------------------------------- @Test(dataProvider = "SitesAndGenotypesVC") public void runModifyVCTests(SitesAndGenotypesVC cfg) { - VariantContext modified = VariantContext.modifyLocation(cfg.vc, "chr2", 123, 123); + VariantContext modified = new VariantContextBuilder(cfg.vc).loc("chr2", 123, 123).make(); Assert.assertEquals(modified.getChr(), "chr2"); Assert.assertEquals(modified.getStart(), 123); Assert.assertEquals(modified.getEnd(), 123); - modified = VariantContext.modifyID(cfg.vc, "newID"); + modified = new VariantContextBuilder(cfg.vc).id("newID").make(); Assert.assertEquals(modified.getID(), "newID"); Set newFilters = Collections.singleton("newFilter"); - modified = VariantContext.modifyFilters(cfg.vc, newFilters); + modified = new VariantContextBuilder(cfg.vc).filters(newFilters).make(); Assert.assertEquals(modified.getFilters(), newFilters); - modified = VariantContext.modifyAttribute(cfg.vc, "AC", 1); + modified = new VariantContextBuilder(cfg.vc).attribute("AC", 1).make(); Assert.assertEquals(modified.getAttribute("AC"), 1); - modified = VariantContext.modifyAttribute(modified, "AC", 2); + modified = new VariantContextBuilder(modified).attribute("AC", 2).make(); Assert.assertEquals(modified.getAttribute("AC"), 2); - modified = VariantContext.modifyAttributes(modified, null); + modified = new VariantContextBuilder(modified).attributes(null).make(); Assert.assertTrue(modified.getAttributes().isEmpty()); Genotype g1 = new Genotype("AA2", Arrays.asList(Aref, Aref), 10); Genotype g2 = new Genotype("AT2", Arrays.asList(Aref, T), 10); Genotype g3 = new Genotype("TT2", Arrays.asList(T, T), 10); GenotypesContext gc = GenotypesContext.create(g1,g2,g3); - modified = VariantContext.modifyGenotypes(cfg.vc, gc); + modified = new VariantContextBuilder(cfg.vc).genotypes(gc).make(); Assert.assertEquals(modified.getGenotypes(), gc); - modified = VariantContext.modifyGenotypes(cfg.vc, null); + modified = new VariantContextBuilder(cfg.vc).noGenotypes().make(); Assert.assertTrue(modified.getGenotypes().isEmpty()); // test that original hasn't changed @@ -697,7 +699,7 @@ public class VariantContextUnitTest extends BaseTest { Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); GenotypesContext gc = GenotypesContext.create(g1, g2, g3); - VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); VariantContext sub = cfg.updateAlleles ? vc.subContextFromSamples(cfg.samples) : vc.subContextFromSamples(cfg.samples, vc.getAlleles()); // unchanged attributes should be the same @@ -782,8 +784,7 @@ public class VariantContextUnitTest extends BaseTest { gc.add(new Genotype(name, Arrays.asList(Aref, T))); } - VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, - snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); + VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); // same sample names => success Assert.assertEquals(vc.getSampleNames(), new HashSet(cfg.sampleNames), "vc.getSampleNames() = " + vc.getSampleNames()); @@ -823,9 +824,7 @@ public class VariantContextUnitTest extends BaseTest { } - VariantContext vc = new VariantContext("genotypes", VCFConstants.EMPTY_ID_FIELD, snpLoc, - snpLocStart, snpLocStop, Arrays.asList(Aref, T), gc); - + VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); Assert.assertEquals(vc.getNSamples(), nSamples); if ( nSamples > 0 ) { Assert.assertEquals(vc.isPolymorphic(), nT > 0); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index 805781fe0..a48596ca1 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -250,7 +250,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { final List inputs = new ArrayList(); for ( final String id : cfg.inputs ) { - inputs.add(VariantContext.modifyID(snpVC1, id)); + inputs.add(new VariantContextBuilder(snpVC1).id(id).make()); } final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, From 768b27322bda816e6c6c5a05b4a6dcbb74f1efcb Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 18 Nov 2011 12:29:15 -0500 Subject: [PATCH 056/113] I figured out why we were getting tons of hom var genotype calls with Mauricio's low quality (synthetic) reduced reads: the RR implementation in the UG was not capping the base quality by the mapping quality, so all the low quality reads were used to generate GLs. Fixed. --- .../DiploidSNPGenotypeLikelihoods.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java index 666fe88a3..295cf8688 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java @@ -28,7 +28,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import net.sf.samtools.SAMUtils; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.fragments.FragmentCollection; -import org.broadinstitute.sting.utils.fragments.FragmentUtils; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.QualityUtils; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -275,19 +274,20 @@ public class DiploidSNPGenotypeLikelihoods implements Cloneable { public int add(PileupElement elt, boolean ignoreBadBases, boolean capBaseQualsAtMappingQual, int minBaseQual) { byte obsBase = elt.getBase(); + byte qual = qualToUse(elt, ignoreBadBases, capBaseQualsAtMappingQual, minBaseQual); if ( elt.isReducedRead() ) { // reduced read representation - byte qual = elt.getQual(); - if ( BaseUtils.isRegularBase( elt.getBase() )) { + if ( BaseUtils.isRegularBase( obsBase )) { add(obsBase, qual, (byte)0, (byte)0, elt.getRepresentativeCount()); // fast calculation of n identical likelihoods return elt.getRepresentativeCount(); // we added nObs bases here - } else // odd bases or deletions => don't use them - return 0; - } else { - byte qual = qualToUse(elt, ignoreBadBases, capBaseQualsAtMappingQual, minBaseQual); - return qual > 0 ? add(obsBase, qual, (byte)0, (byte)0, 1) : 0; + } + + // odd bases or deletions => don't use them + return 0; } + + return qual > 0 ? add(obsBase, qual, (byte)0, (byte)0, 1) : 0; } public int add(List overlappingPair, boolean ignoreBadBases, boolean capBaseQualsAtMappingQual, int minBaseQual) { @@ -511,20 +511,19 @@ public class DiploidSNPGenotypeLikelihoods implements Cloneable { * @return */ private static byte qualToUse(PileupElement p, boolean ignoreBadBases, boolean capBaseQualsAtMappingQual, int minBaseQual) { - if ( ignoreBadBases && !BaseUtils.isRegularBase( p.getBase() ) ) { + if ( ignoreBadBases && !BaseUtils.isRegularBase( p.getBase() ) ) return 0; - } else { - byte qual = p.getQual(); - if ( qual > SAMUtils.MAX_PHRED_SCORE ) - throw new UserException.MalformedBAM(p.getRead(), String.format("the maximum allowed quality score is %d, but a quality of %d was observed in read %s. Perhaps your BAM incorrectly encodes the quality scores in Sanger format; see http://en.wikipedia.org/wiki/FASTQ_format for more details", SAMUtils.MAX_PHRED_SCORE, qual, p.getRead().getReadName())); - if ( capBaseQualsAtMappingQual ) - qual = (byte)Math.min((int)p.getQual(), p.getMappingQual()); - if ( (int)qual < minBaseQual ) - qual = (byte)0; + byte qual = p.getQual(); - return qual; - } + if ( qual > SAMUtils.MAX_PHRED_SCORE ) + throw new UserException.MalformedBAM(p.getRead(), String.format("the maximum allowed quality score is %d, but a quality of %d was observed in read %s. Perhaps your BAM incorrectly encodes the quality scores in Sanger format; see http://en.wikipedia.org/wiki/FASTQ_format for more details", SAMUtils.MAX_PHRED_SCORE, qual, p.getRead().getReadName())); + if ( capBaseQualsAtMappingQual ) + qual = (byte)Math.min((int)p.getQual(), p.getMappingQual()); + if ( (int)qual < minBaseQual ) + qual = (byte)0; + + return qual; } // ----------------------------------------------------------------------------------------------------------------- From c62082ba1b5d1c1b29842f671e5ef0ff6ba60d40 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 18 Nov 2011 12:34:27 -0500 Subject: [PATCH 057/113] Making this class public again as per request from Cancer folks --- .../sting/gatk/walkers/genotyper/DiploidGenotype.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java index b5987963f..106bb1982 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java @@ -34,7 +34,7 @@ import org.broadinstitute.sting.utils.BaseUtils; * Time: 6:46:09 PM * To change this template use File | Settings | File Templates. */ -enum DiploidGenotype { +public enum DiploidGenotype { AA ('A', 'A'), AC ('A', 'C'), AG ('A', 'G'), From f54afc19b48804d697971f284884835be1f290a9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 12:39:10 -0500 Subject: [PATCH 058/113] VariantContextBuilder -- New approach to making VariantContexts modeled on StringBuilder -- No more modify routines -- use VariantContextBuilder -- Renamed isPolymorphic to isPolymorphicInSamples. Same for mono -- getChromosomeCount -> getCalledChrCount -- Walkers changed to use new VariantContext. Some deprecated new VariantContext calls remain -- VCFCodec now uses optimized cached information to create GenotypesContext. --- .../gatk/walkers/annotator/SampleList.java | 2 +- .../validation/ValidationAmplicons.java | 4 +- .../varianteval/evaluators/CompOverlap.java | 2 +- .../varianteval/evaluators/CountVariants.java | 2 +- .../evaluators/G1KPhaseITable.java | 3 +- .../evaluators/IndelLengthHistogram.java | 2 +- .../evaluators/IndelStatistics.java | 4 +- .../evaluators/SimpleMetricsByAC.java | 4 +- .../evaluators/ThetaVariantEvaluator.java | 2 +- .../evaluators/TiTvVariantEvaluator.java | 2 +- .../evaluators/ValidationReport.java | 5 +- .../evaluators/VariantQualityScore.java | 4 +- .../stratifications/AlleleCount.java | 2 +- .../VariantDataManager.java | 4 +- .../walkers/variantutils/SelectVariants.java | 10 +- .../VariantValidationAssessor.java | 12 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 9 +- .../sting/utils/codecs/vcf/VCFCodec.java | 4 +- .../sting/utils/codecs/vcf/VCFHeader.java | 26 ++++ .../variantcontext/GenotypesContext.java | 8 + .../utils/variantcontext/VariantContext.java | 137 +++++------------- .../variantcontext/VariantContextUtils.java | 10 +- .../VariantContextUnitTest.java | 83 ++++++----- 23 files changed, 149 insertions(+), 192 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java index 84a4a3120..cbf536e4f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SampleList.java @@ -47,7 +47,7 @@ import java.util.Map; public class SampleList extends InfoFieldAnnotation { public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - if ( vc.isMonomorphic() || !vc.hasGenotypes() ) + if ( vc.isMonomorphicInSamples() || !vc.hasGenotypes() ) return null; StringBuffer samples = new StringBuffer(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/ValidationAmplicons.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/ValidationAmplicons.java index 088c4ddc4..b27bef265 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/ValidationAmplicons.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/ValidationAmplicons.java @@ -260,7 +260,7 @@ public class ValidationAmplicons extends RodWalker { } } } else /* (mask != null && validate == null ) */ { - if ( ! mask.isSNP() && ! mask.isFiltered() && ( ! filterMonomorphic || ! mask.isMonomorphic() )) { + if ( ! mask.isSNP() && ! mask.isFiltered() && ( ! filterMonomorphic || ! mask.isMonomorphicInSamples() )) { logger.warn("Mask Variant Context on the following warning line is not a SNP. Currently we can only mask out SNPs. This probe will not be designed."); logger.warn(String.format("%s:%d-%d\t%s\t%s",mask.getChr(),mask.getStart(),mask.getEnd(),mask.isSimpleInsertion() ? "INS" : "DEL", Utils.join(",",mask.getAlleles()))); sequenceInvalid = true; @@ -281,7 +281,7 @@ public class ValidationAmplicons extends RodWalker { sequence.append('N'); indelCounter--; rawSequence.append(Character.toUpperCase((char)ref.getBase())); - } else if ( ! mask.isFiltered() && ( ! filterMonomorphic || ! mask.isMonomorphic() )){ + } else if ( ! mask.isFiltered() && ( ! filterMonomorphic || ! mask.isMonomorphicInSamples() )){ logger.debug("SNP in mask found at " + ref.getLocus().toString()); if ( lowerCaseSNPs ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java index 9facb11b5..b3695921a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java @@ -72,7 +72,7 @@ public class CompOverlap extends VariantEvaluator implements StandardEval { } public String update2(VariantContext eval, VariantContext comp, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - boolean evalIsGood = eval != null && eval.isPolymorphic(); + boolean evalIsGood = eval != null && eval.isPolymorphicInSamples(); boolean compIsGood = comp != null && comp.isNotFiltered(); if (evalIsGood) nEvalVariants++; // count the number of eval events diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java index a134ef5aa..d8413573a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java @@ -103,7 +103,7 @@ public class CountVariants extends VariantEvaluator implements StandardEval { // So in order to maintain consistency with the previous implementation (and the intention of the original author), I've // added in a proxy check for monomorphic status here. // Protect against case when vc only as no-calls too - can happen if we strafity by sample and sample as a single no-call. - if ( vc1.isMonomorphic() ) { + if ( vc1.isMonomorphicInSamples() ) { nRefLoci++; } else { switch (vc1.getType()) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java index 417e340b8..ff8f6307c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java @@ -30,7 +30,6 @@ import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.varianteval.VariantEvalWalker; import org.broadinstitute.sting.gatk.walkers.varianteval.util.Analysis; import org.broadinstitute.sting.gatk.walkers.varianteval.util.DataPoint; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; @@ -103,7 +102,7 @@ public class G1KPhaseITable extends VariantEvaluator { } public String update2(VariantContext eval, VariantContext comp, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if ( eval == null || eval.isMonomorphic() ) return null; + if ( eval == null || eval.isMonomorphicInSamples() ) return null; switch (eval.getType()) { case SNP: diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelLengthHistogram.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelLengthHistogram.java index ffe7c185f..ccec9af12 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelLengthHistogram.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelLengthHistogram.java @@ -91,7 +91,7 @@ public class IndelLengthHistogram extends VariantEvaluator { public String update1(VariantContext vc1, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if ( vc1.isIndel() && vc1.isPolymorphic() ) { + if ( vc1.isIndel() && vc1.isPolymorphicInSamples() ) { if ( ! vc1.isBiallelic() ) { //veWalker.getLogger().warn("[IndelLengthHistogram] Non-biallelic indel at "+ref.getLocus()+" ignored."); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelStatistics.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelStatistics.java index f70e6c2de..87b453ae3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelStatistics.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/IndelStatistics.java @@ -8,11 +8,9 @@ import org.broadinstitute.sting.gatk.walkers.varianteval.util.Analysis; import org.broadinstitute.sting.gatk.walkers.varianteval.util.DataPoint; import org.broadinstitute.sting.gatk.walkers.varianteval.util.TableType; import org.broadinstitute.sting.utils.IndelUtils; -import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.ArrayList; -import java.util.HashMap; /* * Copyright (c) 2010 The Broad Institute @@ -270,7 +268,7 @@ public class IndelStatistics extends VariantEvaluator { public String update1(VariantContext eval, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if (eval != null && eval.isPolymorphic()) { + if (eval != null && eval.isPolymorphicInSamples()) { if ( indelStats == null ) { indelStats = new IndelStats(eval); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java index 2d0163206..27e8e7c86 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java @@ -118,7 +118,7 @@ public class SimpleMetricsByAC extends VariantEvaluator implements StandardEval int ac = -1; if ( eval.hasGenotypes() ) - ac = eval.getChromosomeCount(eval.getAlternateAllele(0)); + ac = eval.getCalledChrCount(eval.getAlternateAllele(0)); else if ( eval.hasAttribute("AC") ) { ac = eval.getAttributeAsInt("AC", -1); } @@ -166,7 +166,7 @@ public class SimpleMetricsByAC extends VariantEvaluator implements StandardEval } } - if ( eval.isSNP() && eval.isBiallelic() && eval.isPolymorphic() && metrics != null ) { + if ( eval.isSNP() && eval.isBiallelic() && eval.isPolymorphicInSamples() && metrics != null ) { metrics.incrValue(eval); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java index e1069d2d2..bb7843361 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ThetaVariantEvaluator.java @@ -37,7 +37,7 @@ public class ThetaVariantEvaluator extends VariantEvaluator { } public String update1(VariantContext vc, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if (vc == null || !vc.isSNP() || !vc.hasGenotypes() || vc.isMonomorphic()) { + if (vc == null || !vc.isSNP() || !vc.hasGenotypes() || vc.isMonomorphicInSamples()) { return null; //no interesting sites } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java index 9b6e145e6..17d7171b8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java @@ -40,7 +40,7 @@ public class TiTvVariantEvaluator extends VariantEvaluator implements StandardEv } public void updateTiTv(VariantContext vc, boolean updateStandard) { - if (vc != null && vc.isSNP() && vc.isBiallelic() && vc.isPolymorphic()) { + if (vc != null && vc.isSNP() && vc.isBiallelic() && vc.isPolymorphicInSamples()) { if (VariantContextUtils.isTransition(vc)) { if (updateStandard) nTiInComp++; else nTi++; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java index 3b4967cad..1a0591e9d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java @@ -11,7 +11,6 @@ import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Collection; -import java.util.Set; /** * The Broad Institute @@ -118,8 +117,8 @@ public class ValidationReport extends VariantEvaluator implements StandardEval { public SiteStatus calcSiteStatus(VariantContext vc) { if ( vc == null ) return SiteStatus.NO_CALL; if ( vc.isFiltered() ) return SiteStatus.FILTERED; - if ( vc.isMonomorphic() ) return SiteStatus.MONO; - if ( vc.hasGenotypes() ) return SiteStatus.POLY; // must be polymorphic if isMonomorphic was false and there are genotypes + if ( vc.isMonomorphicInSamples() ) return SiteStatus.MONO; + if ( vc.hasGenotypes() ) return SiteStatus.POLY; // must be polymorphic if isMonomorphicInSamples was false and there are genotypes if ( vc.hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) { int ac = 0; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantQualityScore.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantQualityScore.java index 263227938..ce9e45c9b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantQualityScore.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantQualityScore.java @@ -232,14 +232,14 @@ public class VariantQualityScore extends VariantEvaluator { public String update1(VariantContext eval, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { final String interesting = null; - if( eval != null && eval.isSNP() && eval.isBiallelic() && eval.isPolymorphic() ) { //BUGBUG: only counting biallelic sites (revisit what to do with triallelic sites) + if( eval != null && eval.isSNP() && eval.isBiallelic() && eval.isPolymorphicInSamples() ) { //BUGBUG: only counting biallelic sites (revisit what to do with triallelic sites) if( titvStats == null ) { titvStats = new TiTvStats(); } titvStats.incrValue(eval.getPhredScaledQual(), VariantContextUtils.isTransition(eval)); if( alleleCountStats == null ) { alleleCountStats = new AlleleCountStats(); } int alternateAlleleCount = 0; for (final Allele a : eval.getAlternateAlleles()) { - alternateAlleleCount += eval.getChromosomeCount(a); + alternateAlleleCount += eval.getCalledChrCount(a); } alleleCountStats.incrValue(eval.getPhredScaledQual(), alternateAlleleCount); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleCount.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleCount.java index c7bea93b2..2f342e120 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleCount.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleCount.java @@ -47,7 +47,7 @@ public class AlleleCount extends VariantStratifier { AC = eval.getAttributeAsInt("AC", 0); } else if ( eval.isVariant() ) { for (Allele allele : eval.getAlternateAlleles()) - AC = Math.max(AC, eval.getChromosomeCount(allele)); + AC = Math.max(AC, eval.getCalledChrCount(allele)); } else // by default, the site is considered monomorphic AC = 0; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantDataManager.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantDataManager.java index e04bfab76..a2782fe34 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantDataManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantDataManager.java @@ -26,7 +26,6 @@ package org.broadinstitute.sting.gatk.walkers.variantrecalibration; import org.apache.log4j.Logger; -import org.broadinstitute.sting.commandline.RodBinding; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.GenomeLoc; @@ -38,7 +37,6 @@ import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; /** @@ -284,7 +282,7 @@ public class VariantDataManager { private boolean isValidVariant( final VariantContext evalVC, final VariantContext trainVC, final boolean TRUST_ALL_POLYMORPHIC) { return trainVC != null && trainVC.isNotFiltered() && trainVC.isVariant() && ((evalVC.isSNP() && trainVC.isSNP()) || ((evalVC.isIndel()||evalVC.isMixed()) && (trainVC.isIndel()||trainVC.isMixed()))) && - (TRUST_ALL_POLYMORPHIC || !trainVC.hasGenotypes() || trainVC.isPolymorphic()); + (TRUST_ALL_POLYMORPHIC || !trainVC.hasGenotypes() || trainVC.isPolymorphicInSamples()); } public void writeOutRecalibrationTable( final PrintStream RECAL_FILE ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index be9a193d3..9c24360c5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -488,7 +488,7 @@ public class SelectVariants extends RodWalker { if (outMVFile != null) outMVFileStream.format("MV@%s:%d. REF=%s, ALT=%s, AC=%d, momID=%s, dadID=%s, childID=%s, momG=%s, momGL=%s, dadG=%s, dadGL=%s, " + "childG=%s childGL=%s\n",vc.getChr(), vc.getStart(), - vc.getReference().getDisplayString(), vc.getAlternateAllele(0).getDisplayString(), vc.getChromosomeCount(vc.getAlternateAllele(0)), + vc.getReference().getDisplayString(), vc.getAlternateAllele(0).getDisplayString(), vc.getCalledChrCount(vc.getAlternateAllele(0)), mv.getSampleMom(), mv.getSampleDad(), mv.getSampleChild(), vc.getGenotype(mv.getSampleMom()).toBriefString(), vc.getGenotype(mv.getSampleMom()).getLikelihoods().getAsString(), vc.getGenotype(mv.getSampleDad()).toBriefString(), vc.getGenotype(mv.getSampleMom()).getLikelihoods().getAsString(), @@ -520,7 +520,7 @@ public class SelectVariants extends RodWalker { continue; VariantContext sub = subsetRecord(vc, samples); - if ( (sub.isPolymorphic() || !EXCLUDE_NON_VARIANTS) && (!sub.isFiltered() || !EXCLUDE_FILTERED) ) { + if ( (sub.isPolymorphicInSamples() || !EXCLUDE_NON_VARIANTS) && (!sub.isFiltered() || !EXCLUDE_FILTERED) ) { for ( VariantContextUtils.JexlVCMatchExp jexl : jexls ) { if ( !VariantContextUtils.match(sub, jexl) ) { return 0; @@ -677,11 +677,11 @@ public class SelectVariants extends RodWalker { if (KEEP_ORIGINAL_CHR_COUNTS) { if ( sub.hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) - builder.attribute("AC_Orig",sub.getAttribute(VCFConstants.ALLELE_COUNT_KEY)); + builder.attribute("AC_Orig", sub.getAttribute(VCFConstants.ALLELE_COUNT_KEY)); if ( sub.hasAttribute(VCFConstants.ALLELE_FREQUENCY_KEY) ) - builder.attribute("AF_Orig",sub.getAttribute(VCFConstants.ALLELE_FREQUENCY_KEY)); + builder.attribute("AF_Orig", sub.getAttribute(VCFConstants.ALLELE_FREQUENCY_KEY)); if ( sub.hasAttribute(VCFConstants.ALLELE_NUMBER_KEY) ) - builder.attribute("AN_Orig",sub.getAttribute(VCFConstants.ALLELE_NUMBER_KEY)); + builder.attribute("AN_Orig", sub.getAttribute(VCFConstants.ALLELE_NUMBER_KEY)); } Map attributes = new HashMap(builder.make().getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java index 79bbea29d..31aa8963b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantValidationAssessor.java @@ -233,17 +233,17 @@ public class VariantValidationAssessor extends RodWalker numRecords++; // add the info fields - builder.attribute("NoCallPct", String.format("%.1f", 100.0*noCallProp)); - builder.attribute("HomRefPct", String.format("%.1f", 100.0*homRefProp)); - builder.attribute("HomVarPct", String.format("%.1f", 100.0*homVarProp)); - builder.attribute("HetPct", String.format("%.1f", 100.0*hetProp)); + builder.attribute("NoCallPct", String.format("%.1f", 100.0 * noCallProp)); + builder.attribute("HomRefPct", String.format("%.1f", 100.0 * homRefProp)); + builder.attribute("HomVarPct", String.format("%.1f", 100.0 * homVarProp)); + builder.attribute("HetPct", String.format("%.1f", 100.0 * hetProp)); builder.attribute("HW", String.format("%.2f", hwScore)); Collection altAlleles = vContext.getAlternateAlleles(); - int altAlleleCount = altAlleles.size() == 0 ? 0 : vContext.getChromosomeCount(altAlleles.iterator().next()); + int altAlleleCount = altAlleles.size() == 0 ? 0 : vContext.getCalledChrCount(altAlleles.iterator().next()); if ( !isViolation && altAlleleCount > 0 ) numTrueVariants++; builder.attribute(VCFConstants.ALLELE_COUNT_KEY, String.format("%d", altAlleleCount)); - builder.attribute(VCFConstants.ALLELE_NUMBER_KEY, String.format("%d", vContext.getChromosomeCount())); + builder.attribute(VCFConstants.ALLELE_NUMBER_KEY, String.format("%d", vContext.getCalledChrCount())); return builder.make(); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index ba138a9da..e6e4aa8ce 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -98,7 +98,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, headerStrings.add(line); Set metaData = new TreeSet(); - Set auxTags = new LinkedHashSet(); + Set sampleNames = new LinkedHashSet(); // iterate over all the passed in strings for ( String str : headerStrings ) { if ( !str.startsWith(VCFHeader.METADATA_INDICATOR) ) { @@ -126,9 +126,9 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, } while ( arrayIndex < strings.length ) - auxTags.add(strings[arrayIndex++]); + sampleNames.add(strings[arrayIndex++]); - if ( sawFormatTag && auxTags.size() == 0 ) + if ( sawFormatTag && sampleNames.size() == 0 ) throw new UserException.MalformedVCFHeader("The FORMAT field was provided but there is no genotype/sample data"); } else { @@ -152,7 +152,8 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, } } - header = new VCFHeader(metaData, auxTags); + header = new VCFHeader(metaData, sampleNames); + header.buildVCFReaderMaps(new ArrayList(sampleNames)); return header; } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index 53b3d5fd4..42c224fe9 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -151,7 +151,7 @@ public class VCFCodec extends AbstractVCFCodec { int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - GenotypesContext genotypes = GenotypesContext.create(nParts); + ArrayList genotypes = new ArrayList(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); @@ -215,7 +215,7 @@ public class VCFCodec extends AbstractVCFCodec { } } - return genotypes; + return GenotypesContext.create(genotypes, header.sampleNameToOffset, header.sampleNamesInOrder); } @Override diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFHeader.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFHeader.java index 66e11bc1e..5c5df15ab 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFHeader.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFHeader.java @@ -2,6 +2,7 @@ package org.broadinstitute.sting.utils.codecs.vcf; import org.broad.tribble.util.ParsingUtils; +import org.broadinstitute.sting.utils.variantcontext.Genotype; import java.util.*; @@ -38,6 +39,10 @@ public class VCFHeader { // were the input samples sorted originally (or are we sorting them)? private boolean samplesWereAlreadySorted = true; + // cache for efficient conversion of VCF -> VariantContext + protected ArrayList sampleNamesInOrder = null; + protected HashMap sampleNameToOffset = null; + /** * create a VCF header, given a list of meta data and auxillary tags @@ -69,6 +74,27 @@ public class VCFHeader { samplesWereAlreadySorted = ParsingUtils.isSorted(genotypeSampleNames); } + /** + * Tell this VCF header to use pre-calculated sample name ordering and the + * sample name -> offset map. This assumes that all VariantContext created + * using this header (i.e., read by the VCFCodec) will have genotypes + * occurring in the same order + * + */ + + protected void buildVCFReaderMaps(List genotypeSampleNamesInAppearenceOrder) { + sampleNamesInOrder = new ArrayList(genotypeSampleNamesInAppearenceOrder.size()); + sampleNameToOffset = new HashMap(genotypeSampleNamesInAppearenceOrder.size()); + + int i = 0; + for ( final String name : genotypeSampleNamesInAppearenceOrder ) { + sampleNamesInOrder.add(name); + sampleNameToOffset.put(name, i++); + } + Collections.sort(sampleNamesInOrder); + } + + /** * Adds a header line to the header metadata. * diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 77a02874d..671066d24 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -85,6 +85,12 @@ public class GenotypesContext implements List { return new GenotypesContext(nGenotypes, false); } + public static final GenotypesContext create(final ArrayList genotypes, + final Map sampleNameToOffset, + final List sampleNamesInOrder) { + return new GenotypesContext(genotypes, sampleNameToOffset, sampleNamesInOrder, false); + } + public static final GenotypesContext create(final ArrayList genotypes) { return genotypes == null ? NO_GENOTYPES : new GenotypesContext(genotypes, false); } @@ -101,6 +107,8 @@ public class GenotypesContext implements List { return toCopy == null ? NO_GENOTYPES : create(new ArrayList(toCopy)); } + + // public static final GenotypeMap create(final Collection genotypes) { // if ( genotypes == null ) // return null; // todo -- really should return an empty map diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 455a9b997..9875680b0 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -131,17 +131,17 @@ import java.util.*; * *

  * vc.hasGenotypes()
- * vc.isMonomorphic()
- * vc.isPolymorphic()
+ * vc.isMonomorphicInSamples()
+ * vc.isPolymorphicInSamples()
  * vc.getSamples().size()
  *
  * vc.getGenotypes()
  * vc.getGenotypes().get("g1")
  * vc.hasGenotype("g1")
  *
- * vc.getChromosomeCount()
- * vc.getChromosomeCount(Aref)
- * vc.getChromosomeCount(T)
+ * vc.getCalledChrCount()
+ * vc.getCalledChrCount(Aref)
+ * vc.getCalledChrCount(T)
  * 
* * === NO_CALL alleles === @@ -374,72 +374,17 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- -// /** -// * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotype -// * genotype and alleles in genotype. This is the right way to test if a single genotype is actually -// * variant or not. -// * -// * @param genotype genotype -// * @return vc subcontext -// * @deprecated replaced by {@link #subContextFromSample(String)} -// */ -// public VariantContext subContextFromGenotypes(Genotype genotype) { -// return subContextFromGenotypes(Arrays.asList(genotype)); -// } -// -// -// /** -// * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes -// * genotypes and alleles in these genotypes. This is the right way to test if a single genotype is actually -// * variant or not. -// * -// * @param genotypes genotypes -// * @return vc subcontext -// * @deprecated replaced by {@link #subContextFromSamples(java.util.Collection)} -// */ -// public VariantContext subContextFromGenotypes(Collection genotypes) { -// return subContextFromGenotypes(genotypes, allelesOfGenotypes(genotypes)) ; -// } -// -// /** -// * Returns a context identical to this (i.e., filter, qual are all the same) but containing only the Genotypes -// * genotypes. Also, the resulting variant context will contain the alleles provided, not only those found in genotypes -// * -// * @param genotypes genotypes -// * @param alleles the set of allele segregating alleles at this site. Must include those in genotypes, but may be more -// * @return vc subcontext -// * @deprecated replaced by {@link #subContextFromSamples(java.util.Collection, java.util.Collection)} -// */ -// @Deprecated -// public VariantContext subContextFromGenotypes(Collection genotypes, Collection alleles) { -// return new VariantContext(getSource(), contig, start, stop, alleles, -// GenotypeCollection.create(genotypes), -// getNegLog10PError(), -// filtersWereApplied() ? getFilters() : null, -// getAttributes(), -// getReferenceBaseForIndel()); -// } - public VariantContext subContextFromSamples(Set sampleNames, Collection alleles) { loadGenotypes(); - GenotypesContext newGenotypes = genotypes.subsetToSamples(sampleNames); - return new VariantContext(getSource(), getID(), contig, start, stop, alleles, - newGenotypes, - getNegLog10PError(), - filtersWereApplied() ? getFilters() : null, - getAttributes(), - getReferenceBaseForIndel()); + VariantContextBuilder builder = new VariantContextBuilder(this); + return builder.genotypes(genotypes.subsetToSamples(sampleNames)).make(); } public VariantContext subContextFromSamples(Set sampleNames) { loadGenotypes(); + VariantContextBuilder builder = new VariantContextBuilder(this); GenotypesContext newGenotypes = genotypes.subsetToSamples(sampleNames); - return new VariantContext(getSource(), getID(), contig, start, stop, allelesOfGenotypes(newGenotypes), - newGenotypes, - getNegLog10PError(), - filtersWereApplied() ? getFilters() : null, - getAttributes(), - getReferenceBaseForIndel()); + return builder.genotypes(newGenotypes).alleles(allelesOfGenotypes(newGenotypes)).make(); } public VariantContext subContextFromSample(String sampleName) { @@ -451,12 +396,12 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param genotypes genotypes * @return allele set */ - private Set allelesOfGenotypes(Collection genotypes) { - Set alleles = new HashSet(); + private final Set allelesOfGenotypes(Collection genotypes) { + final Set alleles = new HashSet(); boolean addedref = false; - for ( Genotype g : genotypes ) { - for ( Allele a : g.getAlleles() ) { + for ( final Genotype g : genotypes ) { + for ( final Allele a : g.getAlleles() ) { addedref = addedref || a.isReference(); if ( a.isCalled() ) alleles.add(a); @@ -938,7 +883,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * * @return chromosome count */ - public int getChromosomeCount() { + public int getCalledChrCount() { int n = 0; for ( final Genotype g : getGenotypes() ) { @@ -955,7 +900,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param a allele * @return chromosome count */ - public int getChromosomeCount(Allele a) { + public int getCalledChrCount(Allele a) { int n = 0; for ( final Genotype g : getGenotypes() ) { @@ -971,9 +916,9 @@ public class VariantContext implements Feature { // to enable tribble intergrati * * @return true if it's monomorphic */ - public boolean isMonomorphic() { + public boolean isMonomorphicInSamples() { if ( monomorphic == null ) - monomorphic = ! isVariant() || (hasGenotypes() && getChromosomeCount(getReference()) == getChromosomeCount()); + monomorphic = ! isVariant() || (hasGenotypes() && getCalledChrCount(getReference()) == getCalledChrCount()); return monomorphic; } @@ -983,8 +928,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati * * @return true if it's polymorphic */ - public boolean isPolymorphic() { - return ! isMonomorphic(); + public boolean isPolymorphicInSamples() { + return ! isMonomorphicInSamples(); } private void calculateGenotypeCounts() { @@ -1119,19 +1064,28 @@ public class VariantContext implements Feature { // to enable tribble intergrati } public void validateChromosomeCounts() { - Map observedAttrs = calculateChromosomeCounts(); - // AN if ( hasAttribute(VCFConstants.ALLELE_NUMBER_KEY) ) { int reportedAN = Integer.valueOf(getAttribute(VCFConstants.ALLELE_NUMBER_KEY).toString()); - int observedAN = (Integer)observedAttrs.get(VCFConstants.ALLELE_NUMBER_KEY); + int observedAN = getCalledChrCount(); if ( reportedAN != observedAN ) throw new TribbleException.InternalCodecException(String.format("the Allele Number (AN) tag is incorrect for the record at position %s:%d, %d vs. %d", getChr(), getStart(), reportedAN, observedAN)); } // AC if ( hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) { - List observedACs = (List)observedAttrs.get(VCFConstants.ALLELE_COUNT_KEY); + ArrayList observedACs = new ArrayList(); + + // if there are alternate alleles, record the relevant tags + if ( getAlternateAlleles().size() > 0 ) { + for ( Allele allele : getAlternateAlleles() ) { + observedACs.add(getCalledChrCount(allele)); + } + } + else { // otherwise, set them to 0 + observedACs.add(0); + } + if ( getAttribute(VCFConstants.ALLELE_COUNT_KEY) instanceof List ) { Collections.sort(observedACs); List reportedACs = (List)getAttribute(VCFConstants.ALLELE_COUNT_KEY); @@ -1152,31 +1106,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati } } - private Map calculateChromosomeCounts() { - Map attributes = new HashMap(); - - attributes.put(VCFConstants.ALLELE_NUMBER_KEY, getChromosomeCount()); - ArrayList alleleFreqs = new ArrayList(); - ArrayList alleleCounts = new ArrayList(); - - // if there are alternate alleles, record the relevant tags - if ( getAlternateAlleles().size() > 0 ) { - for ( Allele allele : getAlternateAlleles() ) { - alleleCounts.add(getChromosomeCount(allele)); - alleleFreqs.add((double)getChromosomeCount(allele) / (double)getChromosomeCount()); - } - } - // otherwise, set them to 0 - else { - alleleCounts.add(0); - alleleFreqs.add(0.0); - } - - attributes.put(VCFConstants.ALLELE_COUNT_KEY, alleleCounts); - attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, alleleFreqs); - return attributes; - } - // --------------------------------------------------------------------------------------------------------- // // validation: the normal validation routines are called automatically upon creation of the VC @@ -1399,7 +1328,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati Allele best = null; int maxAC1 = 0; for (Allele a:this.getAlternateAlleles()) { - int ac = this.getChromosomeCount(a); + int ac = this.getCalledChrCount(a); if (ac >=maxAC1) { maxAC1 = ac; best = a; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index d9057ea8f..972f70689 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -63,7 +63,7 @@ public class VariantContextUtils { */ public static void calculateChromosomeCounts(VariantContext vc, Map attributes, boolean removeStaleValues) { // if everyone is a no-call, remove the old attributes if requested - if ( vc.getChromosomeCount() == 0 && removeStaleValues ) { + if ( vc.getCalledChrCount() == 0 && removeStaleValues ) { if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) attributes.remove(VCFConstants.ALLELE_COUNT_KEY); if ( attributes.containsKey(VCFConstants.ALLELE_FREQUENCY_KEY) ) @@ -74,15 +74,15 @@ public class VariantContextUtils { } if ( vc.hasGenotypes() ) { - attributes.put(VCFConstants.ALLELE_NUMBER_KEY, vc.getChromosomeCount()); + attributes.put(VCFConstants.ALLELE_NUMBER_KEY, vc.getCalledChrCount()); // if there are alternate alleles, record the relevant tags if ( vc.getAlternateAlleles().size() > 0 ) { ArrayList alleleFreqs = new ArrayList(); ArrayList alleleCounts = new ArrayList(); - double totalChromosomes = (double)vc.getChromosomeCount(); + double totalChromosomes = (double)vc.getCalledChrCount(); for ( Allele allele : vc.getAlternateAlleles() ) { - int altChromosomes = vc.getChromosomeCount(allele); + int altChromosomes = vc.getCalledChrCount(allele); alleleCounts.add(altChromosomes); String freq = String.format(makePrecisionFormatStringFromDenominatorValue(totalChromosomes), ((double)altChromosomes / totalChromosomes)); alleleFreqs.add(freq); @@ -320,7 +320,7 @@ public class VariantContextUtils { } public static double computeHardyWeinbergPvalue(VariantContext vc) { - if ( vc.getChromosomeCount() == 0 ) + if ( vc.getCalledChrCount() == 0 ) return 0.0; return HardyWeinbergCalculation.hwCalculate(vc.getHomRefCount(), vc.getHetCount(), vc.getHomVarCount()); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 38c4f84ab..200f3859b 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -12,7 +12,6 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.Assert; -import java.lang.reflect.Array; import java.util.*; @@ -260,12 +259,12 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertTrue(vc.isSNP()); Assert.assertEquals(vc.getNAlleles(), 2); Assert.assertTrue(vc.hasGenotypes()); - Assert.assertFalse(vc.isMonomorphic()); - Assert.assertTrue(vc.isPolymorphic()); + Assert.assertFalse(vc.isMonomorphicInSamples()); + Assert.assertTrue(vc.isPolymorphicInSamples()); Assert.assertEquals(vc.getGenotype("foo"), g); - Assert.assertEquals(vc.getChromosomeCount(), 1); // we only have 1 called chromosomes, we exclude the NO_CALL one isn't called - Assert.assertEquals(vc.getChromosomeCount(Aref), 0); - Assert.assertEquals(vc.getChromosomeCount(C), 1); + Assert.assertEquals(vc.getCalledChrCount(), 1); // we only have 1 called chromosomes, we exclude the NO_CALL one isn't called + Assert.assertEquals(vc.getCalledChrCount(Aref), 0); + Assert.assertEquals(vc.getCalledChrCount(C), 1); Assert.assertFalse(vc.getGenotype("foo").isHet()); Assert.assertFalse(vc.getGenotype("foo").isHom()); Assert.assertFalse(vc.getGenotype("foo").isNoCall()); @@ -332,8 +331,8 @@ public class VariantContextUnitTest extends BaseTest { .genotypes(g1, g2, g3).make(); Assert.assertTrue(vc.hasGenotypes()); - Assert.assertFalse(vc.isMonomorphic()); - Assert.assertTrue(vc.isPolymorphic()); + Assert.assertFalse(vc.isMonomorphicInSamples()); + Assert.assertTrue(vc.isPolymorphicInSamples()); Assert.assertEquals(vc.getSampleNames().size(), 3); Assert.assertEquals(vc.getGenotypes().size(), 3); @@ -352,9 +351,9 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertFalse(vc.hasGenotype("at")); Assert.assertFalse(vc.hasGenotype("tt")); - Assert.assertEquals(vc.getChromosomeCount(), 6); - Assert.assertEquals(vc.getChromosomeCount(Aref), 3); - Assert.assertEquals(vc.getChromosomeCount(T), 3); + Assert.assertEquals(vc.getCalledChrCount(), 6); + Assert.assertEquals(vc.getCalledChrCount(Aref), 3); + Assert.assertEquals(vc.getCalledChrCount(T), 3); } @Test @@ -372,17 +371,17 @@ public class VariantContextUnitTest extends BaseTest { .genotypes(g1, g2, g3, g4, g5, g6).make(); Assert.assertTrue(vc.hasGenotypes()); - Assert.assertFalse(vc.isMonomorphic()); - Assert.assertTrue(vc.isPolymorphic()); + Assert.assertFalse(vc.isMonomorphicInSamples()); + Assert.assertTrue(vc.isPolymorphicInSamples()); Assert.assertEquals(vc.getGenotypes().size(), 6); Assert.assertEquals(3, vc.getGenotypes(Arrays.asList("AA", "Td", "dd")).size()); - Assert.assertEquals(10, vc.getChromosomeCount()); - Assert.assertEquals(3, vc.getChromosomeCount(Aref)); - Assert.assertEquals(4, vc.getChromosomeCount(T)); - Assert.assertEquals(3, vc.getChromosomeCount(del)); - Assert.assertEquals(2, vc.getChromosomeCount(Allele.NO_CALL)); + Assert.assertEquals(10, vc.getCalledChrCount()); + Assert.assertEquals(3, vc.getCalledChrCount(Aref)); + Assert.assertEquals(4, vc.getCalledChrCount(T)); + Assert.assertEquals(3, vc.getCalledChrCount(del)); + Assert.assertEquals(2, vc.getCalledChrCount(Allele.NO_CALL)); } @Test @@ -398,14 +397,14 @@ public class VariantContextUnitTest extends BaseTest { .genotypes(g1, g2, g3).make(); Assert.assertTrue(vc.hasGenotypes()); - Assert.assertTrue(vc.isMonomorphic()); - Assert.assertFalse(vc.isPolymorphic()); + Assert.assertTrue(vc.isMonomorphicInSamples()); + Assert.assertFalse(vc.isPolymorphicInSamples()); Assert.assertEquals(vc.getGenotypes().size(), 3); - Assert.assertEquals(4, vc.getChromosomeCount()); - Assert.assertEquals(4, vc.getChromosomeCount(Aref)); - Assert.assertEquals(0, vc.getChromosomeCount(T)); - Assert.assertEquals(2, vc.getChromosomeCount(Allele.NO_CALL)); + Assert.assertEquals(4, vc.getCalledChrCount()); + Assert.assertEquals(4, vc.getCalledChrCount(Aref)); + Assert.assertEquals(0, vc.getCalledChrCount(T)); + Assert.assertEquals(2, vc.getCalledChrCount(Allele.NO_CALL)); } } @@ -452,12 +451,12 @@ public class VariantContextUnitTest extends BaseTest { VariantContext vc14 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g4.getSampleName()))); VariantContext vc5 = vc.subContextFromSamples(new HashSet(Arrays.asList(g5.getSampleName()))); - Assert.assertTrue(vc12.isPolymorphic()); - Assert.assertTrue(vc23.isPolymorphic()); - Assert.assertTrue(vc1.isMonomorphic()); - Assert.assertTrue(vc4.isMonomorphic()); - Assert.assertTrue(vc14.isMonomorphic()); - Assert.assertTrue(vc5.isPolymorphic()); + Assert.assertTrue(vc12.isPolymorphicInSamples()); + Assert.assertTrue(vc23.isPolymorphicInSamples()); + Assert.assertTrue(vc1.isMonomorphicInSamples()); + Assert.assertTrue(vc4.isMonomorphicInSamples()); + Assert.assertTrue(vc14.isMonomorphicInSamples()); + Assert.assertTrue(vc5.isPolymorphicInSamples()); Assert.assertTrue(vc12.isSNP()); Assert.assertTrue(vc12.isVariant()); @@ -484,12 +483,12 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertTrue(vc5.isVariant()); Assert.assertTrue(vc5.isBiallelic()); - Assert.assertEquals(3, vc12.getChromosomeCount(Aref)); - Assert.assertEquals(1, vc23.getChromosomeCount(Aref)); - Assert.assertEquals(2, vc1.getChromosomeCount(Aref)); - Assert.assertEquals(0, vc4.getChromosomeCount(Aref)); - Assert.assertEquals(2, vc14.getChromosomeCount(Aref)); - Assert.assertEquals(0, vc5.getChromosomeCount(Aref)); + Assert.assertEquals(3, vc12.getCalledChrCount(Aref)); + Assert.assertEquals(1, vc23.getCalledChrCount(Aref)); + Assert.assertEquals(2, vc1.getCalledChrCount(Aref)); + Assert.assertEquals(0, vc4.getCalledChrCount(Aref)); + Assert.assertEquals(2, vc14.getCalledChrCount(Aref)); + Assert.assertEquals(0, vc5.getCalledChrCount(Aref)); } public void testGetGenotypeMethods() { @@ -827,14 +826,14 @@ public class VariantContextUnitTest extends BaseTest { VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); Assert.assertEquals(vc.getNSamples(), nSamples); if ( nSamples > 0 ) { - Assert.assertEquals(vc.isPolymorphic(), nT > 0); - Assert.assertEquals(vc.isMonomorphic(), nT == 0); + Assert.assertEquals(vc.isPolymorphicInSamples(), nT > 0); + Assert.assertEquals(vc.isMonomorphicInSamples(), nT == 0); } - Assert.assertEquals(vc.getChromosomeCount(), nA + nT); + Assert.assertEquals(vc.getCalledChrCount(), nA + nT); - Assert.assertEquals(vc.getChromosomeCount(Allele.NO_CALL), nNoCallAlleles); - Assert.assertEquals(vc.getChromosomeCount(Aref), nA); - Assert.assertEquals(vc.getChromosomeCount(T), nT); + Assert.assertEquals(vc.getCalledChrCount(Allele.NO_CALL), nNoCallAlleles); + Assert.assertEquals(vc.getCalledChrCount(Aref), nA); + Assert.assertEquals(vc.getCalledChrCount(T), nT); Assert.assertEquals(vc.getNoCallCount(), nNoCall); Assert.assertEquals(vc.getHomRefCount(), nHomRef); From 660d6009a20c1ff8f187bd4e8ed4586082616ec6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 13:59:30 -0500 Subject: [PATCH 059/113] Documentation and contracts for GenotypesContext and VariantContextBuilder --- .../utils/codecs/vcf/AbstractVCFCodec.java | 1 + .../variantcontext/GenotypesContext.java | 218 +++++++++++++++--- .../variantcontext/VariantContextBuilder.java | 150 +++++++++++- 3 files changed, 332 insertions(+), 37 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index e6e4aa8ce..0f21e1505 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -296,6 +296,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, if (parts.length > NUM_STANDARD_FIELDS) { builder.attribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, new String(parts[8])); builder.attribute(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY, this); + builder.genotypesAreUnparsed(); } VariantContext vc = null; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 671066d24..c1fcd6226 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -24,19 +24,41 @@ package org.broadinstitute.sting.utils.variantcontext; +import com.google.java.contract.Ensures; +import com.google.java.contract.Invariant; +import com.google.java.contract.Requires; + import java.util.*; /** - * + * Represents an ordered collection of Genotype objects */ public class GenotypesContext implements List { + /** + * static constant value for an empty GenotypesContext. Useful since so many VariantContexts have no genotypes + */ public final static GenotypesContext NO_GENOTYPES = new GenotypesContext(new ArrayList(0), new HashMap(0), Collections.emptyList(), true); + /** + *sampleNamesInOrder a list of sample names, one for each genotype in genotypes, sorted in alphabetical order + */ List sampleNamesInOrder = null; + + /** + * a map optimized for efficient lookup. Each genotype in genotypes must have its + * sample name in sampleNameToOffset, with a corresponding integer value that indicates the offset of that + * genotype in the vector of genotypes + */ Map sampleNameToOffset = null; + + /** if true, then we need to reinitialize sampleNamesInOrder and sampleNameToOffset before we use them /*/ boolean cacheIsInvalid = true; + + /** An ArrayList of genotypes contained in this context */ List genotypes; + + /** Are we allowing users to modify the list? */ boolean immutable = false; // --------------------------------------------------------------------------- @@ -45,14 +67,25 @@ public class GenotypesContext implements List { // // --------------------------------------------------------------------------- + /** + * Create an empty GenotypeContext + */ private GenotypesContext() { this(10, false); } + /** + * Create an empty GenotypeContext, with initial capacity for n elements + */ + @Requires("n >= 0") private GenotypesContext(final int n, final boolean immutable) { this(new ArrayList(n), immutable); } + /** + * Create an GenotypeContext containing genotypes + */ + @Requires("genotypes != null") private GenotypesContext(final ArrayList genotypes, final boolean immutable) { this.genotypes = genotypes; this.immutable = immutable; @@ -60,6 +93,23 @@ public class GenotypesContext implements List { this.cacheIsInvalid = true; } + /** + * Create a fully resolved GenotypeContext containing genotypes, sample lookup table, + * and sorted sample names + * + * @param genotypes our genotypes in arbitrary + * @param sampleNameToOffset map optimized for efficient lookup. Each genotype in genotypes must have its + * sample name in sampleNameToOffset, with a corresponding integer value that indicates the offset of that + * genotype in the vector of genotypes + * @param sampleNamesInOrder a list of sample names, one for each genotype in genotypes, sorted in alphabetical + * order. + * @param immutable + */ + @Requires({"genotypes != null", + "sampleNameToOffset != null", + "sampleNamesInOrder != null", + "genotypes.size() == sampleNameToOffset.size()", + "genotypes.size() == sampleNamesInOrder.size()"}) private GenotypesContext(final ArrayList genotypes, final Map sampleNameToOffset, final List sampleNamesInOrder, @@ -77,54 +127,98 @@ public class GenotypesContext implements List { // // --------------------------------------------------------------------------- + /** + * Basic creation routine + * @return an empty, mutable GenotypeContext + */ + @Ensures({"result != null"}) public static final GenotypesContext create() { return new GenotypesContext(); } + /** + * Basic creation routine + * @return an empty, mutable GenotypeContext with initial capacity for nGenotypes + */ + @Requires("nGenotypes >= 0") + @Ensures({"result != null"}) public static final GenotypesContext create(final int nGenotypes) { return new GenotypesContext(nGenotypes, false); } + /** + * Create a fully resolved GenotypeContext containing genotypes, sample lookup table, + * and sorted sample names + * + * @param genotypes our genotypes in arbitrary + * @param sampleNameToOffset map optimized for efficient lookup. Each genotype in genotypes must have its + * sample name in sampleNameToOffset, with a corresponding integer value that indicates the offset of that + * genotype in the vector of genotypes + * @param sampleNamesInOrder a list of sample names, one for each genotype in genotypes, sorted in alphabetical + * order. + * @return an mutable GenotypeContext containing genotypes with already present lookup data + */ + @Requires({"genotypes != null", + "sampleNameToOffset != null", + "sampleNamesInOrder != null", + "sameSamples(genotypes, sampleNamesInOrder)", + "sameSamples(genotypes, sampleNameToOffset.keySet())"}) + @Ensures({"result != null"}) public static final GenotypesContext create(final ArrayList genotypes, final Map sampleNameToOffset, final List sampleNamesInOrder) { return new GenotypesContext(genotypes, sampleNameToOffset, sampleNamesInOrder, false); } + /** + * Create a fully resolved GenotypeContext containing genotypes + * + * @param genotypes our genotypes in arbitrary + * @return an mutable GenotypeContext containing genotypes + */ + @Requires({"genotypes != null"}) + @Ensures({"result != null"}) public static final GenotypesContext create(final ArrayList genotypes) { return genotypes == null ? NO_GENOTYPES : new GenotypesContext(genotypes, false); } + /** + * Create a fully resolved GenotypeContext containing genotypes + * + * @param genotypes our genotypes in arbitrary + * @return an mutable GenotypeContext containing genotypes + */ + @Requires({"genotypes != null"}) + @Ensures({"result != null"}) public static final GenotypesContext create(final Genotype... genotypes) { return new GenotypesContext(new ArrayList(Arrays.asList(genotypes)), false); } + /** + * Create a freshly allocated GenotypeContext containing the genotypes in toCopy + * + * @param toCopy the GenotypesContext to copy + * @return an mutable GenotypeContext containing genotypes + */ + @Requires({"toCopy != null"}) + @Ensures({"result != null"}) public static final GenotypesContext copy(final GenotypesContext toCopy) { return create(new ArrayList(toCopy.genotypes)); } + /** + * Create a GenotypesContext containing the genotypes in iteration order contained + * in toCopy + * + * @param toCopy the collection of genotypes + * @return an mutable GenotypeContext containing genotypes + */ + @Requires({"toCopy != null"}) + @Ensures({"result != null"}) public static final GenotypesContext copy(final Collection toCopy) { return toCopy == null ? NO_GENOTYPES : create(new ArrayList(toCopy)); } - - -// public static final GenotypeMap create(final Collection genotypes) { -// if ( genotypes == null ) -// return null; // todo -- really should return an empty map -// else { -// GenotypeMap genotypeMap = new GenotypeMap(genotypes.size(), false); -// for ( final Genotype g : genotypes ) { -// if ( genotypeMap.containsKey(g.getSampleName() ) ) -// throw new IllegalArgumentException("Duplicate genotype added to VariantContext: " + g); -// genotypeMap.put(g.getSampleName(), g); -// } -// -// //return genotypeMap.immutable(); // todo enable when we have time to dive into mutability issue -// return genotypeMap; -// } -// } - // --------------------------------------------------------------------------- // // Mutability methods @@ -152,23 +246,31 @@ public class GenotypesContext implements List { // // --------------------------------------------------------------------------- + @Ensures({"cacheIsInvalid = true"}) private void invalidateCaches() { cacheIsInvalid = true; sampleNamesInOrder = null; sampleNameToOffset = null; } + @Ensures({"cacheIsInvalid = false", + "sampleNamesInOrder != null", + "sampleNameToOffset != null", + "sameSamples(genotypes, sampleNamesInOrder)", + "sameSamples(genotypes, sampleNameToOffset.keySet())"}) private void buildCache() { - cacheIsInvalid = false; - sampleNamesInOrder = new ArrayList(genotypes.size()); - sampleNameToOffset = new HashMap(genotypes.size()); + if ( cacheIsInvalid ) { + cacheIsInvalid = false; + sampleNamesInOrder = new ArrayList(genotypes.size()); + sampleNameToOffset = new HashMap(genotypes.size()); - for ( int i = 0; i < genotypes.size(); i++ ) { - final Genotype g = genotypes.get(i); - sampleNamesInOrder.add(g.getSampleName()); - sampleNameToOffset.put(g.getSampleName(), i); + for ( int i = 0; i < genotypes.size(); i++ ) { + final Genotype g = genotypes.get(i); + sampleNamesInOrder.add(g.getSampleName()); + sampleNameToOffset.put(g.getSampleName(), i); + } + Collections.sort(sampleNamesInOrder); } - Collections.sort(sampleNamesInOrder); } @@ -195,12 +297,14 @@ public class GenotypesContext implements List { } @Override + @Requires("genotype != null") public boolean add(final Genotype genotype) { checkImmutability(); invalidateCaches(); return genotypes.add(genotype); } + @Requires("genotype != null") public boolean add(final Genotype ... genotype) { checkImmutability(); invalidateCaches(); @@ -263,13 +367,15 @@ public class GenotypesContext implements List { @Override public ListIterator listIterator() { // todo -- must be immutable - return genotypes.listIterator(); + throw new UnsupportedOperationException(); +// return genotypes.listIterator(); } @Override public ListIterator listIterator(final int i) { // todo -- must be immutable - return genotypes.listIterator(i); + throw new UnsupportedOperationException(); +// return genotypes.listIterator(i); } @Override @@ -322,6 +428,14 @@ public class GenotypesContext implements List { return genotypes.toArray(ts); } + /** + * Iterate over the Genotypes in this context in the order specified by sampleNamesInOrder + * + * @param sampleNamesInOrder a Iterable of String, containing exactly one entry for each Genotype sample name in + * this context + * @return a Iterable over the genotypes in this context. + */ + @Requires("sampleNamesInOrder != null") public Iterable iterateInSampleNameOrder(final Iterable sampleNamesInOrder) { return new Iterable() { @Override @@ -331,6 +445,11 @@ public class GenotypesContext implements List { }; } + /** + * Iterate over the Genotypes in this context in their sample name order (A, B, C) + * regardless of the underlying order in the vector of genotypes + * @return a Iterable over the genotypes in this context. + */ public Iterable iterateInSampleNameOrder() { return iterateInSampleNameOrder(getSampleNamesOrderedByName()); } @@ -358,30 +477,57 @@ public class GenotypesContext implements List { } } + /** + * @return The set of sample names for all genotypes in this context, in arbitrary order + */ + @Ensures("result != null") public Set getSampleNames() { buildCache(); return sampleNameToOffset.keySet(); } + /** + * @return The set of sample names for all genotypes in this context, in their natural ordering (A, B, C) + */ + @Ensures("result != null") public List getSampleNamesOrderedByName() { buildCache(); return sampleNamesInOrder; } + @Requires("sample != null") public boolean containsSample(final String sample) { buildCache(); return sampleNameToOffset.containsKey(sample); } + @Requires("samples != null") public boolean containsSamples(final Collection samples) { buildCache(); return getSampleNames().containsAll(samples); } + /** + * Return a freshly allocated subcontext of this context containing only the samples + * listed in samples. Note that samples can contain names not in this context, they + * will just be ignored. + * + * @param samples + * @return + */ + @Requires("samples != null") + @Ensures("result != null") public GenotypesContext subsetToSamples( final Collection samples ) { return subsetToSamples(new HashSet(samples)); } + /** + * {@link #subsetToSamples(java.util.Collection)} + * @param samples + * @return + */ + @Requires("samples != null") + @Ensures("result != null") public GenotypesContext subsetToSamples( final Set samples ) { if ( samples.size() == genotypes.size() ) return this; @@ -426,4 +572,18 @@ public class GenotypesContext implements List { } } } + + private final static boolean sameSamples(List genotypes, Collection sampleNamesInOrder) { + Set names = new HashSet(sampleNamesInOrder); + if ( names.size() != sampleNamesInOrder.size() ) + return false; + if ( genotypes.size() != names.size() ) + return false; + + for ( final Genotype g : genotypes ) + if ( ! names.contains(g.getSampleName()) ) + return false; + + return true; + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java index fb92f60a2..67077e8c3 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java @@ -37,6 +37,25 @@ import java.util.*; /** * Builder class for VariantContext * + * Some basic assumptions here: + * + * 1 -- data isn't protectively copied. If you provide an attribute map to + * the build, and modify it later, the builder will see this and so will any + * resulting variant contexts. It's best not to modify collections provided + * to a builder. + * + * 2 -- the system uses the standard builder model, allowing the simple construction idiom: + * + * builder.source("a").genotypes(gc).id("x").make() => VariantContext + * + * 3 -- The best way to copy a VariantContext is: + * + * new VariantContextBuilder(vc).make() => a copy of VC + * + * 4 -- validation of arguments is done at the during the final make() call, so a + * VariantContextBuilder can exist in an inconsistent state as long as those issues + * are resolved before the call to make() is issued. + * * @author depristo */ public class VariantContextBuilder { @@ -60,10 +79,19 @@ public class VariantContextBuilder { /** enum of what must be validated */ final private EnumSet toValidate = EnumSet.noneOf(VariantContext.Validation.class); - public VariantContextBuilder() { - - } + /** + * Create an empty VariantContextBuilder where all values adopt their default values. Note that + * source, chr, start, stop, and alleles must eventually be filled in, or the resulting VariantContext + * will throw an error. + */ + public VariantContextBuilder() {} + /** + * Create an empty VariantContextBuilder where all values adopt their default values, but the bare min. + * of info (source, chr, start, stop, and alleles) have been provided to start. + */ + @Requires({"source != null", "contig != null", "start >= 0", "stop >= 0", + "alleles != null && !alleles.isEmpty()"}) public VariantContextBuilder(String source, String contig, long start, long stop, Collection alleles) { this.source = source; this.contig = contig; @@ -95,6 +123,12 @@ public class VariantContextBuilder { this.stop = parent.getEnd(); } + /** + * Tells this builder to use this collection of alleles for the resulting VariantContext + * + * @param alleles + * @return this builder + */ @Requires({"alleles != null", "!alleles.isEmpty()"}) public VariantContextBuilder alleles(final Collection alleles) { this.alleles = alleles; @@ -103,6 +137,8 @@ public class VariantContextBuilder { } /** + * Tells this builder to use this map of attributes alleles for the resulting VariantContext + * * Attributes can be null -> meaning there are no attributes. After * calling this routine the builder assumes it can modify the attributes * object here, if subsequent calls are made to set attribute values @@ -114,6 +150,14 @@ public class VariantContextBuilder { return this; } + /** + * Puts the key -> value mapping into this builder's attributes + * + * @param key + * @param value + * @return + */ + @Requires({"key != null"}) public VariantContextBuilder attribute(final String key, final Object value) { if ( ! attributesCanBeModified ) { this.attributesCanBeModified = true; @@ -124,6 +168,8 @@ public class VariantContextBuilder { } /** + * This builder's filters are set to this value + * * filters can be null -> meaning there are no filters * @param filters */ @@ -132,22 +178,41 @@ public class VariantContextBuilder { return this; } + /** + * {@link #filters} + * + * @param filters + * @return + */ public VariantContextBuilder filters(final String ... filters) { filters(new HashSet(Arrays.asList(filters))); return this; } + /** + * Tells this builder that the resulting VariantContext should have PASS filters + * + * @return + */ public VariantContextBuilder passFilters() { return filters(VariantContext.PASSES_FILTERS); } + /** + * Tells this builder that the resulting VariantContext be unfiltered + * + * @return + */ public VariantContextBuilder unfiltered() { this.filters = null; return this; } /** - * genotypes can be null -> meaning there are no genotypes + * Tells this builder that the resulting VariantContext should use this genotypes GenotypeContext + * + * Note that genotypes can be null -> meaning there are no genotypes + * * @param genotypes */ public VariantContextBuilder genotypes(final GenotypesContext genotypes) { @@ -157,41 +222,74 @@ public class VariantContextBuilder { return this; } + /** + * Tells this builder that the resulting VariantContext should use a GenotypeContext containing genotypes + * + * Note that genotypes can be null -> meaning there are no genotypes + * + * @param genotypes + */ public VariantContextBuilder genotypes(final Collection genotypes) { return genotypes(GenotypesContext.copy(genotypes)); } + /** + * Tells this builder that the resulting VariantContext should use a GenotypeContext containing genotypes + * @param genotypes + */ public VariantContextBuilder genotypes(final Genotype ... genotypes) { return genotypes(GenotypesContext.copy(Arrays.asList(genotypes))); } + /** + * Tells this builder that the resulting VariantContext should not contain any GenotypeContext + */ public VariantContextBuilder noGenotypes() { this.genotypes = null; return this; } - public VariantContextBuilder genotypesAreUnparsed(final boolean genotypesAreUnparsed) { - this.genotypesAreUnparsed = genotypesAreUnparsed; + /** + * ADVANCED! tells us that the genotypes data is stored as an unparsed attribute + * @return + */ + public VariantContextBuilder genotypesAreUnparsed() { + this.genotypesAreUnparsed = true; return this; } + /** + * Tells us that the resulting VariantContext should have ID + * @param ID + * @return + */ @Requires("ID != null") public VariantContextBuilder id(final String ID) { this.ID = ID; return this; } + /** + * Tells us that the resulting VariantContext should not have an ID + * @return + */ public VariantContextBuilder noID() { return id(VCFConstants.EMPTY_ID_FIELD); } - @Requires("negLog10PError <= 0") + /** + * Tells us that the resulting VariantContext should have negLog10PError + * @param negLog10PError + * @return + */ + @Requires("negLog10PError <= 0 || negLog10PError == VariantContext.NO_NEG_LOG_10PERROR") public VariantContextBuilder negLog10PError(final double negLog10PError) { this.negLog10PError = negLog10PError; return this; } /** + * Tells us that the resulting VariantContext should use this byte for the reference base * Null means no refBase is available * @param referenceBaseForIndel */ @@ -201,12 +299,24 @@ public class VariantContextBuilder { return this; } + /** + * Tells us that the resulting VariantContext should have source field set to source + * @param source + * @return + */ @Requires("source != null") public VariantContextBuilder source(final String source) { this.source = source; return this; } + /** + * Tells us that the resulting VariantContext should have the specified location + * @param contig + * @param start + * @param stop + * @return + */ @Requires({"contig != null", "start >= 0", "stop >= 0"}) public VariantContextBuilder loc(final String contig, final long start, final long stop) { this.contig = contig; @@ -217,12 +327,22 @@ public class VariantContextBuilder { return this; } - @Requires({"contig != null", "start >= 0", "stop >= 0"}) + /** + * Tells us that the resulting VariantContext should have the specified contig chr + * @param contig + * @return + */ + @Requires({"contig != null"}) public VariantContextBuilder chr(final String contig) { this.contig = contig; return this; } + /** + * Tells us that the resulting VariantContext should have the specified contig start + * @param start + * @return + */ @Requires({"start >= 0"}) public VariantContextBuilder start(final long start) { this.start = start; @@ -231,12 +351,26 @@ public class VariantContextBuilder { return this; } + /** + * Tells us that the resulting VariantContext should have the specified contig stop + * @param stop + * @return + */ @Requires({"stop >= 0"}) public VariantContextBuilder stop(final long stop) { this.stop = stop; return this; } + /** + * Takes all of the builder data provided up to this point, and instantiates + * a freshly allocated VariantContext with all of the builder data. This + * VariantContext is validated as appropriate and if not failing QC (and + * throwing an exception) is returned. + * + * Note that this function can be called multiple times to create multiple + * VariantContexts from the same builder. + */ public VariantContext make() { return new VariantContext(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, From a2e79fbe8a24d8d970b8fbf790d395c0b79b03cc Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 14:18:53 -0500 Subject: [PATCH 060/113] Fixes to contracts --- .../sting/utils/variantcontext/GenotypesContext.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index c1fcd6226..a639f512e 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -246,19 +246,19 @@ public class GenotypesContext implements List { // // --------------------------------------------------------------------------- - @Ensures({"cacheIsInvalid = true"}) - private void invalidateCaches() { + @Ensures({"cacheIsInvalid == true"}) + private synchronized void invalidateCaches() { cacheIsInvalid = true; sampleNamesInOrder = null; sampleNameToOffset = null; } - @Ensures({"cacheIsInvalid = false", + @Ensures({"cacheIsInvalid == false", "sampleNamesInOrder != null", "sampleNameToOffset != null", "sameSamples(genotypes, sampleNamesInOrder)", "sameSamples(genotypes, sampleNameToOffset.keySet())"}) - private void buildCache() { + private synchronized void buildCache() { if ( cacheIsInvalid ) { cacheIsInvalid = false; sampleNamesInOrder = new ArrayList(genotypes.size()); From 8bb4d4dca32e3b8521c2d7ba22db5a5e9966cccf Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Tue, 13 Sep 2011 10:49:16 -0400 Subject: [PATCH 061/113] First pass of the asynchronous block loader. Block loads are only triggered on queue empty at this point. Disabled by default (enable with nt:io=?). --- .../src/net/sf/samtools/BAMFileReader.java | 762 ++++++++++++++++++ .../src/net/sf/samtools/GATKBAMFileSpan.java | 13 + .../java/src/net/sf/samtools/GATKChunk.java | 16 + .../net/sf/samtools/util/BAMInputStream.java | 72 ++ .../util/BlockCompressedInputStream.java | 483 +++++++++++ .../sting/commandline/CommandLineProgram.java | 10 +- .../sting/gatk/CommandLineGATK.java | 4 +- .../sting/gatk/GenomeAnalysisEngine.java | 146 ++-- .../arguments/GATKArgumentCollection.java | 22 +- .../reads/BAMBlockStartIterator.java | 128 --- .../datasources/reads/BAMIndexContent.java | 195 ----- .../gatk/datasources/reads/BAMOverlap.java | 29 - .../gatk/datasources/reads/BAMSchedule.java | 18 +- .../gatk/datasources/reads/BAMScheduler.java | 136 +++- .../reads/BGZFBlockLoadingDispatcher.java | 85 ++ .../datasources/reads/BlockInputStream.java | 436 ++++++++++ .../gatk/datasources/reads/BlockLoader.java | 188 +++++ .../datasources/reads/FileHandleCache.java | 231 ++++++ .../gatk/datasources/reads/FilePointer.java | 46 +- .../datasources/reads/IntervalSharder.java | 445 +--------- .../reads/LocusShardBalancer.java} | 40 +- .../datasources/reads/LocusShardStrategy.java | 178 ---- .../reads/LowMemoryIntervalSharder.java | 68 -- .../datasources/reads/MonolithicShard.java | 34 - .../reads/MonolithicShardStrategy.java | 77 -- .../gatk/datasources/reads/ReadShard.java | 9 +- .../datasources/reads/ReadShardBalancer.java | 115 +++ .../datasources/reads/ReadShardStrategy.java | 183 ----- .../gatk/datasources/reads/ReaderBin.java | 33 - .../gatk/datasources/reads/SAMDataSource.java | 257 +++--- .../datasources/reads/SAMReaderPosition.java | 120 +++ .../gatk/datasources/reads/ShardBalancer.java | 21 + .../gatk/datasources/reads/ShardStrategy.java | 31 - .../reads/ShardStrategyFactory.java | 117 --- .../reads/utilities/FindLargeShards.java | 10 +- .../reference/ReferenceDataSource.java | 64 +- .../executive/HierarchicalMicroScheduler.java | 3 +- .../gatk/executive/LinearMicroScheduler.java | 3 +- .../sting/gatk/executive/MicroScheduler.java | 16 +- .../resourcemanagement/ThreadAllocation.java | 93 +++ .../providers/LocusViewTemplate.java | 3 +- .../datasources/reads/MockLocusShard.java | 3 +- .../reads/SAMBAMDataSourceUnitTest.java | 223 ----- .../reads/SAMDataSourceUnitTest.java | 147 ++++ .../traversals/TraverseReadsUnitTest.java | 16 +- 45 files changed, 3292 insertions(+), 2037 deletions(-) create mode 100644 public/java/src/net/sf/samtools/BAMFileReader.java create mode 100644 public/java/src/net/sf/samtools/util/BAMInputStream.java create mode 100755 public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java rename public/java/src/{net/sf/samtools/GATKBinList.java => org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java} (52%) delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java delete mode 100755 public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java create mode 100755 public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java diff --git a/public/java/src/net/sf/samtools/BAMFileReader.java b/public/java/src/net/sf/samtools/BAMFileReader.java new file mode 100644 index 000000000..5005b6265 --- /dev/null +++ b/public/java/src/net/sf/samtools/BAMFileReader.java @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package net.sf.samtools; + + +import net.sf.samtools.util.*; +import net.sf.samtools.SAMFileReader.ValidationStringency; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Internal class for reading and querying BAM files. + */ +class BAMFileReader extends SAMFileReader.ReaderImplementation { + // True if reading from a File rather than an InputStream + private boolean mIsSeekable = false; + + // For converting bytes into other primitive types + private BinaryCodec mStream = null; + + // Underlying compressed data stream. + private final BAMInputStream mInputStream; + private SAMFileHeader mFileHeader = null; + + // Populated if the file is seekable and an index exists + private File mIndexFile; + private BAMIndex mIndex = null; + private long mFirstRecordPointer = 0; + private CloseableIterator mCurrentIterator = null; + + // If true, all SAMRecords are fully decoded as they are read. + private final boolean eagerDecode; + + // For error-checking. + private ValidationStringency mValidationStringency; + + // For creating BAMRecords + private SAMRecordFactory samRecordFactory; + + /** + * Use the caching index reader implementation rather than the disk-hit-per-file model. + */ + private boolean mEnableIndexCaching = false; + + /** + * Use the traditional memory-mapped implementation for BAM file indexes rather than regular I/O. + */ + private boolean mEnableIndexMemoryMapping = true; + + /** + * Add information about the origin (reader and position) to SAM records. + */ + private SAMFileReader mFileReader = null; + + /** + * Prepare to read BAM from a stream (not seekable) + * @param stream source of bytes. + * @param eagerDecode if true, decode all BAM fields as reading rather than lazily. + * @param validationStringency Controls how to handle invalidate reads or header lines. + */ + BAMFileReader(final InputStream stream, + final File indexFile, + final boolean eagerDecode, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + mIndexFile = indexFile; + mIsSeekable = false; + mInputStream = stream instanceof BAMInputStream ? (BAMInputStream)stream : new BlockCompressedInputStream(stream); + mStream = new BinaryCodec(new DataInputStream((InputStream)mInputStream)); + this.eagerDecode = eagerDecode; + this.mValidationStringency = validationStringency; + this.samRecordFactory = factory; + readHeader(null); + } + + /** + * Prepare to read BAM from a file (seekable) + * @param file source of bytes. + * @param eagerDecode if true, decode all BAM fields as reading rather than lazily. + * @param validationStringency Controls how to handle invalidate reads or header lines. + */ + BAMFileReader(final File file, + final File indexFile, + final boolean eagerDecode, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + this(new BlockCompressedInputStream(file), indexFile!=null ? indexFile : findIndexFile(file), eagerDecode, file.getAbsolutePath(), validationStringency, factory); + if (mIndexFile != null && mIndexFile.lastModified() < file.lastModified()) { + System.err.println("WARNING: BAM index file " + mIndexFile.getAbsolutePath() + + " is older than BAM " + file.getAbsolutePath()); + } + } + + BAMFileReader(final SeekableStream strm, + final File indexFile, + final boolean eagerDecode, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + this(strm instanceof BAMInputStream ? (BAMInputStream)strm : new BlockCompressedInputStream(strm), + indexFile, + eagerDecode, + strm.getSource(), + validationStringency, + factory); + } + + private BAMFileReader(final BAMInputStream inputStream, + final File indexFile, + final boolean eagerDecode, + final String source, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + mIndexFile = indexFile; + mIsSeekable = true; + mInputStream = inputStream; + mStream = new BinaryCodec(new DataInputStream((InputStream)inputStream)); + this.eagerDecode = eagerDecode; + this.mValidationStringency = validationStringency; + this.samRecordFactory = factory; + readHeader(source); + mFirstRecordPointer = inputStream.getFilePointer(); + } + + /** + * If true, writes the source of every read into the source SAMRecords. + * @param enabled true to write source information into each SAMRecord. + */ + void enableFileSource(final SAMFileReader reader, final boolean enabled) { + this.mFileReader = enabled ? reader : null; + } + + /** + * If true, uses the caching version of the index reader. + * @param enabled true to write source information into each SAMRecord. + */ + public void enableIndexCaching(final boolean enabled) { + if(mIndex != null) + throw new SAMException("Unable to turn on index caching; index file has already been loaded."); + this.mEnableIndexCaching = enabled; + } + + /** + * If false, disable the use of memory mapping for accessing index files (default behavior is to use memory mapping). + * This is slower but more scalable when accessing large numbers of BAM files sequentially. + * @param enabled True to use memory mapping, false to use regular I/O. + */ + public void enableIndexMemoryMapping(final boolean enabled) { + if (mIndex != null) { + throw new SAMException("Unable to change index memory mapping; index file has already been loaded."); + } + this.mEnableIndexMemoryMapping = enabled; + } + + @Override void enableCrcChecking(final boolean enabled) { + this.mInputStream.setCheckCrcs(enabled); + } + + @Override void setSAMRecordFactory(final SAMRecordFactory factory) { this.samRecordFactory = factory; } + + /** + * @return true if ths is a BAM file, and has an index + */ + public boolean hasIndex() { + return (mIndexFile != null); + } + + /** + * Retrieves the index for the given file type. Ensure that the index is of the specified type. + * @return An index of the given type. + */ + public BAMIndex getIndex() { + if(mIndexFile == null) + throw new SAMException("No index is available for this BAM file."); + if(mIndex == null) + mIndex = mEnableIndexCaching ? new CachingBAMFileIndex(mIndexFile, getFileHeader().getSequenceDictionary(), mEnableIndexMemoryMapping) + : new DiskBasedBAMFileIndex(mIndexFile, getFileHeader().getSequenceDictionary(), mEnableIndexMemoryMapping); + return mIndex; + } + + void close() { + if (mStream != null) { + mStream.close(); + } + if (mIndex != null) { + mIndex.close(); + } + mStream = null; + mFileHeader = null; + mIndex = null; + } + + SAMFileHeader getFileHeader() { + return mFileHeader; + } + + /** + * Set error-checking level for subsequent SAMRecord reads. + */ + void setValidationStringency(final SAMFileReader.ValidationStringency validationStringency) { + this.mValidationStringency = validationStringency; + } + + SAMFileReader.ValidationStringency getValidationStringency() { + return this.mValidationStringency; + } + + /** + * Prepare to iterate through the SAMRecords in file order. + * Only a single iterator on a BAM file can be extant at a time. If getIterator() or a query method has been called once, + * that iterator must be closed before getIterator() can be called again. + * A somewhat peculiar aspect of this method is that if the file is not seekable, a second call to + * getIterator() begins its iteration where the last one left off. That is the best that can be + * done in that situation. + */ + CloseableIterator getIterator() { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (mIsSeekable) { + try { + mInputStream.seek(mFirstRecordPointer); + } catch (IOException exc) { + throw new RuntimeException(exc.getMessage(), exc); + } + } + mCurrentIterator = new BAMFileIterator(); + return mCurrentIterator; + } + + @Override + CloseableIterator getIterator(final SAMFileSpan chunks) { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!(chunks instanceof BAMFileSpan)) { + throw new IllegalStateException("BAMFileReader cannot handle this type of file span."); + } + + // Create an iterator over the given chunk boundaries. + mCurrentIterator = new BAMFileIndexIterator(((BAMFileSpan)chunks).toCoordinateArray()); + return mCurrentIterator; + } + + /** + * Gets an unbounded pointer to the first record in the BAM file. Because the reader doesn't necessarily know + * when the file ends, the rightmost bound of the file pointer will not end exactly where the file ends. However, + * the rightmost bound is guaranteed to be after the last read in the file. + * @return An unbounded pointer to the first record in the BAM file. + */ + @Override + SAMFileSpan getFilePointerSpanningReads() { + return new BAMFileSpan(new Chunk(mFirstRecordPointer,Long.MAX_VALUE)); + } + + /** + * Prepare to iterate through the SAMRecords that match the given interval. + * Only a single iterator on a BAMFile can be extant at a time. The previous one must be closed + * before calling any of the methods that return an iterator. + * + * Note that an unmapped SAMRecord may still have a reference name and an alignment start for sorting + * purposes (typically this is the coordinate of its mate), and will be found by this method if the coordinate + * matches the specified interval. + * + * Note that this method is not necessarily efficient in terms of disk I/O. The index does not have perfect + * resolution, so some SAMRecords may be read and then discarded because they do not match the specified interval. + * + * @param sequence Reference sequence sought. + * @param start Desired SAMRecords must overlap or be contained in the interval specified by start and end. + * A value of zero implies the start of the reference sequence. + * @param end A value of zero implies the end of the reference sequence. + * @param contained If true, the alignments for the SAMRecords must be completely contained in the interval + * specified by start and end. If false, the SAMRecords need only overlap the interval. + * @return Iterator for the matching SAMRecords + */ + CloseableIterator query(final String sequence, final int start, final int end, final boolean contained) { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!mIsSeekable) { + throw new UnsupportedOperationException("Cannot query stream-based BAM file"); + } + mCurrentIterator = createIndexIterator(sequence, start, end, contained? QueryType.CONTAINED: QueryType.OVERLAPPING); + return mCurrentIterator; + } + + /** + * Prepare to iterate through the SAMRecords with the given alignment start. + * Only a single iterator on a BAMFile can be extant at a time. The previous one must be closed + * before calling any of the methods that return an iterator. + * + * Note that an unmapped SAMRecord may still have a reference name and an alignment start for sorting + * purposes (typically this is the coordinate of its mate), and will be found by this method if the coordinate + * matches the specified interval. + * + * Note that this method is not necessarily efficient in terms of disk I/O. The index does not have perfect + * resolution, so some SAMRecords may be read and then discarded because they do not match the specified interval. + * + * @param sequence Reference sequence sought. + * @param start Alignment start sought. + * @return Iterator for the matching SAMRecords. + */ + CloseableIterator queryAlignmentStart(final String sequence, final int start) { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!mIsSeekable) { + throw new UnsupportedOperationException("Cannot query stream-based BAM file"); + } + mCurrentIterator = createIndexIterator(sequence, start, -1, QueryType.STARTING_AT); + return mCurrentIterator; + } + + public CloseableIterator queryUnmapped() { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!mIsSeekable) { + throw new UnsupportedOperationException("Cannot query stream-based BAM file"); + } + try { + final long startOfLastLinearBin = getIndex().getStartOfLastLinearBin(); + if (startOfLastLinearBin != -1) { + mInputStream.seek(startOfLastLinearBin); + } else { + // No mapped reads in file, just start at the first read in file. + mInputStream.seek(mFirstRecordPointer); + } + mCurrentIterator = new BAMFileIndexUnmappedIterator(); + return mCurrentIterator; + } catch (IOException e) { + throw new RuntimeException("IOException seeking to unmapped reads", e); + } + } + + /** + * Reads the header from the file or stream + * @param source Note that this is used only for reporting errors. + */ + private void readHeader(final String source) + throws IOException { + + final byte[] buffer = new byte[4]; + mStream.readBytes(buffer); + if (!Arrays.equals(buffer, BAMFileConstants.BAM_MAGIC)) { + throw new IOException("Invalid BAM file header"); + } + + final int headerTextLength = mStream.readInt(); + final String textHeader = mStream.readString(headerTextLength); + final SAMTextHeaderCodec headerCodec = new SAMTextHeaderCodec(); + headerCodec.setValidationStringency(mValidationStringency); + mFileHeader = headerCodec.decode(new StringLineReader(textHeader), + source); + + final int sequenceCount = mStream.readInt(); + if (mFileHeader.getSequenceDictionary().size() > 0) { + // It is allowed to have binary sequences but no text sequences, so only validate if both are present + if (sequenceCount != mFileHeader.getSequenceDictionary().size()) { + throw new SAMFormatException("Number of sequences in text header (" + + mFileHeader.getSequenceDictionary().size() + + ") != number of sequences in binary header (" + sequenceCount + ") for file " + source); + } + for (int i = 0; i < sequenceCount; i++) { + final SAMSequenceRecord binarySequenceRecord = readSequenceRecord(source); + final SAMSequenceRecord sequenceRecord = mFileHeader.getSequence(i); + if (!sequenceRecord.getSequenceName().equals(binarySequenceRecord.getSequenceName())) { + throw new SAMFormatException("For sequence " + i + ", text and binary have different names in file " + + source); + } + if (sequenceRecord.getSequenceLength() != binarySequenceRecord.getSequenceLength()) { + throw new SAMFormatException("For sequence " + i + ", text and binary have different lengths in file " + + source); + } + } + } else { + // If only binary sequences are present, copy them into mFileHeader + final List sequences = new ArrayList(sequenceCount); + for (int i = 0; i < sequenceCount; i++) { + sequences.add(readSequenceRecord(source)); + } + mFileHeader.setSequenceDictionary(new SAMSequenceDictionary(sequences)); + } + } + + /** + * Reads a single binary sequence record from the file or stream + * @param source Note that this is used only for reporting errors. + */ + private SAMSequenceRecord readSequenceRecord(final String source) { + final int nameLength = mStream.readInt(); + if (nameLength <= 1) { + throw new SAMFormatException("Invalid BAM file header: missing sequence name in file " + source); + } + final String sequenceName = mStream.readString(nameLength - 1); + // Skip the null terminator + mStream.readByte(); + final int sequenceLength = mStream.readInt(); + return new SAMSequenceRecord(SAMSequenceRecord.truncateSequenceName(sequenceName), sequenceLength); + } + + /** + * Iterator for non-indexed sequential iteration through all SAMRecords in file. + * Starting point of iteration is wherever current file position is when the iterator is constructed. + */ + private class BAMFileIterator implements CloseableIterator { + private SAMRecord mNextRecord = null; + private final BAMRecordCodec bamRecordCodec; + private long samRecordIndex = 0; // Records at what position (counted in records) we are at in the file + + BAMFileIterator() { + this(true); + } + + /** + * @param advance Trick to enable subclass to do more setup before advancing + */ + BAMFileIterator(final boolean advance) { + this.bamRecordCodec = new BAMRecordCodec(getFileHeader(), samRecordFactory); + this.bamRecordCodec.setInputStream(BAMFileReader.this.mStream.getInputStream()); + + if (advance) { + advance(); + } + } + + public void close() { + if (mCurrentIterator != null && this != mCurrentIterator) { + throw new IllegalStateException("Attempt to close non-current iterator"); + } + mCurrentIterator = null; + } + + public boolean hasNext() { + return (mNextRecord != null); + } + + public SAMRecord next() { + final SAMRecord result = mNextRecord; + advance(); + return result; + } + + public void remove() { + throw new UnsupportedOperationException("Not supported: remove"); + } + + void advance() { + try { + mNextRecord = getNextRecord(); + + if (mNextRecord != null) { + ++this.samRecordIndex; + // Because some decoding is done lazily, the record needs to remember the validation stringency. + mNextRecord.setValidationStringency(mValidationStringency); + + if (mValidationStringency != ValidationStringency.SILENT) { + final List validationErrors = mNextRecord.isValid(); + SAMUtils.processValidationErrors(validationErrors, + this.samRecordIndex, BAMFileReader.this.getValidationStringency()); + } + } + if (eagerDecode && mNextRecord != null) { + mNextRecord.eagerDecode(); + } + } catch (IOException exc) { + throw new RuntimeException(exc.getMessage(), exc); + } + } + + /** + * Read the next record from the input stream. + */ + SAMRecord getNextRecord() throws IOException { + final long startCoordinate = mInputStream.getFilePointer(); + final SAMRecord next = bamRecordCodec.decode(); + final long stopCoordinate = mInputStream.getFilePointer(); + + if(mFileReader != null && next != null) + next.setFileSource(new SAMFileSource(mFileReader,new BAMFileSpan(new Chunk(startCoordinate,stopCoordinate)))); + + return next; + } + + /** + * @return The record that will be return by the next call to next() + */ + protected SAMRecord peek() { + return mNextRecord; + } + } + + /** + * Prepare to iterate through SAMRecords matching the target interval. + * @param sequence Desired reference sequence. + * @param start 1-based start of target interval, inclusive. + * @param end 1-based end of target interval, inclusive. + * @param queryType contained, overlapping, or starting-at query. + */ + private CloseableIterator createIndexIterator(final String sequence, + final int start, + final int end, + final QueryType queryType) { + long[] filePointers = null; + + // Hit the index to determine the chunk boundaries for the required data. + final SAMFileHeader fileHeader = getFileHeader(); + final int referenceIndex = fileHeader.getSequenceIndex(sequence); + if (referenceIndex != -1) { + final BAMIndex fileIndex = getIndex(); + final BAMFileSpan fileSpan = fileIndex.getSpanOverlapping(referenceIndex, start, end); + filePointers = fileSpan != null ? fileSpan.toCoordinateArray() : null; + } + + // Create an iterator over the above chunk boundaries. + final BAMFileIndexIterator iterator = new BAMFileIndexIterator(filePointers); + + // Add some preprocessing filters for edge-case reads that don't fit into this + // query type. + return new BAMQueryFilteringIterator(iterator,sequence,start,end,queryType); + } + + enum QueryType {CONTAINED, OVERLAPPING, STARTING_AT} + + /** + * Look for BAM index file according to standard naming convention. + * + * @param dataFile BAM file name. + * @return Index file name, or null if not found. + */ + private static File findIndexFile(final File dataFile) { + // If input is foo.bam, look for foo.bai + final String bamExtension = ".bam"; + File indexFile; + final String fileName = dataFile.getName(); + if (fileName.endsWith(bamExtension)) { + final String bai = fileName.substring(0, fileName.length() - bamExtension.length()) + BAMIndex.BAMIndexSuffix; + indexFile = new File(dataFile.getParent(), bai); + if (indexFile.exists()) { + return indexFile; + } + } + + // If foo.bai doesn't exist look for foo.bam.bai + indexFile = new File(dataFile.getParent(), dataFile.getName() + ".bai"); + if (indexFile.exists()) { + return indexFile; + } else { + return null; + } + } + + private class BAMFileIndexIterator extends BAMFileIterator { + + private long[] mFilePointers = null; + private int mFilePointerIndex = 0; + private long mFilePointerLimit = -1; + + /** + * Prepare to iterate through SAMRecords stored in the specified compressed blocks at the given offset. + * @param filePointers the block / offset combination, stored in chunk format. + */ + BAMFileIndexIterator(final long[] filePointers) { + super(false); // delay advance() until after construction + mFilePointers = filePointers; + advance(); + } + + SAMRecord getNextRecord() + throws IOException { + // Advance to next file block if necessary + while (mInputStream.getFilePointer() >= mFilePointerLimit) { + if (mFilePointers == null || + mFilePointerIndex >= mFilePointers.length) { + return null; + } + final long startOffset = mFilePointers[mFilePointerIndex++]; + final long endOffset = mFilePointers[mFilePointerIndex++]; + mInputStream.seek(startOffset); + mFilePointerLimit = endOffset; + } + // Pull next record from stream + return super.getNextRecord(); + } + } + + /** + * A decorating iterator that filters out records that are outside the bounds of the + * given query parameters. + */ + private class BAMQueryFilteringIterator implements CloseableIterator { + /** + * The wrapped iterator. + */ + private final CloseableIterator wrappedIterator; + + /** + * The next record to be returned. Will be null if no such record exists. + */ + private SAMRecord mNextRecord; + + private final int mReferenceIndex; + private final int mRegionStart; + private final int mRegionEnd; + private final QueryType mQueryType; + + public BAMQueryFilteringIterator(final CloseableIterator iterator,final String sequence, final int start, final int end, final QueryType queryType) { + this.wrappedIterator = iterator; + final SAMFileHeader fileHeader = getFileHeader(); + mReferenceIndex = fileHeader.getSequenceIndex(sequence); + mRegionStart = start; + if (queryType == QueryType.STARTING_AT) { + mRegionEnd = mRegionStart; + } else { + mRegionEnd = (end <= 0) ? Integer.MAX_VALUE : end; + } + mQueryType = queryType; + mNextRecord = advance(); + } + + /** + * Returns true if a next element exists; false otherwise. + */ + public boolean hasNext() { + return mNextRecord != null; + } + + /** + * Gets the next record from the given iterator. + * @return The next SAM record in the iterator. + */ + public SAMRecord next() { + if(!hasNext()) + throw new NoSuchElementException("BAMQueryFilteringIterator: no next element available"); + final SAMRecord currentRead = mNextRecord; + mNextRecord = advance(); + return currentRead; + } + + /** + * Closes down the existing iterator. + */ + public void close() { + if (this != mCurrentIterator) { + throw new IllegalStateException("Attempt to close non-current iterator"); + } + mCurrentIterator = null; + } + + /** + * @throws UnsupportedOperationException always. + */ + public void remove() { + throw new UnsupportedOperationException("Not supported: remove"); + } + + SAMRecord advance() { + while (true) { + // Pull next record from stream + if(!wrappedIterator.hasNext()) + return null; + + final SAMRecord record = wrappedIterator.next(); + // If beyond the end of this reference sequence, end iteration + final int referenceIndex = record.getReferenceIndex(); + if (referenceIndex != mReferenceIndex) { + if (referenceIndex < 0 || + referenceIndex > mReferenceIndex) { + return null; + } + // If before this reference sequence, continue + continue; + } + if (mRegionStart == 0 && mRegionEnd == Integer.MAX_VALUE) { + // Quick exit to avoid expensive alignment end calculation + return record; + } + final int alignmentStart = record.getAlignmentStart(); + // If read is unmapped but has a coordinate, return it if the coordinate is within + // the query region, regardless of whether the mapped mate will be returned. + final int alignmentEnd; + if (mQueryType == QueryType.STARTING_AT) { + alignmentEnd = -1; + } else { + alignmentEnd = (record.getAlignmentEnd() != SAMRecord.NO_ALIGNMENT_START? + record.getAlignmentEnd(): alignmentStart); + } + + if (alignmentStart > mRegionEnd) { + // If scanned beyond target region, end iteration + return null; + } + // Filter for overlap with region + if (mQueryType == QueryType.CONTAINED) { + if (alignmentStart >= mRegionStart && alignmentEnd <= mRegionEnd) { + return record; + } + } else if (mQueryType == QueryType.OVERLAPPING) { + if (alignmentEnd >= mRegionStart && alignmentStart <= mRegionEnd) { + return record; + } + } else { + if (alignmentStart == mRegionStart) { + return record; + } + } + } + } + } + + private class BAMFileIndexUnmappedIterator extends BAMFileIterator { + private BAMFileIndexUnmappedIterator() { + while (this.hasNext() && peek().getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + advance(); + } + } + } + +} diff --git a/public/java/src/net/sf/samtools/GATKBAMFileSpan.java b/public/java/src/net/sf/samtools/GATKBAMFileSpan.java index 623f46291..4692c6671 100644 --- a/public/java/src/net/sf/samtools/GATKBAMFileSpan.java +++ b/public/java/src/net/sf/samtools/GATKBAMFileSpan.java @@ -25,6 +25,7 @@ package net.sf.samtools; import net.sf.picard.util.PeekableIterator; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +48,18 @@ public class GATKBAMFileSpan extends BAMFileSpan { super(); } + /** + * Create a new GATKBAMFileSpan from an existing BAMFileSpan. + * @param sourceFileSpan + */ + public GATKBAMFileSpan(SAMFileSpan sourceFileSpan) { + if(!(sourceFileSpan instanceof BAMFileSpan)) + throw new SAMException("Unable to create GATKBAMFileSpan from a SAMFileSpan. Please submit a BAMFileSpan instead"); + BAMFileSpan sourceBAMFileSpan = (BAMFileSpan)sourceFileSpan; + for(Chunk chunk: sourceBAMFileSpan.getChunks()) + add(chunk instanceof GATKChunk ? chunk : new GATKChunk(chunk)); + } + /** * Convenience constructor to construct a BAM file span from * a single chunk. diff --git a/public/java/src/net/sf/samtools/GATKChunk.java b/public/java/src/net/sf/samtools/GATKChunk.java index f590809e2..5d349e72e 100644 --- a/public/java/src/net/sf/samtools/GATKChunk.java +++ b/public/java/src/net/sf/samtools/GATKChunk.java @@ -69,6 +69,22 @@ public class GATKChunk extends Chunk { super.setChunkEnd(value); } + public long getBlockStart() { + return getChunkStart() >>> 16; + } + + public int getBlockOffsetStart() { + return (int)(getChunkStart() & 0xFFFF); + } + + public long getBlockEnd() { + return getChunkEnd() >>> 16; + } + + public int getBlockOffsetEnd() { + return ((int)getChunkEnd() & 0xFFFF); + } + /** * Computes an approximation of the uncompressed size of the * chunk, in bytes. Can be used to determine relative weights diff --git a/public/java/src/net/sf/samtools/util/BAMInputStream.java b/public/java/src/net/sf/samtools/util/BAMInputStream.java new file mode 100644 index 000000000..d825c23d5 --- /dev/null +++ b/public/java/src/net/sf/samtools/util/BAMInputStream.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.sf.samtools.util; + +import java.io.IOException; + +/** + * An input stream formulated for use reading BAM files. Supports + */ +public interface BAMInputStream { + /** + * Seek to the given position in the file. Note that pos is a special virtual file pointer, + * not an actual byte offset. + * + * @param pos virtual file pointer + */ + public void seek(final long pos) throws IOException; + + /** + * @return virtual file pointer that can be passed to seek() to return to the current position. This is + * not an actual byte offset, so arithmetic on file pointers cannot be done to determine the distance between + * the two. + */ + public long getFilePointer(); + + /** + * Determines whether or not the inflater will re-calculated the CRC on the decompressed data + * and check it against the value stored in the GZIP header. CRC checking is an expensive + * operation and should be used accordingly. + */ + public void setCheckCrcs(final boolean check); + + public int read() throws java.io.IOException; + + public int read(byte[] bytes) throws java.io.IOException; + + public int read(byte[] bytes, int i, int i1) throws java.io.IOException; + + public long skip(long l) throws java.io.IOException; + + public int available() throws java.io.IOException; + + public void close() throws java.io.IOException; + + public void mark(int i); + + public void reset() throws java.io.IOException; + + public boolean markSupported(); +} diff --git a/public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java b/public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java new file mode 100755 index 000000000..fae2fc89b --- /dev/null +++ b/public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java @@ -0,0 +1,483 @@ +/* + * The MIT License + * + * Copyright (c) 2009 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.sf.samtools.util; + + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import net.sf.samtools.FileTruncatedException; + +/* + * Utility class for reading BGZF block compressed files. The caller can treat this file like any other InputStream. + * It probably is not necessary to wrap this stream in a buffering stream, because there is internal buffering. + * The advantage of BGZF over conventional GZip format is that BGZF allows for seeking without having to read the + * entire file up to the location being sought. Note that seeking is only possible if the ctor(File) is used. + * + * c.f. http://samtools.sourceforge.net/SAM1.pdf for details of BGZF format + */ +public class BlockCompressedInputStream extends InputStream implements BAMInputStream { + private InputStream mStream = null; + private SeekableStream mFile = null; + private byte[] mFileBuffer = null; + private byte[] mCurrentBlock = null; + private int mCurrentOffset = 0; + private long mBlockAddress = 0; + private int mLastBlockLength = 0; + private final BlockGunzipper blockGunzipper = new BlockGunzipper(); + + + /** + * Note that seek() is not supported if this ctor is used. + */ + public BlockCompressedInputStream(final InputStream stream) { + mStream = IOUtil.toBufferedStream(stream); + mFile = null; + } + + /** + * Use this ctor if you wish to call seek() + */ + public BlockCompressedInputStream(final File file) + throws IOException { + mFile = new SeekableFileStream(file); + mStream = null; + + } + + public BlockCompressedInputStream(final URL url) { + mFile = new SeekableBufferedStream(new SeekableHTTPStream(url)); + mStream = null; + } + + /** + * For providing some arbitrary data source. No additional buffering is + * provided, so if the underlying source is not buffered, wrap it in a + * SeekableBufferedStream before passing to this ctor. + */ + public BlockCompressedInputStream(final SeekableStream strm) { + mFile = strm; + mStream = null; + } + + /** + * Determines whether or not the inflater will re-calculated the CRC on the decompressed data + * and check it against the value stored in the GZIP header. CRC checking is an expensive + * operation and should be used accordingly. + */ + public void setCheckCrcs(final boolean check) { + this.blockGunzipper.setCheckCrcs(check); + } + + /** + * @return the number of bytes that can be read (or skipped over) from this input stream without blocking by the + * next caller of a method for this input stream. The next caller might be the same thread or another thread. + * Note that although the next caller can read this many bytes without blocking, the available() method call itself + * may block in order to fill an internal buffer if it has been exhausted. + */ + public int available() + throws IOException { + if (mCurrentBlock == null || mCurrentOffset == mCurrentBlock.length) { + readBlock(); + } + if (mCurrentBlock == null) { + return 0; + } + return mCurrentBlock.length - mCurrentOffset; + } + + /** + * Closes the underlying InputStream or RandomAccessFile + */ + public void close() + throws IOException { + if (mFile != null) { + mFile.close(); + mFile = null; + } else if (mStream != null) { + mStream.close(); + mStream = null; + } + // Encourage garbage collection + mFileBuffer = null; + mCurrentBlock = null; + } + + /** + * Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. + * If no byte is available because the end of the stream has been reached, the value -1 is returned. + * This method blocks until input data is available, the end of the stream is detected, or an exception is thrown. + + * @return the next byte of data, or -1 if the end of the stream is reached. + */ + public int read() + throws IOException { + return (available() > 0) ? mCurrentBlock[mCurrentOffset++] : -1; + } + + /** + * Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes + * actually read is returned as an integer. This method blocks until input data is available, end of file is detected, + * or an exception is thrown. + * + * read(buf) has the same effect as read(buf, 0, buf.length). + * + * @param buffer the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or -1 is there is no more data because the end of + * the stream has been reached. + */ + public int read(final byte[] buffer) + throws IOException { + return read(buffer, 0, buffer.length); + } + + private volatile ByteArrayOutputStream buf = null; + private static final byte eol = '\n'; + private static final byte eolCr = '\r'; + + /** + * Reads a whole line. A line is considered to be terminated by either a line feed ('\n'), + * carriage return ('\r') or carriage return followed by a line feed ("\r\n"). + * + * @return A String containing the contents of the line, excluding the line terminating + * character, or null if the end of the stream has been reached + * + * @exception IOException If an I/O error occurs + * @ + */ + public String readLine() throws IOException { + int available = available(); + if (available == 0) { + return null; + } + if(null == buf){ // lazy initialisation + buf = new ByteArrayOutputStream(8192); + } + buf.reset(); + boolean done = false; + boolean foundCr = false; // \r found flag + while (!done) { + int linetmpPos = mCurrentOffset; + int bCnt = 0; + while((available-- > 0)){ + final byte c = mCurrentBlock[linetmpPos++]; + if(c == eol){ // found \n + done = true; + break; + } else if(foundCr){ // previous char was \r + --linetmpPos; // current char is not \n so put it back + done = true; + break; + } else if(c == eolCr){ // found \r + foundCr = true; + continue; // no ++bCnt + } + ++bCnt; + } + if(mCurrentOffset < linetmpPos){ + buf.write(mCurrentBlock, mCurrentOffset, bCnt); + mCurrentOffset = linetmpPos; + } + available = available(); + if(available == 0){ + // EOF + done = true; + } + } + return buf.toString(); + } + + /** + * Reads up to len bytes of data from the input stream into an array of bytes. An attempt is made to read + * as many as len bytes, but a smaller number may be read. The number of bytes actually read is returned as an integer. + * + * This method blocks until input data is available, end of file is detected, or an exception is thrown. + * + * @param buffer buffer into which data is read. + * @param offset the start offset in array b at which the data is written. + * @param length the maximum number of bytes to read. + * @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of + * the stream has been reached. + */ + public int read(final byte[] buffer, int offset, int length) + throws IOException { + final int originalLength = length; + while (length > 0) { + final int available = available(); + if (available == 0) { + // Signal EOF to caller + if (originalLength == length) { + return -1; + } + break; + } + final int copyLength = Math.min(length, available); + System.arraycopy(mCurrentBlock, mCurrentOffset, buffer, offset, copyLength); + mCurrentOffset += copyLength; + offset += copyLength; + length -= copyLength; + } + return originalLength - length; + } + + /** + * Seek to the given position in the file. Note that pos is a special virtual file pointer, + * not an actual byte offset. + * + * @param pos virtual file pointer + */ + public void seek(final long pos) + throws IOException { + if (mFile == null) { + throw new IOException("Cannot seek on stream based file"); + } + // Decode virtual file pointer + // Upper 48 bits is the byte offset into the compressed stream of a block. + // Lower 16 bits is the byte offset into the uncompressed stream inside the block. + final long compressedOffset = BlockCompressedFilePointerUtil.getBlockAddress(pos); + final int uncompressedOffset = BlockCompressedFilePointerUtil.getBlockOffset(pos); + final int available; + if (mBlockAddress == compressedOffset && mCurrentBlock != null) { + available = mCurrentBlock.length; + } else { + mFile.seek(compressedOffset); + mBlockAddress = compressedOffset; + mLastBlockLength = 0; + readBlock(); + available = available(); + } + if (uncompressedOffset > available || + (uncompressedOffset == available && !eof())) { + throw new IOException("Invalid file pointer: " + pos); + } + mCurrentOffset = uncompressedOffset; + } + + private boolean eof() throws IOException { + if (mFile.eof()) { + return true; + } + // If the last remaining block is the size of the EMPTY_GZIP_BLOCK, this is the same as being at EOF. + return (mFile.length() - (mBlockAddress + mLastBlockLength) == BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + } + + /** + * @return virtual file pointer that can be passed to seek() to return to the current position. This is + * not an actual byte offset, so arithmetic on file pointers cannot be done to determine the distance between + * the two. + */ + public long getFilePointer() { + if (mCurrentOffset == mCurrentBlock.length) { + // If current offset is at the end of the current block, file pointer should point + // to the beginning of the next block. + return BlockCompressedFilePointerUtil.makeFilePointer(mBlockAddress + mLastBlockLength, 0); + } + return BlockCompressedFilePointerUtil.makeFilePointer(mBlockAddress, mCurrentOffset); + } + + public static long getFileBlock(final long bgzfOffset) { + return BlockCompressedFilePointerUtil.getBlockAddress(bgzfOffset); + } + + /** + * @param stream Must be at start of file. Throws RuntimeException if !stream.markSupported(). + * @return true if the given file looks like a valid BGZF file. + */ + public static boolean isValidFile(final InputStream stream) + throws IOException { + if (!stream.markSupported()) { + throw new RuntimeException("Cannot test non-buffered stream"); + } + stream.mark(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + final byte[] buffer = new byte[BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH]; + final int count = readBytes(stream, buffer, 0, BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + stream.reset(); + return count == BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH && isValidBlockHeader(buffer); + } + + private static boolean isValidBlockHeader(final byte[] buffer) { + return (buffer[0] == BlockCompressedStreamConstants.GZIP_ID1 && + (buffer[1] & 0xFF) == BlockCompressedStreamConstants.GZIP_ID2 && + (buffer[3] & BlockCompressedStreamConstants.GZIP_FLG) != 0 && + buffer[10] == BlockCompressedStreamConstants.GZIP_XLEN && + buffer[12] == BlockCompressedStreamConstants.BGZF_ID1 && + buffer[13] == BlockCompressedStreamConstants.BGZF_ID2); + } + + private void readBlock() + throws IOException { + + if (mFileBuffer == null) { + mFileBuffer = new byte[BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE]; + } + int count = readBytes(mFileBuffer, 0, BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + if (count == 0) { + // Handle case where there is no empty gzip block at end. + mCurrentOffset = 0; + mBlockAddress += mLastBlockLength; + mCurrentBlock = new byte[0]; + return; + } + if (count != BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH) { + throw new IOException("Premature end of file"); + } + final int blockLength = unpackInt16(mFileBuffer, BlockCompressedStreamConstants.BLOCK_LENGTH_OFFSET) + 1; + if (blockLength < BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH || blockLength > mFileBuffer.length) { + throw new IOException("Unexpected compressed block length: " + blockLength); + } + final int remaining = blockLength - BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH; + count = readBytes(mFileBuffer, BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH, remaining); + if (count != remaining) { + throw new FileTruncatedException("Premature end of file"); + } + inflateBlock(mFileBuffer, blockLength); + mCurrentOffset = 0; + mBlockAddress += mLastBlockLength; + mLastBlockLength = blockLength; + } + + private void inflateBlock(final byte[] compressedBlock, final int compressedLength) + throws IOException { + final int uncompressedLength = unpackInt32(compressedBlock, compressedLength-4); + byte[] buffer = mCurrentBlock; + mCurrentBlock = null; + if (buffer == null || buffer.length != uncompressedLength) { + try { + buffer = new byte[uncompressedLength]; + } catch (NegativeArraySizeException e) { + throw new RuntimeException("BGZF file has invalid uncompressedLength: " + uncompressedLength, e); + } + } + blockGunzipper.unzipBlock(buffer, compressedBlock, compressedLength); + mCurrentBlock = buffer; + } + + private int readBytes(final byte[] buffer, final int offset, final int length) + throws IOException { + if (mFile != null) { + return readBytes(mFile, buffer, offset, length); + } else if (mStream != null) { + return readBytes(mStream, buffer, offset, length); + } else { + return 0; + } + } + + private static int readBytes(final SeekableStream file, final byte[] buffer, final int offset, final int length) + throws IOException { + int bytesRead = 0; + while (bytesRead < length) { + final int count = file.read(buffer, offset + bytesRead, length - bytesRead); + if (count <= 0) { + break; + } + bytesRead += count; + } + return bytesRead; + } + + private static int readBytes(final InputStream stream, final byte[] buffer, final int offset, final int length) + throws IOException { + int bytesRead = 0; + while (bytesRead < length) { + final int count = stream.read(buffer, offset + bytesRead, length - bytesRead); + if (count <= 0) { + break; + } + bytesRead += count; + } + return bytesRead; + } + + private int unpackInt16(final byte[] buffer, final int offset) { + return ((buffer[offset] & 0xFF) | + ((buffer[offset+1] & 0xFF) << 8)); + } + + private int unpackInt32(final byte[] buffer, final int offset) { + return ((buffer[offset] & 0xFF) | + ((buffer[offset+1] & 0xFF) << 8) | + ((buffer[offset+2] & 0xFF) << 16) | + ((buffer[offset+3] & 0xFF) << 24)); + } + + public enum FileTermination {HAS_TERMINATOR_BLOCK, HAS_HEALTHY_LAST_BLOCK, DEFECTIVE} + + public static FileTermination checkTermination(final File file) + throws IOException { + final long fileSize = file.length(); + if (fileSize < BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length) { + return FileTermination.DEFECTIVE; + } + final RandomAccessFile raFile = new RandomAccessFile(file, "r"); + try { + raFile.seek(fileSize - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + byte[] buf = new byte[BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length]; + raFile.readFully(buf); + if (Arrays.equals(buf, BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK)) { + return FileTermination.HAS_TERMINATOR_BLOCK; + } + final int bufsize = (int)Math.min(fileSize, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE); + buf = new byte[bufsize]; + raFile.seek(fileSize - bufsize); + raFile.read(buf); + for (int i = buf.length - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length; + i >= 0; --i) { + if (!preambleEqual(BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE, + buf, i, BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length)) { + continue; + } + final ByteBuffer byteBuffer = ByteBuffer.wrap(buf, i + BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length, 4); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final int totalBlockSizeMinusOne = byteBuffer.getShort() & 0xFFFF; + if (buf.length - i == totalBlockSizeMinusOne + 1) { + return FileTermination.HAS_HEALTHY_LAST_BLOCK; + } else { + return FileTermination.DEFECTIVE; + } + } + return FileTermination.DEFECTIVE; + } finally { + raFile.close(); + } + } + + private static boolean preambleEqual(final byte[] preamble, final byte[] buf, final int startOffset, final int length) { + for (int i = 0; i < length; ++i) { + if (preamble[i] != buf[i + startOffset]) { + return false; + } + } + return true; + } +} + + diff --git a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java index bed1e710e..b0b57f7fc 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java +++ b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java @@ -331,12 +331,12 @@ public abstract class CommandLineProgram { * used to indicate an error occured * * @param msg the message - * @param e the error + * @param t the error */ - public static void exitSystemWithError(String msg, final Exception e) { + public static void exitSystemWithError(String msg, final Throwable t) { errorPrintf("------------------------------------------------------------------------------------------%n"); errorPrintf("stack trace %n"); - e.printStackTrace(); + t.printStackTrace(); errorPrintf("------------------------------------------------------------------------------------------%n"); errorPrintf("A GATK RUNTIME ERROR has occurred (version %s):%n", CommandLineGATK.getVersionNumber()); @@ -394,8 +394,8 @@ public abstract class CommandLineProgram { * * @param e the exception occured */ - public static void exitSystemWithError(Exception e) { - exitSystemWithError(e.getMessage(), e); + public static void exitSystemWithError(Throwable t) { + exitSystemWithError(t.getMessage(), t); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java index b8488dc9a..d3db35c07 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java +++ b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java @@ -99,8 +99,8 @@ public class CommandLineGATK extends CommandLineExecutable { } catch (net.sf.samtools.SAMException e) { // Let's try this out and see how it is received by our users exitSystemWithSamError(e); - } catch (Exception e) { - exitSystemWithError(e); + } catch (Throwable t) { + exitSystemWithError(t); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 2ceb4ab46..f2e0b5d0c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -35,6 +35,7 @@ import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; import org.broadinstitute.sting.gatk.datasources.reads.*; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.gatk.samples.SampleDB; import org.broadinstitute.sting.gatk.executive.MicroScheduler; import org.broadinstitute.sting.gatk.filters.FilterManager; @@ -126,6 +127,11 @@ public class GenomeAnalysisEngine { */ private Collection filters; + /** + * Controls the allocation of threads between CPU vs IO. + */ + private ThreadAllocation threadAllocation; + /** * A currently hacky unique name for this GATK instance */ @@ -199,6 +205,9 @@ public class GenomeAnalysisEngine { if (this.getArguments().nonDeterministicRandomSeed) resetRandomGenerator(System.currentTimeMillis()); + // Determine how the threads should be divided between CPU vs. IO. + determineThreadAllocation(); + // Prepare the data for traversal. initializeDataSources(); @@ -218,7 +227,7 @@ public class GenomeAnalysisEngine { // create the output streams " initializeOutputStreams(microScheduler.getOutputTracker()); - ShardStrategy shardStrategy = getShardStrategy(readsDataSource,microScheduler.getReference(),intervals); + Iterable shardStrategy = getShardStrategy(readsDataSource,microScheduler.getReference(),intervals); // execute the microscheduler, storing the results return microScheduler.execute(this.walker, shardStrategy); @@ -266,6 +275,16 @@ public class GenomeAnalysisEngine { return Collections.unmodifiableList(filters); } + /** + * Parse out the thread allocation from the given command-line argument. + */ + private void determineThreadAllocation() { + Tags tags = parsingEngine.getTags(argCollection.numberOfThreads); + Integer numCPUThreads = tags.containsKey("cpu") ? Integer.parseInt(tags.getValue("cpu")) : null; + Integer numIOThreads = tags.containsKey("io") ? Integer.parseInt(tags.getValue("io")) : null; + this.threadAllocation = new ThreadAllocation(argCollection.numberOfThreads,numCPUThreads,numIOThreads); + } + /** * Allow subclasses and others within this package direct access to the walker manager. * @return The walker manager used by this package. @@ -286,7 +305,7 @@ public class GenomeAnalysisEngine { throw new UserException.CommandLineException("Read-based traversals require a reference file but none was given"); } - return MicroScheduler.create(this,walker,this.getReadsDataSource(),this.getReferenceDataSource().getReference(),this.getRodDataSources(),this.getArguments().numberOfThreads); + return MicroScheduler.create(this,walker,this.getReadsDataSource(),this.getReferenceDataSource().getReference(),this.getRodDataSources(),threadAllocation); } protected DownsamplingMethod getDownsamplingMethod() { @@ -397,103 +416,49 @@ public class GenomeAnalysisEngine { * @param intervals intervals * @return the sharding strategy */ - protected ShardStrategy getShardStrategy(SAMDataSource readsDataSource, ReferenceSequenceFile drivingDataSource, GenomeLocSortedSet intervals) { + protected Iterable getShardStrategy(SAMDataSource readsDataSource, ReferenceSequenceFile drivingDataSource, GenomeLocSortedSet intervals) { ValidationExclusion exclusions = (readsDataSource != null ? readsDataSource.getReadsInfo().getValidationExclusionList() : null); ReferenceDataSource referenceDataSource = this.getReferenceDataSource(); - // Use monolithic sharding if no index is present. Monolithic sharding is always required for the original - // sharding system; it's required with the new sharding system only for locus walkers. - if(readsDataSource != null && !readsDataSource.hasIndex() ) { - if(!exclusions.contains(ValidationExclusion.TYPE.ALLOW_UNINDEXED_BAM)) + + // If reads are present, assume that accessing the reads is always the dominant factor and shard based on that supposition. + if(!readsDataSource.isEmpty()) { + if(!readsDataSource.hasIndex() && !exclusions.contains(ValidationExclusion.TYPE.ALLOW_UNINDEXED_BAM)) throw new UserException.CommandLineException("Cannot process the provided BAM file(s) because they were not indexed. The GATK does offer limited processing of unindexed BAMs in --unsafe mode, but this GATK feature is currently unsupported."); - if(intervals != null && !argCollection.allowIntervalsWithUnindexedBAM) + if(!readsDataSource.hasIndex() && intervals != null && !argCollection.allowIntervalsWithUnindexedBAM) throw new UserException.CommandLineException("Cannot perform interval processing when reads are present but no index is available."); - Shard.ShardType shardType; if(walker instanceof LocusWalker) { if (readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.coordinate) throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.coordinate, "Locus walkers can only traverse coordinate-sorted data. Please resort your input BAM file(s) or set the Sort Order tag in the header appropriately."); - shardType = Shard.ShardType.LOCUS; + if(intervals == null) + return readsDataSource.createShardIteratorOverMappedReads(referenceDataSource.getReference().getSequenceDictionary(),new LocusShardBalancer()); + else + return readsDataSource.createShardIteratorOverIntervals(intervals,new LocusShardBalancer()); + } + else if(walker instanceof ReadWalker || walker instanceof ReadPairWalker || walker instanceof DuplicateWalker) { + // Apply special validation to read pair walkers. + if(walker instanceof ReadPairWalker) { + if(readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.queryname) + throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers are exceptions in that they cannot be run on coordinate-sorted BAMs but instead require query name-sorted files. You will need to resort your input BAM file in query name order to use this walker."); + if(intervals != null && !intervals.isEmpty()) + throw new UserException.CommandLineException("Pairs traversal cannot be used in conjunction with intervals."); + } + + if(intervals == null) + return readsDataSource.createShardIteratorOverAllReads(new ReadShardBalancer()); + else + return readsDataSource.createShardIteratorOverIntervals(intervals,new ReadShardBalancer()); } - else if(walker instanceof ReadWalker || walker instanceof DuplicateWalker || walker instanceof ReadPairWalker) - shardType = Shard.ShardType.READ; else - throw new UserException.CommandLineException("The GATK cannot currently process unindexed BAM files"); - - List region; - if(intervals != null) - region = intervals.toList(); - else { - region = new ArrayList(); - for(SAMSequenceRecord sequenceRecord: drivingDataSource.getSequenceDictionary().getSequences()) - region.add(getGenomeLocParser().createGenomeLoc(sequenceRecord.getSequenceName(),1,sequenceRecord.getSequenceLength())); - } - - return new MonolithicShardStrategy(getGenomeLocParser(), readsDataSource,shardType,region); + throw new ReviewedStingException("Unable to determine walker type for walker " + walker.getClass().getName()); + } + else { + final int SHARD_SIZE = walker instanceof RodWalker ? 100000000 : 100000; + if(intervals == null) + return referenceDataSource.createShardsOverEntireReference(readsDataSource,genomeLocParser,SHARD_SIZE); + else + return referenceDataSource.createShardsOverIntervals(readsDataSource,intervals,SHARD_SIZE); } - - ShardStrategy shardStrategy; - ShardStrategyFactory.SHATTER_STRATEGY shardType; - - long SHARD_SIZE = 100000L; - - if (walker instanceof LocusWalker) { - if (walker instanceof RodWalker) SHARD_SIZE *= 1000; - - if (intervals != null && !intervals.isEmpty()) { - if (readsDataSource == null) - throw new IllegalArgumentException("readsDataSource is null"); - if(!readsDataSource.isEmpty() && readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.coordinate) - throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.coordinate, "Locus walkers can only traverse coordinate-sorted data. Please resort your input BAM file(s) or set the Sort Order tag in the header appropriately."); - - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser(), - intervals); - } else - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE,getGenomeLocParser()); - } else if (walker instanceof ReadWalker || - walker instanceof DuplicateWalker) { - shardType = ShardStrategyFactory.SHATTER_STRATEGY.READS_EXPERIMENTAL; - - if (intervals != null && !intervals.isEmpty()) { - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - shardType, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser(), - intervals); - } else { - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - shardType, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser()); - } - } else if (walker instanceof ReadPairWalker) { - if(readsDataSource != null && readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.queryname) - throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers are exceptions in that they cannot be run on coordinate-sorted BAMs but instead require query name-sorted files. You will need to resort your input BAM file in query name order to use this walker."); - if(intervals != null && !intervals.isEmpty()) - throw new UserException.CommandLineException("Pairs traversal cannot be used in conjunction with intervals."); - - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - ShardStrategyFactory.SHATTER_STRATEGY.READS_EXPERIMENTAL, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser()); - } else - throw new ReviewedStingException("Unable to support walker of type" + walker.getClass().getName()); - - return shardStrategy; } protected boolean flashbackData() { @@ -751,6 +716,8 @@ public class GenomeAnalysisEngine { return new SAMDataSource( samReaderIDs, + threadAllocation, + argCollection.numberOfBAMFileHandles, genomeLocParser, argCollection.useOriginalBaseQualities, argCollection.strictnessLevel, @@ -763,8 +730,7 @@ public class GenomeAnalysisEngine { getWalkerBAQApplicationTime() == BAQ.ApplicationTime.ON_INPUT ? argCollection.BAQMode : BAQ.CalculationMode.OFF, getWalkerBAQQualityMode(), refReader, - argCollection.defaultBaseQualities, - !argCollection.disableLowMemorySharding); + argCollection.defaultBaseQualities); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java index 8078a1ea4..64b63dcd2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -194,10 +194,14 @@ public class GATKArgumentCollection { @Argument(fullName = "unsafe", shortName = "U", doc = "If set, enables unsafe operations: nothing will be checked at runtime. For expert users only who know what they are doing. We do not support usage of this argument.", required = false) public ValidationExclusion.TYPE unsafe; - @Argument(fullName = "num_threads", shortName = "nt", doc = "How many threads should be allocated to running this analysis", required = false) - public int numberOfThreads = 1; + /** How many threads should be allocated to this analysis. */ + @Argument(fullName = "num_threads", shortName = "nt", doc = "How many threads should be allocated to running this analysis.", required = false) + public Integer numberOfThreads = 1; - @Input(fullName = "read_group_black_list", shortName="rgbl", doc="Filters out read groups matching : or a .txt file containing the filter strings one per line", required = false) + @Argument(fullName = "num_bam_file_handles", shortName = "bfh", doc="The total number of BAM file handles to keep open simultaneously", required=false) + public Integer numberOfBAMFileHandles = null; + + @Input(fullName = "read_group_black_list", shortName="rgbl", doc="Filters out read groups matching : or a .txt file containing the filter strings one per line.", required = false) public List readGroupBlackList = null; // -------------------------------------------------------------------------------------------------------------- @@ -292,9 +296,6 @@ public class GATKArgumentCollection { @Hidden public boolean allowIntervalsWithUnindexedBAM = false; - @Argument(fullName="disable_experimental_low_memory_sharding",doc="Disable experimental low-memory sharding functionality",required=false) - public boolean disableLowMemorySharding = false; - // -------------------------------------------------------------------------------------------------------------- // // methods @@ -365,7 +366,11 @@ public class GATKArgumentCollection { (other.downsampleCoverage != null && !other.downsampleCoverage.equals(this.downsampleCoverage))) { return false; } - if (other.numberOfThreads != this.numberOfThreads) { + if (!other.numberOfThreads.equals(this.numberOfThreads)) { + return false; + } + if ((other.numberOfBAMFileHandles == null && this.numberOfBAMFileHandles != null) || + (other.numberOfBAMFileHandles != null && !other.numberOfBAMFileHandles.equals(this.numberOfBAMFileHandles))) { return false; } if (other.intervalMerging != this.intervalMerging) { @@ -389,9 +394,6 @@ public class GATKArgumentCollection { if (allowIntervalsWithUnindexedBAM != other.allowIntervalsWithUnindexedBAM) return false; - if (disableLowMemorySharding != other.disableLowMemorySharding) - return false; - return true; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java deleted file mode 100644 index de938e845..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2011, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.datasources.reads; - -import org.broadinstitute.sting.utils.exceptions.StingException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; -import java.util.Iterator; - -/** - * Created by IntelliJ IDEA. - * User: mhanna - * Date: Feb 7, 2011 - * Time: 2:46:34 PM - * To change this template use File | Settings | File Templates. - */ -public class BAMBlockStartIterator implements Iterator { - /** - * How large is a BGZF header? - */ - private static int BGZF_HEADER_SIZE = 18; - - /** - * Where within the header does the BLOCKSIZE actually live? - */ - private static int BLOCK_SIZE_HEADER_POSITION = BGZF_HEADER_SIZE - 2; - - private FileChannel bamInputChannel; - private ByteBuffer headerByteBuffer; - - private long nextLocation = 0; - - public BAMBlockStartIterator(File bamFile) { - try { - FileInputStream bamInputStream = new FileInputStream(bamFile); - bamInputChannel = bamInputStream.getChannel(); - - headerByteBuffer = ByteBuffer.allocate(BGZF_HEADER_SIZE); - headerByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - } - catch(IOException ex) { - throw new StingException("Could not open file",ex); - } - } - - public boolean hasNext() { - return nextLocation != -1; - } - - public Long next() { - long currentLocation = nextLocation; - advance(); - return currentLocation; - } - - public void remove() { - throw new UnsupportedOperationException("Cannot remove from a BAMBlockStartIterator"); - } - - private void advance() { - int readStatus; - - headerByteBuffer.clear(); - try { - readStatus = bamInputChannel.read(headerByteBuffer); - } - catch(IOException ex) { - throw new StingException("Could not read header data",ex); - } - - if(readStatus == -1) { - nextLocation = -1; - try { - bamInputChannel.close(); - } - catch(IOException ex) { - throw new StingException("Could not close input file",ex); - } - return; - } - - headerByteBuffer.position(BLOCK_SIZE_HEADER_POSITION); - int blockSize = headerByteBuffer.getShort(); - - try { - bamInputChannel.position(bamInputChannel.position()+blockSize-BGZF_HEADER_SIZE+1); - nextLocation = bamInputChannel.position(); - } - catch(IOException ex) { - throw new StingException("Could not reposition input stream",ex); - } - } - - public static void main(String argv[]) throws IOException { - BAMBlockStartIterator blockStartIterator = new BAMBlockStartIterator(new File("/Users/mhanna/testdata/reads/MV1994.bam")); - int i = 0; - while(blockStartIterator.hasNext()) - System.out.printf("%d -> %d%n",i++,blockStartIterator.next()); - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java deleted file mode 100644 index 4d91fb45f..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2011, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.samtools.GATKBin; -import net.sf.samtools.GATKChunk; -import net.sf.samtools.LinearIndex; - -import java.util.*; - -/** - * Represents the contents of a bam index file for one reference. - * A BAM index (.bai) file contains information for all references in the bam file. - * This class describes the data present in the index file for one of these references; - * including the bins, chunks, and linear index. - */ -class BAMIndexContent { - /** - * The reference sequence for the data currently loaded. - */ - private final int mReferenceSequence; - - /** - * A list of all bins in the above reference sequence. - */ - private final BinList mBinList; - - /** - * The linear index for the reference sequence above. - */ - private final LinearIndex mLinearIndex; - - - /** - * @param referenceSequence Content corresponds to this reference. - * @param bins Array of bins represented by this content, possibly sparse - * @param numberOfBins Number of non-null bins - * @param linearIndex Additional index used to optimize queries - */ - BAMIndexContent(final int referenceSequence, final GATKBin[] bins, final int numberOfBins, final LinearIndex linearIndex) { - this.mReferenceSequence = referenceSequence; - this.mBinList = new BinList(bins, numberOfBins); - this.mLinearIndex = linearIndex; - } - - /** - * Reference for this Content - */ - public int getReferenceSequence() { - return mReferenceSequence; - } - - /** - * Does this content have anything in this bin? - */ - public boolean containsBin(final GATKBin bin) { - return mBinList.getBin(bin.getBinNumber()) != null; - } - - /** - * @return iterable list of bins represented by this content - */ - public BinList getBins() { - return mBinList; - } - - /** - * @return the number of non-null bins represented by this content - */ - int getNumberOfNonNullBins() { - return mBinList.getNumberOfNonNullBins(); - } - - /** - * @return all chunks associated with all bins in this content - */ - public List getAllChunks() { - List allChunks = new ArrayList(); - for (GATKBin b : mBinList) - if (b.getChunkList() != null) { - allChunks.addAll(Arrays.asList(b.getChunkList())); - } - return Collections.unmodifiableList(allChunks); - } - - /** - * @return the linear index represented by this content - */ - public LinearIndex getLinearIndex() { - return mLinearIndex; - } - - /** - * This class is used to encapsulate the list of Bins store in the BAMIndexContent - * While it is currently represented as an array, we may decide to change it to an ArrayList or other structure - */ - class BinList implements Iterable { - - private final GATKBin[] mBinArray; - public final int numberOfNonNullBins; - public final int maxBinNumber; // invariant: maxBinNumber = mBinArray.length -1 since array is 0 based - - /** - * @param binArray a sparse array representation of the bins. The index into the array is the bin number. - * @param numberOfNonNullBins - */ - BinList(GATKBin[] binArray, int numberOfNonNullBins) { - this.mBinArray = binArray; - this.numberOfNonNullBins = numberOfNonNullBins; - this.maxBinNumber = mBinArray.length - 1; - } - - GATKBin getBin(int binNumber) { - if (binNumber > maxBinNumber) return null; - return mBinArray[binNumber]; - } - - int getNumberOfNonNullBins() { - return numberOfNonNullBins; - } - - /** - * Gets an iterator over all non-null bins. - * - * @return An iterator over all bins. - */ - public Iterator iterator() { - return new BinIterator(); - } - - private class BinIterator implements Iterator { - /** - * Stores the bin # of the Bin currently in use. - */ - private int nextBin; - - public BinIterator() { - nextBin = 0; - } - - /** - * Are there more bins in this set, waiting to be returned? - * - * @return True if more bins are remaining. - */ - public boolean hasNext() { - while (nextBin <= maxBinNumber) { - if (getBin(nextBin) != null) return true; - nextBin++; - } - return false; - } - - /** - * Gets the next bin in the provided BinList. - * - * @return the next available bin in the BinList. - */ - public GATKBin next() { - if (!hasNext()) - throw new NoSuchElementException("This BinIterator is currently empty"); - GATKBin result = getBin(nextBin); - nextBin++; - return result; - } - - public void remove() { - throw new UnsupportedOperationException("Unable to remove from a bin iterator"); - } - } - } - -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java deleted file mode 100644 index 15a372ca6..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.samtools.Bin; - -import java.util.HashMap; -import java.util.Map; - -/** - * Models a bin at which all BAM files in the merged input stream overlap. - */ -class BAMOverlap { - public final int start; - public final int stop; - - private final Map bins = new HashMap(); - - public BAMOverlap(final int start, final int stop) { - this.start = start; - this.stop = stop; - } - - public void addBin(final SAMReaderID id, final Bin bin) { - bins.put(id,bin); - } - - public Bin getBin(final SAMReaderID id) { - return bins.get(id); - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java index 521bcd5a3..762722fcd 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java @@ -84,21 +84,21 @@ public class BAMSchedule implements CloseableIterator { /** * Create a new BAM schedule based on the given index. - * @param indexFiles Index files. + * @param dataSource The SAM data source to use. * @param intervals List of */ - public BAMSchedule(final Map indexFiles, final List intervals) { + public BAMSchedule(final SAMDataSource dataSource, final List intervals) { if(intervals.isEmpty()) throw new ReviewedStingException("Tried to write schedule for empty interval list."); - referenceSequence = intervals.get(0).getContigIndex(); + referenceSequence = dataSource.getHeader().getSequence(intervals.get(0).getContig()).getSequenceIndex(); createScheduleFile(); - readerIDs.addAll(indexFiles.keySet()); + readerIDs.addAll(dataSource.getReaderIDs()); for(final SAMReaderID reader: readerIDs) { - final GATKBAMIndex index = indexFiles.get(reader); + final GATKBAMIndex index = dataSource.getIndex(reader); final GATKBAMIndexData indexData = index.readReferenceSequence(referenceSequence); int currentBinInLowestLevel = GATKBAMIndex.getFirstBinInLevel(GATKBAMIndex.getNumIndexLevels()-1); @@ -237,7 +237,10 @@ public class BAMSchedule implements CloseableIterator { if(selectedIterators.isEmpty()) return; + // Create the target schedule entry BAMScheduleEntry mergedScheduleEntry = new BAMScheduleEntry(currentStart,currentStop); + + // For each schedule entry with data, load the data into the merged schedule. for (int reader = selectedIterators.nextSetBit(0); reader >= 0; reader = selectedIterators.nextSetBit(reader+1)) { PeekableIterator scheduleIterator = scheduleIterators.get(reader); BAMScheduleEntry individualScheduleEntry = scheduleIterator.peek(); @@ -248,6 +251,11 @@ public class BAMSchedule implements CloseableIterator { scheduleIterator.next(); } + // For each schedule entry without data, add a blank entry. + for (int reader = selectedIterators.nextClearBit(0); reader < readerIDs.size(); reader = selectedIterators.nextClearBit(reader+1)) { + mergedScheduleEntry.addFileSpan(readerIDs.get(reader),new GATKBAMFileSpan()); + } + nextScheduleEntry = mergedScheduleEntry; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java index 47eb55b28..dca4cc771 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java @@ -27,7 +27,12 @@ package org.broadinstitute.sting.gatk.datasources.reads; import net.sf.picard.util.PeekableIterator; import net.sf.samtools.GATKBAMFileSpan; import net.sf.samtools.GATKChunk; +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMFileSpan; +import net.sf.samtools.SAMSequenceDictionary; +import net.sf.samtools.SAMSequenceRecord; import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.GenomeLocSortedSet; import java.util.*; @@ -42,21 +47,86 @@ public class BAMScheduler implements Iterator { private FilePointer nextFilePointer = null; - private final GenomeLocSortedSet loci; + private GenomeLocSortedSet loci; + private PeekableIterator locusIterator; + private GenomeLoc currentLocus; - private final PeekableIterator locusIterator; + public static BAMScheduler createOverMappedReads(final SAMDataSource dataSource, final SAMSequenceDictionary referenceSequenceDictionary, final GenomeLocParser parser) { + BAMScheduler scheduler = new BAMScheduler(dataSource); + GenomeLocSortedSet intervals = new GenomeLocSortedSet(parser); + for(SAMSequenceRecord sequence: referenceSequenceDictionary.getSequences()) { + // Match only on sequence name; trust startup validation to make sure all the sequences match. + if(dataSource.getHeader().getSequenceDictionary().getSequence(sequence.getSequenceName()) != null) + intervals.add(parser.createOverEntireContig(sequence.getSequenceName())); + } + scheduler.populateFilteredIntervalList(intervals); + return scheduler; + } - private GenomeLoc currentLocus; + public static BAMScheduler createOverAllReads(final SAMDataSource dataSource, final GenomeLocParser parser) { + BAMScheduler scheduler = new BAMScheduler(dataSource); + scheduler.populateUnfilteredIntervalList(parser); + return scheduler; + } - public BAMScheduler(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { + public static BAMScheduler createOverIntervals(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { + BAMScheduler scheduler = new BAMScheduler(dataSource); + scheduler.populateFilteredIntervalList(loci); + return scheduler; + } + + + private BAMScheduler(final SAMDataSource dataSource) { this.dataSource = dataSource; - for(SAMReaderID reader: dataSource.getReaderIDs()) - indexFiles.put(reader,(GATKBAMIndex)dataSource.getIndex(reader)); + for(SAMReaderID reader: dataSource.getReaderIDs()) { + GATKBAMIndex index = dataSource.getIndex(reader); + if(index != null) + indexFiles.put(reader,dataSource.getIndex(reader)); + } + } + + /** + * The consumer has asked for a bounded set of locations. Prepare an iterator over those locations. + * @param loci The list of locations to search and iterate over. + */ + private void populateFilteredIntervalList(final GenomeLocSortedSet loci) { this.loci = loci; - locusIterator = new PeekableIterator(loci.iterator()); - if(locusIterator.hasNext()) - currentLocus = locusIterator.next(); - advance(); + if(!indexFiles.isEmpty()) { + // If index data is available, start up the iterator. + locusIterator = new PeekableIterator(loci.iterator()); + if(locusIterator.hasNext()) + currentLocus = locusIterator.next(); + advance(); + } + else { + // Otherwise, seed the iterator with a single file pointer over the entire region. + nextFilePointer = generatePointerOverEntireFileset(); + for(GenomeLoc locus: loci) + nextFilePointer.addLocation(locus); + locusIterator = new PeekableIterator(Collections.emptyList().iterator()); + } + } + + /** + * The consumer has provided null, meaning to iterate over all available data. Create a file pointer stretching + * from just before the start of the region to the end of the region. + */ + private void populateUnfilteredIntervalList(final GenomeLocParser parser) { + this.loci = new GenomeLocSortedSet(parser); + locusIterator = new PeekableIterator(Collections.emptyList().iterator()); + nextFilePointer = generatePointerOverEntireFileset(); + } + + /** + * Generate a span that runs from the end of the BAM header to the end of the fle. + * @return A file pointer over the specified region. + */ + private FilePointer generatePointerOverEntireFileset() { + FilePointer filePointer = new FilePointer(); + Map currentPosition = dataSource.getCurrentPosition(); + for(SAMReaderID reader: dataSource.getReaderIDs()) + filePointer.addFileSpans(reader,createSpanToEndOfFile(currentPosition.get(reader).getGATKChunks().get(0).getChunkStart())); + return filePointer; } public boolean hasNext() { @@ -67,7 +137,9 @@ public class BAMScheduler implements Iterator { if(!hasNext()) throw new NoSuchElementException("No next element available in interval sharder"); FilePointer currentFilePointer = nextFilePointer; + nextFilePointer = null; advance(); + return currentFilePointer; } @@ -79,13 +151,12 @@ public class BAMScheduler implements Iterator { if(loci.isEmpty()) return; - nextFilePointer = null; while(nextFilePointer == null && currentLocus != null) { // special case handling of the unmapped shard. if(currentLocus == GenomeLoc.UNMAPPED) { nextFilePointer = new FilePointer(GenomeLoc.UNMAPPED); for(SAMReaderID id: dataSource.getReaderIDs()) - nextFilePointer.addFileSpans(id,new GATKBAMFileSpan(new GATKChunk(indexFiles.get(id).getStartOfLastLinearBin(),Long.MAX_VALUE))); + nextFilePointer.addFileSpans(id,createSpanToEndOfFile(indexFiles.get(id).getStartOfLastLinearBin())); currentLocus = null; continue; } @@ -96,7 +167,7 @@ public class BAMScheduler implements Iterator { int coveredRegionStop = Integer.MAX_VALUE; GenomeLoc coveredRegion = null; - BAMScheduleEntry scheduleEntry = getNextOverlappingBAMScheduleEntry(indexFiles,currentLocus); + BAMScheduleEntry scheduleEntry = getNextOverlappingBAMScheduleEntry(currentLocus); // No overlapping data at all. if(scheduleEntry != null) { @@ -108,7 +179,6 @@ public class BAMScheduler implements Iterator { } else { // Always create a file span, whether there was covered data or not. If there was no covered data, then the binTree is empty. - //System.out.printf("Shard: index file = %s; reference sequence = %d; ",index.getIndexFile(),currentLocus.getContigIndex()); for(SAMReaderID reader: indexFiles.keySet()) nextFilePointer.addFileSpans(reader,new GATKBAMFileSpan()); } @@ -116,21 +186,13 @@ public class BAMScheduler implements Iterator { // Early exit if no bins were found. if(coveredRegion == null) { // for debugging only: maximum split is 16384. - if(currentLocus.size() > 16384) { - GenomeLoc[] splitContigs = currentLocus.split(currentLocus.getStart()+16384); - nextFilePointer.addLocation(splitContigs[0]); - currentLocus = splitContigs[1]; - } - else { - nextFilePointer.addLocation(currentLocus); - currentLocus = locusIterator.hasNext() ? locusIterator.next() : null; - } + nextFilePointer.addLocation(currentLocus); + currentLocus = locusIterator.hasNext() ? locusIterator.next() : null; continue; } // Early exit if only part of the first interval was found. if(currentLocus.startsBefore(coveredRegion)) { - // for debugging only: maximum split is 16384. int splitPoint = Math.min(coveredRegion.getStart()-currentLocus.getStart(),16384)+currentLocus.getStart(); GenomeLoc[] splitContigs = currentLocus.split(splitPoint); nextFilePointer.addLocation(splitContigs[0]); @@ -175,25 +237,30 @@ public class BAMScheduler implements Iterator { /** * Get the next overlapping tree of bins associated with the given BAM file. - * @param indices BAM indices. * @param currentLocus The actual locus for which to check overlap. * @return The next schedule entry overlapping with the given list of loci. */ - private BAMScheduleEntry getNextOverlappingBAMScheduleEntry(final Map indices, final GenomeLoc currentLocus) { + private BAMScheduleEntry getNextOverlappingBAMScheduleEntry(final GenomeLoc currentLocus) { + // Make sure that we consult the BAM header to ensure that we're using the correct contig index for this contig name. + // This will ensure that if the two sets of contigs don't quite match (b36 male vs female ref, hg19 Epstein-Barr), then + // we'll be using the correct contig index for the BAMs. + // TODO: Warning: assumes all BAMs use the same sequence dictionary! Get around this with contig aliasing. + final int currentContigIndex = dataSource.getHeader().getSequence(currentLocus.getContig()).getSequenceIndex(); + // Stale reference sequence or first invocation. (Re)create the binTreeIterator. - if(lastReferenceSequenceLoaded == null || lastReferenceSequenceLoaded != currentLocus.getContigIndex()) { + if(lastReferenceSequenceLoaded == null || lastReferenceSequenceLoaded != currentContigIndex) { if(bamScheduleIterator != null) bamScheduleIterator.close(); - lastReferenceSequenceLoaded = currentLocus.getContigIndex(); + lastReferenceSequenceLoaded = currentContigIndex; // Naive algorithm: find all elements in current contig for proper schedule creation. List lociInContig = new LinkedList(); for(GenomeLoc locus: loci) { - if(locus.getContigIndex() == lastReferenceSequenceLoaded) + if(dataSource.getHeader().getSequence(locus.getContig()).getSequenceIndex() == lastReferenceSequenceLoaded) lociInContig.add(locus); } - bamScheduleIterator = new PeekableIterator(new BAMSchedule(indices,lociInContig)); + bamScheduleIterator = new PeekableIterator(new BAMSchedule(dataSource,lociInContig)); } if(!bamScheduleIterator.hasNext()) @@ -209,4 +276,13 @@ public class BAMScheduler implements Iterator { return (bamScheduleEntry != null && bamScheduleEntry.overlaps(currentLocus)) ? bamScheduleEntry : null; } + /** + * Create a span from the given start point to the end of the file. + * @param startOfRegion Start of the region, in encoded coordinates (block start << 16 & block offset). + * @return A file span from the given point to the end of the file. + */ + private GATKBAMFileSpan createSpanToEndOfFile(final long startOfRegion) { + return new GATKBAMFileSpan(new GATKChunk(startOfRegion,Long.MAX_VALUE)); + } + } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java new file mode 100644 index 000000000..f468d2020 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Preloads BGZF blocks in preparation for unzipping and data processing. + * TODO: Right now, the block loader has all threads blocked waiting for a work request. Ultimately this should + * TODO: be replaced with a central thread management strategy. + */ +public class BGZFBlockLoadingDispatcher { + /** + * The file handle cache, used when allocating blocks from the dispatcher. + */ + private final FileHandleCache fileHandleCache; + + private final ExecutorService threadPool; + + private final Queue inputQueue; + + public BGZFBlockLoadingDispatcher(final int numThreads, final int numFileHandles) { + threadPool = Executors.newFixedThreadPool(numThreads); + fileHandleCache = new FileHandleCache(numFileHandles); + inputQueue = new LinkedList(); + + threadPool.execute(new BlockLoader(this,fileHandleCache,true)); + } + + /** + * Initiates a request for a new block load. + * @param readerPosition Position at which to load. + */ + void queueBlockLoad(final SAMReaderPosition readerPosition) { + synchronized(inputQueue) { + inputQueue.add(readerPosition); + inputQueue.notify(); + } + } + + /** + * Claims the next work request from the queue. + * @return The next work request, or null if none is available. + */ + SAMReaderPosition claimNextWorkRequest() { + synchronized(inputQueue) { + while(inputQueue.isEmpty()) { + try { + inputQueue.wait(); + } + catch(InterruptedException ex) { + throw new ReviewedStingException("Interrupt occurred waiting for next block reader work item"); + } + } + return inputQueue.poll(); + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java new file mode 100644 index 000000000..e377f865d --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import net.sf.samtools.GATKBAMFileSpan; +import net.sf.samtools.GATKChunk; +import net.sf.samtools.util.BAMInputStream; +import net.sf.samtools.util.BlockCompressedFilePointerUtil; +import net.sf.samtools.util.BlockCompressedInputStream; +import net.sf.samtools.util.RuntimeEOFException; +import net.sf.samtools.util.SeekableStream; +import org.broad.tribble.util.BlockCompressedStreamConstants; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.LinkedList; + +/** + * Presents decompressed blocks to the SAMFileReader. + */ +public class BlockInputStream extends SeekableStream implements BAMInputStream { + /** + * Mechanism for triggering block loads. + */ + private final BGZFBlockLoadingDispatcher dispatcher; + + /** + * The reader whose data is supplied by this input stream. + */ + private final SAMReaderID reader; + + /** + * Length of the input stream. + */ + private final long length; + + /** + * The latest error reported by an asynchronous block load. + */ + private Throwable error; + + /** + * Current position. + */ + private SAMReaderPosition position; + + /** + * A stream of compressed data blocks. + */ + private final ByteBuffer buffer; + + /** + * Offsets of the given blocks in the buffer. + */ + private LinkedList blockOffsets = new LinkedList(); + + /** + * Source positions of the given blocks in the buffer. + */ + private LinkedList blockPositions = new LinkedList(); + + /** + * Provides a lock to wait for more data to arrive. + */ + private final Object lock = new Object(); + + /** + * An input stream to use when comparing data back to what it should look like. + */ + private final BlockCompressedInputStream validatingInputStream; + + /** + * Has the buffer been filled since last request? + */ + private boolean bufferFilled = false; + + /** + * Create a new block presenting input stream with a dedicated buffer. + * @param dispatcher the block loading messenger. + * @param reader the reader for which to load data. + * @param validate validates the contents read into the buffer against the contents of a Picard BlockCompressedInputStream. + */ + BlockInputStream(final BGZFBlockLoadingDispatcher dispatcher, final SAMReaderID reader, final boolean validate) { + this.reader = reader; + this.length = reader.samFile.length(); + + buffer = ByteBuffer.wrap(new byte[64*1024]); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // The state of the buffer assumes that the range of data written into the buffer appears in the range + // [position,limit), while extra capacity exists in the range [limit,capacity) + buffer.limit(0); + + this.dispatcher = dispatcher; + // TODO: Kill the region when all we want to do is start at the beginning of the stream and run to the end of the stream. + this.position = new SAMReaderPosition(reader,this,new GATKBAMFileSpan(new GATKChunk(0,Long.MAX_VALUE))); + + try { + if(validate) { + System.out.printf("BlockInputStream %s: BGZF block validation mode activated%n",this); + validatingInputStream = new BlockCompressedInputStream(reader.samFile); + // A bug in ValidatingInputStream means that calling getFilePointer() immediately after initialization will result in an NPE. + // Poke the stream to start reading data. + validatingInputStream.available(); + } + else + validatingInputStream = null; + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + + public long length() { + return length; + } + + public long getFilePointer() { + long filePointer; + synchronized(lock) { + if(buffer.remaining() > 0) { + // If there's data in the buffer, figure out from whence it came. + final long blockAddress = blockPositions.size() > 0 ? blockPositions.get(0) : 0; + final int blockOffset = buffer.position(); + filePointer = blockAddress << 16 | blockOffset; + } + else { + // Otherwise, find the next position to load. + filePointer = position.getBlockAddress() << 16; + } + } + + if(validatingInputStream != null && filePointer != validatingInputStream.getFilePointer()) + throw new ReviewedStingException(String.format("Position of input stream is invalid; expected (block address, block offset) = (%d,%d), got (%d,%d)", + BlockCompressedFilePointerUtil.getBlockAddress(filePointer),BlockCompressedFilePointerUtil.getBlockOffset(filePointer), + BlockCompressedFilePointerUtil.getBlockAddress(validatingInputStream.getFilePointer()),BlockCompressedFilePointerUtil.getBlockOffset(validatingInputStream.getFilePointer()))); + + return filePointer; + } + + public void seek(long target) { + // TODO: Validate the seek point. + //System.out.printf("Thread %s, BlockInputStream %s: seeking to block %d, offset %d%n",Thread.currentThread().getId(),this,BlockCompressedFilePointerUtil.getBlockAddress(target),BlockCompressedFilePointerUtil.getBlockOffset(target)); + synchronized(lock) { + clearBuffers(); + position.advancePosition(BlockCompressedFilePointerUtil.getBlockAddress(target)); + waitForBufferFill(); + buffer.position(BlockCompressedFilePointerUtil.getBlockOffset(target)); + + if(validatingInputStream != null) { + try { + validatingInputStream.seek(target); + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + } + } + + private void clearBuffers() { + this.position.reset(); + + // Buffer semantics say that outside of a lock, buffer should always be prepared for reading. + // Indicate no data to be read. + buffer.clear(); + buffer.limit(0); + + blockOffsets.clear(); + blockPositions.clear(); + } + + public boolean eof() { + synchronized(lock) { + // TODO: Handle multiple empty BGZF blocks at end of the file. + return position != null && position.getBlockAddress() >= length; + } + } + + public void setCheckCrcs(final boolean check) { + // TODO: Implement + } + + /** + * Submits a new access plan for the given dataset. + * @param position The next seek point for BAM data in this reader. + */ + public void submitAccessPlan(final SAMReaderPosition position) { + //System.out.printf("Thread %s: submitting access plan for block at position: %d%n",Thread.currentThread().getId(),position.getBlockAddress()); + synchronized(lock) { + // Assume that the access plan is going to tell us to start where we are and move forward. + // If this isn't the case, we'll soon receive a seek request and the buffer will be forced to reset. + if(this.position != null && position.getBlockAddress() < this.position.getBlockAddress()) + position.advancePosition(this.position.getBlockAddress()); + } + this.position = position; + } + + private void compactBuffer() { + // Compact buffer to maximize storage space. + int bytesToRemove = 0; + + // Look ahead to see if we can compact away the first block in the series. + while(blockOffsets.size() > 1 && buffer.position() < blockOffsets.get(1)) { + bytesToRemove += blockOffsets.remove(); + blockPositions.remove(); + } + + // If we end up with an empty block at the end of the series, compact this as well. + if(buffer.remaining() == 0 && !blockOffsets.isEmpty() && buffer.position() >= blockOffsets.peek()) { + bytesToRemove += buffer.position(); + blockOffsets.remove(); + blockPositions.remove(); + } + + int finalBufferStart = buffer.position() - bytesToRemove; + int finalBufferSize = buffer.remaining(); + + buffer.position(bytesToRemove); + buffer.compact(); + + buffer.position(finalBufferStart); + buffer.limit(finalBufferStart+finalBufferSize); + } + + /** + * Push contents of incomingBuffer into the end of this buffer. + * MUST be called from a thread that is NOT the reader thread. + * @param incomingBuffer The data being pushed into this input stream. + * @param position target position for the data. + */ + public void copyIntoBuffer(final ByteBuffer incomingBuffer, final SAMReaderPosition position, final long filePosition) { + synchronized(lock) { + try { + compactBuffer(); + // Open up the buffer for more reading. + buffer.limit(buffer.capacity()); + + // Advance the position to take the most recent read into account. + long lastReadPosition = position.getBlockAddress(); + + byte[] validBytes = null; + if(validatingInputStream != null) { + validBytes = new byte[incomingBuffer.remaining()]; + + byte[] currentBytes = new byte[incomingBuffer.remaining()]; + int pos = incomingBuffer.position(); + int lim = incomingBuffer.limit(); + incomingBuffer.get(currentBytes); + + incomingBuffer.limit(lim); + incomingBuffer.position(pos); + + long currentFilePointer = validatingInputStream.getFilePointer(); + validatingInputStream.seek(lastReadPosition << 16); + validatingInputStream.read(validBytes); + validatingInputStream.seek(currentFilePointer); + + if(!Arrays.equals(validBytes,currentBytes)) + throw new ReviewedStingException(String.format("Bytes being inserted into BlockInputStream %s are incorrect",this)); + } + + this.position = position; + position.advancePosition(filePosition); + + if(buffer.remaining() < incomingBuffer.remaining()) { + //System.out.printf("Thread %s: waiting for available space in buffer; buffer remaining = %d, incoming buffer remaining = %d%n",Thread.currentThread().getId(),buffer.remaining(),incomingBuffer.remaining()); + lock.wait(); + //System.out.printf("Thread %s: waited for available space in buffer; buffer remaining = %d, incoming buffer remaining = %d%n", Thread.currentThread().getId(), buffer.remaining(), incomingBuffer.remaining()); + } + + // Queue list of block offsets / block positions. + blockOffsets.add(buffer.position()); + blockPositions.add(lastReadPosition); + + buffer.put(incomingBuffer); + + // Set up the buffer for reading. + buffer.flip(); + bufferFilled = true; + + lock.notify(); + } + catch(Exception ex) { + reportException(ex); + lock.notify(); + } + } + } + + void reportException(Throwable t) { + synchronized(lock) { + this.error = t; + lock.notify(); + } + } + + private void checkForErrors() { + synchronized(lock) { + if(error != null) { + ReviewedStingException toThrow = new ReviewedStingException(String.format("Thread %s, BlockInputStream %s: Unable to retrieve BAM data from disk",Thread.currentThread().getId(),this),error); + toThrow.setStackTrace(error.getStackTrace()); + throw toThrow; + } + } + } + + /** + * Reads the next byte of data from the input stream. + * @return Next byte of data, from 0->255, as an int. + */ + @Override + public int read() { + byte[] singleByte = new byte[1]; + read(singleByte); + return singleByte[0]; + } + + /** + * Fills the given byte array to the extent possible. + * @param bytes byte array to be filled. + * @return The number of bytes actually read. + */ + @Override + public int read(byte[] bytes) { + return read(bytes,0,bytes.length); + } + + @Override + public int read(byte[] bytes, final int offset, final int length) { + int remaining = length; + synchronized(lock) { + while(remaining > 0) { + // Check for error conditions during last read. + checkForErrors(); + + // If completely out of space, queue up another buffer fill. + waitForBufferFill(); + + // Couldn't manage to load any data at all; abort and return what's available. + if(buffer.remaining() == 0) + break; + + int numBytesToCopy = Math.min(buffer.remaining(),remaining); + buffer.get(bytes,length-remaining+offset,numBytesToCopy); + remaining -= numBytesToCopy; + + //if(remaining > 0) + // System.out.printf("Thread %s: read the first %d bytes of a %d byte request%n",Thread.currentThread().getId(),length-remaining,length); + // TODO: Assert that we don't copy across a block boundary + } + + // Notify any waiting threads that some of the contents of the buffer were removed. + if(length-remaining > 0) + lock.notify(); + } + + if(validatingInputStream != null) { + byte[] validBytes = new byte[length]; + try { + validatingInputStream.read(validBytes,offset,length); + for(int i = offset; i < offset+length; i++) { + if(bytes[i] != validBytes[i]) { + System.out.printf("Thread %s: preparing to throw an exception because contents don't match%n",Thread.currentThread().getId()); + throw new ReviewedStingException(String.format("Thread %s: blockInputStream %s attempting to return wrong set of bytes; mismatch at offset %d",Thread.currentThread().getId(),this,i)); + } + } + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + + return length - remaining; + } + + public void close() { + if(validatingInputStream != null) { + try { + validatingInputStream.close(); + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + } + + public String getSource() { + return reader.getSamFilePath(); + } + + private void waitForBufferFill() { + synchronized(lock) { + bufferFilled = false; + if(buffer.remaining() == 0 && !eof()) { + //System.out.printf("Thread %s is waiting for a buffer fill from position %d to buffer %s%n",Thread.currentThread().getId(),position.getBlockAddress(),this); + dispatcher.queueBlockLoad(position); + try { + lock.wait(); + } + catch(InterruptedException ex) { + // TODO: handle me. + throw new ReviewedStingException("Interrupt occurred waiting for buffer to fill",ex); + } + + if(bufferFilled && buffer.remaining() == 0) + throw new RuntimeEOFException("No more data left in InputStream"); + } + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java new file mode 100644 index 000000000..ab4299802 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import org.broad.tribble.util.BlockCompressedStreamConstants; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * An engine for loading blocks. + */ +class BlockLoader implements Runnable { + /** + * Coordinates the input queue. + */ + private BGZFBlockLoadingDispatcher dispatcher; + + /** + * A cache from which to retrieve open file handles. + */ + private final FileHandleCache fileHandleCache; + + /** + * Whether asynchronous decompression should happen. + */ + private final boolean decompress; + + /** + * An direct input buffer for incoming data from disk. + */ + private final ByteBuffer inputBuffer; + + public BlockLoader(final BGZFBlockLoadingDispatcher dispatcher, final FileHandleCache fileHandleCache, final boolean decompress) { + this.dispatcher = dispatcher; + this.fileHandleCache = fileHandleCache; + this.decompress = decompress; + + this.inputBuffer = ByteBuffer.allocateDirect(64*1024 + BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + inputBuffer.order(ByteOrder.LITTLE_ENDIAN); + } + + public void run() { + for(;;) { + SAMReaderPosition readerPosition = null; + try { + readerPosition = dispatcher.claimNextWorkRequest(); + FileInputStream inputStream = fileHandleCache.claimFileInputStream(readerPosition.getReader()); + + long blockAddress = readerPosition.getBlockAddress(); + //System.out.printf("Thread %s: BlockLoader: copying bytes from %s at position %d into %s%n",Thread.currentThread().getId(),inputStream,blockAddress,readerPosition.getInputStream()); + + ByteBuffer compressedBlock = readBGZFBlock(inputStream,readerPosition.getBlockAddress()); + long nextBlockAddress = position(inputStream); + fileHandleCache.releaseFileInputStream(readerPosition.getReader(),inputStream); + + ByteBuffer block = decompress ? decompressBGZFBlock(compressedBlock) : compressedBlock; + int bytesCopied = block.remaining(); + + BlockInputStream bamInputStream = readerPosition.getInputStream(); + bamInputStream.copyIntoBuffer(block,readerPosition,nextBlockAddress); + + //System.out.printf("Thread %s: BlockLoader: copied %d bytes from %s at position %d into %s%n",Thread.currentThread().getId(),bytesCopied,inputStream,blockAddress,readerPosition.getInputStream()); + } + catch(Throwable error) { + if(readerPosition != null && readerPosition.getInputStream() != null) + readerPosition.getInputStream().reportException(error); + } + } + + } + + private ByteBuffer readBGZFBlock(final FileInputStream inputStream, final long blockAddress) throws IOException { + FileChannel channel = inputStream.getChannel(); + + // Read the block header + channel.position(blockAddress); + + int uncompressedDataSize = 0; + int bufferSize = 0; + + do { + inputBuffer.clear(); + inputBuffer.limit(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + channel.read(inputBuffer); + + // Read out the size of the full BGZF block into a two bit short container, then 'or' that + // value into an int buffer to transfer the bitwise contents into an int. + inputBuffer.flip(); + if(inputBuffer.remaining() != BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH) + throw new ReviewedStingException("BUG: unable to read a the complete block header in one pass."); + + // Verify that the file was read at a valid point. + if(unpackUByte8(inputBuffer,0) != BlockCompressedStreamConstants.GZIP_ID1 || + unpackUByte8(inputBuffer,1) != BlockCompressedStreamConstants.GZIP_ID2 || + unpackUByte8(inputBuffer,3) != BlockCompressedStreamConstants.GZIP_FLG || + unpackUInt16(inputBuffer,10) != BlockCompressedStreamConstants.GZIP_XLEN || + unpackUByte8(inputBuffer,12) != BlockCompressedStreamConstants.BGZF_ID1 || + unpackUByte8(inputBuffer,13) != BlockCompressedStreamConstants.BGZF_ID2) { + throw new ReviewedStingException("BUG: Started reading compressed block at incorrect position"); + } + + inputBuffer.position(BlockCompressedStreamConstants.BLOCK_LENGTH_OFFSET); + bufferSize = unpackUInt16(inputBuffer,BlockCompressedStreamConstants.BLOCK_LENGTH_OFFSET)+1; + + // Adjust buffer limits and finish reading the block. Also read the next header, just in case there's a 0-byte block. + inputBuffer.limit(bufferSize); + inputBuffer.position(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + channel.read(inputBuffer); + + // Check the uncompressed length. If 0 and not at EOF, we'll want to check the next block. + uncompressedDataSize = inputBuffer.getInt(inputBuffer.limit()-4); + //System.out.printf("Uncompressed block size of the current block (at position %d) is %d%n",channel.position()-inputBuffer.limit(),uncompressedDataSize); + } + while(uncompressedDataSize == 0 && channel.position() < channel.size()); + + // Prepare the buffer for reading. + inputBuffer.flip(); + + return inputBuffer; + } + + private ByteBuffer decompressBGZFBlock(final ByteBuffer bgzfBlock) throws DataFormatException { + final int compressedBufferSize = bgzfBlock.remaining(); + + // Determine the uncompressed buffer size ( + bgzfBlock.position(bgzfBlock.limit()-4); + int uncompressedBufferSize = bgzfBlock.getInt(); + byte[] uncompressedContent = new byte[uncompressedBufferSize]; + + // Bound the CDATA section of the buffer. + bgzfBlock.limit(compressedBufferSize-BlockCompressedStreamConstants.BLOCK_FOOTER_LENGTH); + bgzfBlock.position(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + byte[] compressedContent = new byte[bgzfBlock.remaining()]; + ByteBuffer.wrap(compressedContent).put(bgzfBlock); + + // Decompress the buffer. + final Inflater inflater = new Inflater(true); + inflater.setInput(compressedContent); + int bytesUncompressed = inflater.inflate(uncompressedContent); + if(bytesUncompressed != uncompressedBufferSize) + throw new ReviewedStingException("Error decompressing block"); + + return ByteBuffer.wrap(uncompressedContent); + } + + private long position(final FileInputStream inputStream) throws IOException { + return inputStream.getChannel().position(); + } + + private int unpackUByte8(final ByteBuffer buffer,final int position) { + return buffer.get(position) & 0xFF; + } + + private int unpackUInt16(final ByteBuffer buffer,final int position) { + // Read out the size of the full BGZF block into a two bit short container, then 'or' that + // value into an int buffer to transfer the bitwise contents into an int. + return buffer.getShort(position) & 0xFFFF; + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java new file mode 100644 index 000000000..29de6eb37 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.StingException; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +/** + * Caches frequently used file handles. Right now, caches only a single file handle. + * TODO: Generalize to support arbitrary file handle caches. + */ +public class FileHandleCache { + /** + * The underlying data structure storing file handles. + */ + private final FileHandleStorage fileHandleStorage; + + /** + * How many file handles should be kept open at once. + */ + private final int cacheSize; + + /** + * A uniquifier: assign a unique ID to every instance of a file handle. + */ + private final Map keyCounter = new HashMap(); + + /** + * A shared lock, private so that outside users cannot notify it. + */ + private final Object lock = new Object(); + + /** + * Indicates how many file handles are outstanding at this point. + */ + private int numOutstandingFileHandles = 0; + + /** + * Create a new file handle cache of the given cache size. + * @param cacheSize how many readers to hold open at once. + */ + public FileHandleCache(final int cacheSize) { + this.cacheSize = cacheSize; + fileHandleStorage = new FileHandleStorage(); + } + + /** + * Retrieves or opens a file handle for the given reader ID. + * @param key The ke + * @return A file input stream from the cache, if available, or otherwise newly opened. + */ + public FileInputStream claimFileInputStream(final SAMReaderID key) { + synchronized(lock) { + FileInputStream inputStream = findExistingEntry(key); + if(inputStream == null) { + try { + // If the cache is maxed out, wait for another file handle to emerge. + if(numOutstandingFileHandles >= cacheSize) + lock.wait(); + } + catch(InterruptedException ex) { + throw new ReviewedStingException("Interrupted while waiting for a file handle"); + } + inputStream = openInputStream(key); + } + numOutstandingFileHandles++; + + //System.out.printf("Handing input stream %s to thread %s%n",inputStream,Thread.currentThread().getId()); + return inputStream; + } + } + + /** + * Releases the current reader and returns it to the cache. + * @param key The reader. + * @param inputStream The stream being used. + */ + public void releaseFileInputStream(final SAMReaderID key, final FileInputStream inputStream) { + synchronized(lock) { + numOutstandingFileHandles--; + UniqueKey newID = allocateKey(key); + fileHandleStorage.put(newID,inputStream); + // Let any listeners know that another file handle has become available. + lock.notify(); + } + } + + /** + * Finds an existing entry in the storage mechanism. + * @param key Reader. + * @return a cached stream, if available. Otherwise, + */ + private FileInputStream findExistingEntry(final SAMReaderID key) { + int existingHandles = getMostRecentUniquifier(key); + + // See if any of the keys currently exist in the repository. + for(int i = 0; i <= existingHandles; i++) { + UniqueKey uniqueKey = new UniqueKey(key,i); + if(fileHandleStorage.containsKey(uniqueKey)) + return fileHandleStorage.remove(uniqueKey); + } + + return null; + } + + /** + * Gets the most recent uniquifier used for the given reader. + * @param reader Reader for which to determine uniqueness. + * @return + */ + private int getMostRecentUniquifier(final SAMReaderID reader) { + if(keyCounter.containsKey(reader)) + return keyCounter.get(reader); + else return -1; + } + + private UniqueKey allocateKey(final SAMReaderID reader) { + int uniquifier = getMostRecentUniquifier(reader)+1; + keyCounter.put(reader,uniquifier); + return new UniqueKey(reader,uniquifier); + } + + private FileInputStream openInputStream(final SAMReaderID reader) { + try { + return new FileInputStream(reader.getSamFilePath()); + } + catch(IOException ex) { + throw new StingException("Unable to open input file"); + } + } + + private void closeInputStream(final FileInputStream inputStream) { + try { + inputStream.close(); + } + catch(IOException ex) { + throw new StingException("Unable to open input file"); + } + } + + /** + * Actually contains the file handles, purging them as they get too old. + */ + private class FileHandleStorage extends LinkedHashMap { + /** + * Remove the oldest entry + * @param entry Entry to consider removing. + * @return True if the cache size has been exceeded. False otherwise. + */ + @Override + protected boolean removeEldestEntry(Map.Entry entry) { + synchronized (lock) { + if(size() > cacheSize) { + keyCounter.put(entry.getKey().key,keyCounter.get(entry.getKey().key)-1); + closeInputStream(entry.getValue()); + + return true; + } + } + return false; + } + } + + /** + * Uniquifies a key by adding a numerical uniquifier. + */ + private class UniqueKey { + /** + * The file handle's key. + */ + private final SAMReaderID key; + + /** + * A uniquifier, so that multiple of the same reader can exist in the cache. + */ + private final int uniqueID; + + public UniqueKey(final SAMReaderID reader, final int uniqueID) { + this.key = reader; + this.uniqueID = uniqueID; + } + + @Override + public boolean equals(Object other) { + if(!(other instanceof UniqueKey)) + return false; + UniqueKey otherUniqueKey = (UniqueKey)other; + return key.equals(otherUniqueKey.key) && this.uniqueID == otherUniqueKey.uniqueID; + } + + @Override + public int hashCode() { + return key.hashCode(); + } + } + + + +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java index e4141f61c..df7827250 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java @@ -29,6 +29,7 @@ import net.sf.samtools.GATKBAMFileSpan; import net.sf.samtools.SAMFileSpan; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.interval.IntervalMergingRule; import org.broadinstitute.sting.utils.interval.IntervalUtils; @@ -40,28 +41,25 @@ import java.util.*; */ public class FilePointer { protected final SortedMap fileSpans = new TreeMap(); - protected final BAMOverlap overlap; - protected final List locations; + protected final List locations = new ArrayList(); /** * Does this file pointer point into an unmapped region? */ protected final boolean isRegionUnmapped; - public FilePointer() { - this((BAMOverlap)null); - } - - public FilePointer(final GenomeLoc location) { - this.overlap = null; - this.locations = Collections.singletonList(location); - this.isRegionUnmapped = GenomeLoc.isUnmapped(location); - } - - public FilePointer(final BAMOverlap overlap) { - this.overlap = overlap; - this.locations = new ArrayList(); - this.isRegionUnmapped = false; + public FilePointer(final GenomeLoc... locations) { + this.locations.addAll(Arrays.asList(locations)); + boolean foundMapped = false, foundUnmapped = false; + for(GenomeLoc location: locations) { + if(GenomeLoc.isUnmapped(location)) + foundUnmapped = true; + else + foundMapped = true; + } + if(foundMapped && foundUnmapped) + throw new ReviewedStingException("BUG: File pointers cannot be mixed mapped/unmapped."); + this.isRegionUnmapped = foundUnmapped; } /** @@ -217,4 +215,20 @@ public class FilePointer { fileSpan = fileSpan.union((GATKBAMFileSpan)iterators[i].next().getValue()); combined.addFileSpans(initialElement.getKey(),fileSpan); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FilePointer:%n"); + builder.append("\tlocations = {"); + builder.append(Utils.join(";",locations)); + builder.append("}%n\tregions = %n"); + for(Map.Entry entry: fileSpans.entrySet()) { + builder.append(entry.getKey()); + builder.append("= {"); + builder.append(entry.getValue()); + builder.append("}"); + } + return builder.toString(); + } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java index 4ddf28dce..f78693c27 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java @@ -25,419 +25,58 @@ package org.broadinstitute.sting.gatk.datasources.reads; import net.sf.picard.util.PeekableIterator; -import net.sf.samtools.AbstractBAMFileIndex; -import net.sf.samtools.Bin; -import net.sf.samtools.BrowseableBAMIndex; -import net.sf.samtools.SAMSequenceRecord; -import org.apache.log4j.Logger; -import org.broadinstitute.sting.utils.GenomeLoc; +import net.sf.samtools.SAMSequenceDictionary; +import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.GenomeLocSortedSet; -import org.broadinstitute.sting.utils.collections.Pair; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import java.util.*; +import java.util.Iterator; /** - * Shard intervals based on position within the BAM file. - * - * @author mhanna - * @version 0.1 + * Handles the process of aggregating BAM intervals into individual shards. + * TODO: The task performed by IntervalSharder is now better performed by LocusShardBalancer. Merge BAMScheduler and IntervalSharder. */ -public class IntervalSharder { - private static Logger logger = Logger.getLogger(IntervalSharder.class); +public class IntervalSharder implements Iterator { + /** + * The iterator actually laying out the data for BAM scheduling. + */ + private final PeekableIterator wrappedIterator; - public static Iterator shardIntervals(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { - return new IntervalSharder.FilePointerIterator(dataSource,loci); + /** + * The parser, for interval manipulation. + */ + private final GenomeLocParser parser; + + public static IntervalSharder shardOverAllReads(final SAMDataSource dataSource, final GenomeLocParser parser) { + return new IntervalSharder(BAMScheduler.createOverAllReads(dataSource,parser),parser); + } + + public static IntervalSharder shardOverMappedReads(final SAMDataSource dataSource, final SAMSequenceDictionary sequenceDictionary, final GenomeLocParser parser) { + return new IntervalSharder(BAMScheduler.createOverMappedReads(dataSource,sequenceDictionary,parser),parser); + } + + public static IntervalSharder shardOverIntervals(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { + return new IntervalSharder(BAMScheduler.createOverIntervals(dataSource,loci),loci.getGenomeLocParser()); + } + + private IntervalSharder(final BAMScheduler scheduler, final GenomeLocParser parser) { + wrappedIterator = new PeekableIterator(scheduler); + this.parser = parser; + } + + public boolean hasNext() { + return wrappedIterator.hasNext(); } /** - * A lazy-loading iterator over file pointers. + * Accumulate shards where there's no additional cost to processing the next shard in the sequence. + * @return The next file pointer to process. */ - private static class FilePointerIterator implements Iterator { - final SAMDataSource dataSource; - final GenomeLocSortedSet loci; - final PeekableIterator locusIterator; - final Queue cachedFilePointers = new LinkedList(); - - public FilePointerIterator(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { - this.dataSource = dataSource; - this.loci = loci; - locusIterator = new PeekableIterator(loci.iterator()); - advance(); - } - - public boolean hasNext() { - return !cachedFilePointers.isEmpty(); - } - - public FilePointer next() { - if(!hasNext()) - throw new NoSuchElementException("FilePointerIterator iteration is complete"); - FilePointer filePointer = cachedFilePointers.remove(); - if(cachedFilePointers.isEmpty()) - advance(); - return filePointer; - } - - public void remove() { - throw new UnsupportedOperationException("Cannot remove from a FilePointerIterator"); - } - - private void advance() { - GenomeLocSortedSet nextBatch = new GenomeLocSortedSet(loci.getGenomeLocParser()); - String contig = null; - - // If the next section of the BAM to be processed is unmapped, handle this region separately. - while(locusIterator.hasNext() && nextBatch.isEmpty()) { - contig = null; - while(locusIterator.hasNext() && (contig == null || (!GenomeLoc.isUnmapped(locusIterator.peek()) && locusIterator.peek().getContig().equals(contig)))) { - GenomeLoc nextLocus = locusIterator.next(); - contig = nextLocus.getContig(); - nextBatch.add(nextLocus); - } - } - - if(nextBatch.size() > 0) { - cachedFilePointers.addAll(shardIntervalsOnContig(dataSource,contig,nextBatch)); - } - } + public FilePointer next() { + FilePointer current = wrappedIterator.next(); + while(wrappedIterator.hasNext() && current.isRegionUnmapped == wrappedIterator.peek().isRegionUnmapped && current.minus(wrappedIterator.peek()) == 0) + current = current.combine(parser,wrappedIterator.next()); + return current; } - /** - * Merge / split intervals based on an awareness of the structure of the BAM file. - * @param dataSource - * @param contig Contig against which to align the intervals. If null, create a file pointer across unmapped reads. - * @param loci - * @return - */ - private static List shardIntervalsOnContig(final SAMDataSource dataSource, final String contig, final GenomeLocSortedSet loci) { - // If the contig is null, eliminate the chopping process and build out a file pointer consisting of the unmapped region of all BAMs. - if(contig == null) { - FilePointer filePointer = new FilePointer(GenomeLoc.UNMAPPED); - for(SAMReaderID id: dataSource.getReaderIDs()) - filePointer.addFileSpans(id,null); - return Collections.singletonList(filePointer); - } - - // Gather bins for the given loci, splitting loci as necessary so that each falls into exactly one lowest-level bin. - List filePointers = new ArrayList(); - FilePointer lastFilePointer = null; - BAMOverlap lastBAMOverlap = null; - - Map readerToIndexMap = new HashMap(); - IntervalSharder.BinMergingIterator binMerger = new IntervalSharder.BinMergingIterator(); - for(SAMReaderID id: dataSource.getReaderIDs()) { - final SAMSequenceRecord referenceSequence = dataSource.getHeader(id).getSequence(contig); - // If this contig can't be found in the reference, skip over it. - if(referenceSequence == null && contig != null) - continue; - final BrowseableBAMIndex index = (BrowseableBAMIndex)dataSource.getIndex(id); - binMerger.addReader(id, - index, - referenceSequence.getSequenceIndex(), - index.getBinsOverlapping(referenceSequence.getSequenceIndex(),1,referenceSequence.getSequenceLength()).iterator()); - // Cache the reader for later data lookup. - readerToIndexMap.put(id,index); - } - - PeekableIterator binIterator = new PeekableIterator(binMerger); - - for(GenomeLoc location: loci) { - if(!location.getContig().equals(contig)) - throw new ReviewedStingException("Location outside bounds of contig"); - - if(!binIterator.hasNext()) - break; - - int locationStart = location.getStart(); - final int locationStop = location.getStop(); - - // Advance to first bin. - while(binIterator.peek().stop < locationStart) - binIterator.next(); - - // Add all relevant bins to a list. If the given bin extends beyond the end of the current interval, make - // sure the extending bin is not pruned from the list. - List bamOverlaps = new ArrayList(); - while(binIterator.hasNext() && binIterator.peek().stop <= locationStop) - bamOverlaps.add(binIterator.next()); - if(binIterator.hasNext() && binIterator.peek().start <= locationStop) - bamOverlaps.add(binIterator.peek()); - - // Bins found; try to match bins with locations. - Iterator bamOverlapIterator = bamOverlaps.iterator(); - - while(locationStop >= locationStart) { - int binStart = lastFilePointer!=null ? lastFilePointer.overlap.start : 0; - int binStop = lastFilePointer!=null ? lastFilePointer.overlap.stop : 0; - - while(binStop < locationStart && bamOverlapIterator.hasNext()) { - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) - filePointers.add(lastFilePointer); - - lastBAMOverlap = bamOverlapIterator.next(); - lastFilePointer = new FilePointer(lastBAMOverlap); - binStart = lastFilePointer.overlap.start; - binStop = lastFilePointer.overlap.stop; - } - - if(locationStart < binStart) { - // The region starts before the first bin in the sequence. Add the region occurring before the sequence. - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) { - filePointers.add(lastFilePointer); - lastFilePointer = null; - lastBAMOverlap = null; - } - - final int regionStop = Math.min(locationStop,binStart-1); - - GenomeLoc subset = loci.getGenomeLocParser().createGenomeLoc(location.getContig(),locationStart,regionStop); - lastFilePointer = new FilePointer(subset); - - locationStart = regionStop + 1; - } - else if(locationStart > binStop) { - // The region starts after the last bin in the sequence. Add the region occurring after the sequence. - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) { - filePointers.add(lastFilePointer); - lastFilePointer = null; - lastBAMOverlap = null; - } - - GenomeLoc subset = loci.getGenomeLocParser().createGenomeLoc(location.getContig(),locationStart,locationStop); - filePointers.add(new FilePointer(subset)); - - locationStart = locationStop + 1; - } - else { - if(lastFilePointer == null) - throw new ReviewedStingException("Illegal state: initializer failed to create cached file pointer."); - - // The start of the region overlaps the bin. Add the overlapping subset. - final int regionStop = Math.min(locationStop,binStop); - lastFilePointer.addLocation(loci.getGenomeLocParser().createGenomeLoc(location.getContig(),locationStart,regionStop)); - locationStart = regionStop + 1; - } - } - } - - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) - filePointers.add(lastFilePointer); - - // Lookup the locations for every file pointer in the index. - for(SAMReaderID id: readerToIndexMap.keySet()) { - BrowseableBAMIndex index = readerToIndexMap.get(id); - for(FilePointer filePointer: filePointers) - filePointer.addFileSpans(id,index.getSpanOverlapping(filePointer.overlap.getBin(id))); - } - - return filePointers; - } - - private static class BinMergingIterator implements Iterator { - private PriorityQueue binQueue = new PriorityQueue(); - private Queue pendingOverlaps = new LinkedList(); - - public void addReader(final SAMReaderID id, final BrowseableBAMIndex index, final int referenceSequence, Iterator bins) { - binQueue.add(new BinQueueState(id,index,referenceSequence,new IntervalSharder.LowestLevelBinFilteringIterator(index,bins))); - } - - public boolean hasNext() { - return pendingOverlaps.size() > 0 || !binQueue.isEmpty(); - } - - public BAMOverlap next() { - if(!hasNext()) - throw new NoSuchElementException("No elements left in merging iterator"); - if(pendingOverlaps.isEmpty()) - advance(); - return pendingOverlaps.remove(); - } - - public void advance() { - List bins = new ArrayList(); - int boundsStart, boundsStop; - - // Prime the pump - if(binQueue.isEmpty()) - return; - bins.add(getNextBin()); - boundsStart = bins.get(0).getStart(); - boundsStop = bins.get(0).getStop(); - - // Accumulate all the bins that overlap the current bin, in sorted order. - while(!binQueue.isEmpty() && peekNextBin().getStart() <= boundsStop) { - ReaderBin bin = getNextBin(); - bins.add(bin); - boundsStart = Math.min(boundsStart,bin.getStart()); - boundsStop = Math.max(boundsStop,bin.getStop()); - } - - List> range = new ArrayList>(); - int start = bins.get(0).getStart(); - int stop = bins.get(0).getStop(); - while(start <= boundsStop) { - // Find the next stopping point. - for(ReaderBin bin: bins) { - stop = Math.min(stop,bin.getStop()); - if(start < bin.getStart()) - stop = Math.min(stop,bin.getStart()-1); - } - - range.add(new Pair(start,stop)); - // If the last entry added included the last element, stop. - if(stop >= boundsStop) - break; - - // Find the next start. - start = stop + 1; - for(ReaderBin bin: bins) { - if(start >= bin.getStart() && start <= bin.getStop()) - break; - else if(start < bin.getStart()) { - start = bin.getStart(); - break; - } - } - } - - // Add the next series of BAM overlaps to the window. - for(Pair window: range) { - BAMOverlap bamOverlap = new BAMOverlap(window.first,window.second); - for(ReaderBin bin: bins) - bamOverlap.addBin(bin.id,bin.bin); - pendingOverlaps.add(bamOverlap); - } - } - - public void remove() { throw new UnsupportedOperationException("Cannot remove from a merging iterator."); } - - private ReaderBin peekNextBin() { - if(binQueue.isEmpty()) - throw new NoSuchElementException("No more bins are available"); - BinQueueState current = binQueue.peek(); - return new ReaderBin(current.getReaderID(),current.getIndex(),current.getReferenceSequence(),current.peekNextBin()); - } - - private ReaderBin getNextBin() { - if(binQueue.isEmpty()) - throw new NoSuchElementException("No more bins are available"); - BinQueueState current = binQueue.remove(); - ReaderBin readerBin = new ReaderBin(current.getReaderID(),current.getIndex(),current.getReferenceSequence(),current.nextBin()); - if(current.hasNextBin()) - binQueue.add(current); - return readerBin; - } - - } - - /** - * Filters out bins not at the lowest level in the tree. - */ - private static class LowestLevelBinFilteringIterator implements Iterator { - private BrowseableBAMIndex index; - private Iterator wrappedIterator; - - private Bin nextBin; - - public LowestLevelBinFilteringIterator(final BrowseableBAMIndex index, Iterator iterator) { - this.index = index; - this.wrappedIterator = iterator; - advance(); - } - - public boolean hasNext() { - return nextBin != null; - } - - public Bin next() { - Bin bin = nextBin; - advance(); - return bin; - } - - public void remove() { throw new UnsupportedOperationException("Remove operation is not supported"); } - - private void advance() { - nextBin = null; - while(wrappedIterator.hasNext() && nextBin == null) { - Bin bin = wrappedIterator.next(); - if(index.getLevelForBin(bin) == AbstractBAMFileIndex.getNumIndexLevels()-1) - nextBin = bin; - } - } - } + public void remove() { throw new UnsupportedOperationException("Unable to remove from an interval sharder."); } } - -class BinQueueState implements Comparable { - private final SAMReaderID id; - private final BrowseableBAMIndex index; - private final int referenceSequence; - private final PeekableIterator bins; - - private int firstLocusInCurrentBin; - private int lastLocusInCurrentBin; - - public BinQueueState(final SAMReaderID id, final BrowseableBAMIndex index, final int referenceSequence, final Iterator bins) { - this.id = id; - this.index = index; - this.referenceSequence = referenceSequence; - this.bins = new PeekableIterator(bins); - refreshLocusInBinCache(); - } - - public SAMReaderID getReaderID() { - return id; - } - - public BrowseableBAMIndex getIndex() { - return index; - } - - public int getReferenceSequence() { - return referenceSequence; - } - - public boolean hasNextBin() { - return bins.hasNext(); - } - - public Bin peekNextBin() { - return bins.peek(); - } - - public Bin nextBin() { - Bin nextBin = bins.next(); - refreshLocusInBinCache(); - return nextBin; - } - - public int compareTo(org.broadinstitute.sting.gatk.datasources.reads.BinQueueState other) { - if(!this.bins.hasNext() && !other.bins.hasNext()) return 0; - if(!this.bins.hasNext()) return -1; - if(!this.bins.hasNext()) return 1; - - // Both BinQueueStates have next bins. Before proceeding, make sure the bin cache is valid. - if(this.firstLocusInCurrentBin <= 0 || this.lastLocusInCurrentBin <= 0 || - other.firstLocusInCurrentBin <= 0 || other.lastLocusInCurrentBin <= 0) { - throw new ReviewedStingException("Sharding mechanism error - bin->locus cache is invalid."); - } - - // Straight integer subtraction works here because lhsStart, rhsStart always positive. - if(this.firstLocusInCurrentBin != other.firstLocusInCurrentBin) - return this.firstLocusInCurrentBin - other.firstLocusInCurrentBin; - - // Straight integer subtraction works here because lhsStop, rhsStop always positive. - return this.lastLocusInCurrentBin - other.lastLocusInCurrentBin; - } - - private void refreshLocusInBinCache() { - firstLocusInCurrentBin = -1; - lastLocusInCurrentBin = -1; - if(bins.hasNext()) { - Bin bin = bins.peek(); - firstLocusInCurrentBin = index.getFirstLocusInBin(bin); - lastLocusInCurrentBin = index.getLastLocusInBin(bin); - } - } -} \ No newline at end of file diff --git a/public/java/src/net/sf/samtools/GATKBinList.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java similarity index 52% rename from public/java/src/net/sf/samtools/GATKBinList.java rename to public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java index b53062aaf..585b63457 100644 --- a/public/java/src/net/sf/samtools/GATKBinList.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java @@ -22,30 +22,34 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package net.sf.samtools; +package org.broadinstitute.sting.gatk.datasources.reads; -import java.util.BitSet; +import java.util.Iterator; /** - * A temporary solution to work around Java access rights issues: - * override chunk and make it public. - * TODO: Eliminate once we determine the final fate of the BAM index reading code. + * Batch granular file pointers into potentially larger shards. */ -public class GATKBinList extends BinList { +public class LocusShardBalancer extends ShardBalancer { /** - * Create a new BinList over sequenceCount sequences, consisting of the given bins. - * @param referenceSequence Reference sequence to which these bins are relevant. - * @param bins The given bins to include. + * Convert iterators of file pointers into balanced iterators of shards. + * @return An iterator over balanced shards. */ - public GATKBinList(final int referenceSequence, final BitSet bins) { - super(referenceSequence,bins); - } + public Iterator iterator() { + return new Iterator() { + public boolean hasNext() { + return filePointers.hasNext(); + } - /** - * Retrieves the bins stored in this list. - * @return A bitset where a bin is present in the list if the bit is true. - */ - public BitSet getBins() { - return super.getBins(); + public Shard next() { + FilePointer current = filePointers.next(); + while(filePointers.hasNext() && current.minus(filePointers.peek()) == 0) + current = current.combine(parser,filePointers.next()); + return new LocusShard(parser,readsDataSource,current.getLocations(),current.fileSpans); + } + + public void remove() { + throw new UnsupportedOperationException("Unable to remove from shard balancing iterator"); + } + }; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java deleted file mode 100755 index a5ca07853..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.picard.reference.IndexedFastaSequenceFile; -import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMFileSpan; -import net.sf.samtools.SAMSequenceRecord; -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * A sharding strategy for loci based on reading of the index. - */ -public class LocusShardStrategy implements ShardStrategy { - /** - * The data source to use when performing this sharding. - */ - private final SAMDataSource reads; - - /** - * the parser for creating shards - */ - private GenomeLocParser genomeLocParser; - - /** - * An iterator through the available file pointers. - */ - private final Iterator filePointerIterator; - - /** - * construct the shard strategy from a seq dictionary, a shard size, and and genomeLocs - * @param reads Data source from which to load index data. - * @param locations List of locations for which to load data. - */ - public LocusShardStrategy(SAMDataSource reads, IndexedFastaSequenceFile reference, GenomeLocParser genomeLocParser, GenomeLocSortedSet locations) { - this.reads = reads; - this.genomeLocParser = genomeLocParser; - - if(!reads.isEmpty()) { - GenomeLocSortedSet intervals; - if(locations == null) { - // If no locations were passed in, shard the entire BAM file. - SAMFileHeader header = reads.getHeader(); - intervals = new GenomeLocSortedSet(genomeLocParser); - - for(SAMSequenceRecord readsSequenceRecord: header.getSequenceDictionary().getSequences()) { - // Check this sequence against the reference sequence dictionary. - // TODO: Do a better job of merging reads + reference. - SAMSequenceRecord refSequenceRecord = reference.getSequenceDictionary().getSequence(readsSequenceRecord.getSequenceName()); - if(refSequenceRecord != null) { - final int length = Math.min(readsSequenceRecord.getSequenceLength(),refSequenceRecord.getSequenceLength()); - intervals.add(genomeLocParser.createGenomeLoc(readsSequenceRecord.getSequenceName(),1,length)); - } - } - } - else - intervals = locations; - - if(reads.isLowMemoryShardingEnabled()) { - /* - Iterator filePointerIterator = new LowMemoryIntervalSharder(this.reads,intervals); - List filePointers = new ArrayList(); - while(filePointerIterator.hasNext()) - filePointers.add(filePointerIterator.next()); - this.filePointerIterator = filePointers.iterator(); - */ - this.filePointerIterator = new LowMemoryIntervalSharder(this.reads,intervals); - } - else - this.filePointerIterator = IntervalSharder.shardIntervals(this.reads,intervals); - } - else { - final int maxShardSize = 100000; - List filePointers = new ArrayList(); - if(locations == null) { - for(SAMSequenceRecord refSequenceRecord: reference.getSequenceDictionary().getSequences()) { - for(int shardStart = 1; shardStart <= refSequenceRecord.getSequenceLength(); shardStart += maxShardSize) { - final int shardStop = Math.min(shardStart+maxShardSize-1, refSequenceRecord.getSequenceLength()); - filePointers.add(new FilePointer(genomeLocParser.createGenomeLoc(refSequenceRecord.getSequenceName(),shardStart,shardStop))); - } - } - } - else { - for(GenomeLoc interval: locations) { - while(interval.size() > maxShardSize) { - filePointers.add(new FilePointer(locations.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart(),interval.getStart()+maxShardSize-1))); - interval = locations.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart()+maxShardSize,interval.getStop()); - } - filePointers.add(new FilePointer(interval)); - } - } - filePointerIterator = filePointers.iterator(); - } - - } - - /** - * returns true if there are additional shards - * - * @return false if we're done processing shards - */ - public boolean hasNext() { - return filePointerIterator.hasNext(); - } - - public long shardNumber = 0; - - /** - * gets the next Shard - * - * @return the next shard - */ - public LocusShard next() { - FilePointer nextFilePointer = filePointerIterator.next(); - Map fileSpansBounding = nextFilePointer.fileSpans != null ? nextFilePointer.fileSpans : null; - - /* - System.out.printf("Shard %d: interval = {",++shardNumber); - for(GenomeLoc locus: nextFilePointer.locations) - System.out.printf("%s;",locus); - System.out.printf("}; "); - - if(fileSpansBounding == null) - System.out.printf("no shard data%n"); - else { - SortedMap sortedSpans = new TreeMap(fileSpansBounding); - for(Map.Entry entry: sortedSpans.entrySet()) { - System.out.printf("Shard %d:%s = {%s}%n",shardNumber,entry.getKey().samFile,entry.getValue()); - } - } - */ - - return new LocusShard(genomeLocParser, reads,nextFilePointer.locations,fileSpansBounding); - } - - /** we don't support the remove command */ - public void remove() { - throw new UnsupportedOperationException("ShardStrategies don't support remove()"); - } - - /** - * makes the IntervalShard iterable, i.e. usable in a for loop. - * - * @return - */ - public Iterator iterator() { - return this; - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java deleted file mode 100644 index bf5f33dc3..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2011, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.picard.util.PeekableIterator; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; - -import java.util.Iterator; - -/** - * Handles the process of aggregating BAM intervals into individual shards. - */ -public class LowMemoryIntervalSharder implements Iterator { - /** - * The iterator actually laying out the data for BAM scheduling. - */ - private final PeekableIterator wrappedIterator; - - /** - * The parser, for interval manipulation. - */ - private final GenomeLocParser parser; - - public LowMemoryIntervalSharder(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { - wrappedIterator = new PeekableIterator(new BAMScheduler(dataSource,loci)); - parser = loci.getGenomeLocParser(); - } - - public boolean hasNext() { - return wrappedIterator.hasNext(); - } - - /** - * Accumulate shards where there's no additional cost to processing the next shard in the sequence. - * @return The next file pointer to process. - */ - public FilePointer next() { - FilePointer current = wrappedIterator.next(); - while(wrappedIterator.hasNext() && current.isRegionUnmapped == wrappedIterator.peek().isRegionUnmapped && current.minus(wrappedIterator.peek()) == 0) - current = current.combine(parser,wrappedIterator.next()); - return current; - } - - public void remove() { throw new UnsupportedOperationException("Unable to remove from an interval sharder."); } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java deleted file mode 100644 index 278eeb898..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -import java.util.List; - -/** - * A single, monolithic shard bridging all available data. - * @author mhanna - * @version 0.1 - */ -public class MonolithicShard extends Shard { - /** - * Creates a new monolithic shard of the given type. - * @param shardType Type of the shard. Must be either read or locus; cannot be intervalic. - * @param locs Intervals that this monolithic shard should process. - */ - public MonolithicShard(GenomeLocParser parser, SAMDataSource readsDataSource, ShardType shardType, List locs) { - super(parser, shardType, locs, readsDataSource, null, false); - if(shardType != ShardType.LOCUS && shardType != ShardType.READ) - throw new ReviewedStingException("Invalid shard type for monolithic shard: " + shardType); - } - - /** - * String representation of this shard. - * @return "entire genome". - */ - @Override - public String toString() { - return "entire genome"; - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java deleted file mode 100644 index 28b737f28..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; - -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -/** - * Create a giant shard representing all the data in the input BAM(s). - * - * @author mhanna - * @version 0.1 - */ -public class MonolithicShardStrategy implements ShardStrategy { - /** - * The single shard associated with this sharding strategy. - */ - private MonolithicShard shard; - - /** - * Create a new shard strategy for shards of the given type. - * @param shardType The shard type. - */ - public MonolithicShardStrategy(final GenomeLocParser parser, final SAMDataSource readsDataSource, final Shard.ShardType shardType, final List region) { - shard = new MonolithicShard(parser,readsDataSource,shardType,region); - } - - /** - * Convenience for using in a foreach loop. Will NOT create a new, reset instance of the iterator; - * will only return another copy of the active iterator. - * @return A copy of this. - */ - public Iterator iterator() { - return this; - } - - /** - * Returns true if the monolithic shard has not yet been consumed, or false otherwise. - * @return True if shard has been consumed, false otherwise. - */ - public boolean hasNext() { - return shard != null; - } - - /** - * Returns the monolithic shard if it has not already been retrieved. - * @return The monolithic shard. - * @throws NoSuchElementException if no such data exists. - */ - public Shard next() { - if(shard == null) - throw new NoSuchElementException("Monolithic shard has already been retrived."); - - Shard working = shard; - shard = null; - return working; - } - - /** - * Mandated by the interface, but is unsupported in this context. Will throw an exception always. - */ - public void remove() { - throw new UnsupportedOperationException("Cannot remove from a shard strategy"); - } - - /** - * Mandated by the interface, but is unsupported in this context. Will throw an exception always. - * @param size adjust the next size to this - */ - public void adjustNextShardSize( long size ) { - throw new UnsupportedOperationException("Cannot adjust the next size of a monolithic shard; there will be no next shard."); - } - -} - diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java index 4d9c9092d..5f40c0ea5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java @@ -35,10 +35,15 @@ import java.util.Map; * @version 0.1 */ public class ReadShard extends Shard { + /** + * What is the maximum number of reads which should go into a read shard. + */ + public static final int MAX_READS = 10000; + /** * The reads making up this shard. */ - private final Collection reads = new ArrayList(ReadShardStrategy.MAX_READS); + private final Collection reads = new ArrayList(MAX_READS); public ReadShard(GenomeLocParser parser, SAMDataSource readsDataSource, Map fileSpans, List loci, boolean isUnmapped) { super(parser, ShardType.READ, loci, readsDataSource, fileSpans, isUnmapped); @@ -66,7 +71,7 @@ public class ReadShard extends Shard { * @return True if this shard's buffer is full (and the shard can buffer reads). */ public boolean isBufferFull() { - return reads.size() > ReadShardStrategy.MAX_READS; + return reads.size() > ReadShard.MAX_READS; } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java new file mode 100644 index 000000000..fa8a7d454 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import net.sf.samtools.GATKBAMFileSpan; +import net.sf.samtools.SAMFileSpan; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Divide up large file pointers containing reads into more manageable subcomponents. + */ +public class ReadShardBalancer extends ShardBalancer { + /** + * Convert iterators of file pointers into balanced iterators of shards. + * @return An iterator over balanced shards. + */ + public Iterator iterator() { + return new Iterator() { + /** + * The cached shard to be returned next. Prefetched in the peekable iterator style. + */ + private Shard nextShard = null; + + /** + * The file pointer currently being processed. + */ + private FilePointer currentFilePointer; + + /** + * Ending position of the last shard in the file. + */ + private Map position = readsDataSource.getCurrentPosition(); + + { + if(filePointers.hasNext()) + currentFilePointer = filePointers.next(); + advance(); + } + + public boolean hasNext() { + return nextShard != null; + } + + public Shard next() { + if(!hasNext()) + throw new NoSuchElementException("No next read shard available"); + Shard currentShard = nextShard; + advance(); + return currentShard; + } + + public void remove() { + throw new UnsupportedOperationException("Unable to remove from shard balancing iterator"); + } + + private void advance() { + Map shardPosition; + nextShard = null; + + Map selectedReaders = new HashMap(); + while(selectedReaders.size() == 0 && currentFilePointer != null) { + shardPosition = currentFilePointer.fileSpans; + + for(SAMReaderID id: shardPosition.keySet()) { + SAMFileSpan fileSpan = new GATKBAMFileSpan(shardPosition.get(id).removeContentsBefore(position.get(id))); + if(!fileSpan.isEmpty()) + selectedReaders.put(id,fileSpan); + } + + if(selectedReaders.size() > 0) { + Shard shard = new ReadShard(parser,readsDataSource,selectedReaders,currentFilePointer.locations,currentFilePointer.isRegionUnmapped); + readsDataSource.fillShard(shard); + + if(!shard.isBufferEmpty()) { + nextShard = shard; + break; + } + } + + selectedReaders.clear(); + currentFilePointer = filePointers.hasNext() ? filePointers.next() : null; + } + + position = readsDataSource.getCurrentPosition(); + } + }; + } + +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java deleted file mode 100755 index 5ea75dbb0..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2010, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.samtools.SAMFileSpan; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; - -import java.util.*; - -/** - * The sharding strategy for reads using a simple counting mechanism. Each read shard - * has a specific number of reads (default to 10K) which is configured in the constructor. - * @author aaron - * @version 1.0 - * @date Apr 14, 2009 - */ -public class ReadShardStrategy implements ShardStrategy { - /** - * What is the maximum number of reads which should go into a read shard. - */ - protected static final int MAX_READS = 10000; - - /** - * The data source used to shard. - */ - private final SAMDataSource dataSource; - - /** - * The intervals to be processed. - */ - private final GenomeLocSortedSet locations; - - /** - * The cached shard to be returned next. Prefetched in the peekable iterator style. - */ - private Shard nextShard = null; - - /** our storage of the genomic locations they'd like to shard over */ - private final List filePointers = new ArrayList(); - - /** - * Iterator over the list of file pointers. - */ - private final Iterator filePointerIterator; - - /** - * The file pointer currently being processed. - */ - private FilePointer currentFilePointer; - - /** - * Ending position of the last shard in the file. - */ - private Map position; - - /** - * An indicator whether the strategy has sharded into the unmapped region. - */ - private boolean isIntoUnmappedRegion = false; - - private final GenomeLocParser parser; - - /** - * Create a new read shard strategy, loading read shards from the given BAM file. - * @param dataSource Data source from which to load shards. - * @param locations intervals to use for sharding. - */ - public ReadShardStrategy(GenomeLocParser parser, SAMDataSource dataSource, GenomeLocSortedSet locations) { - this.dataSource = dataSource; - this.parser = parser; - this.position = this.dataSource.getCurrentPosition(); - this.locations = locations; - - if(locations != null) - filePointerIterator = dataSource.isLowMemoryShardingEnabled() ? new LowMemoryIntervalSharder(this.dataSource,locations) : IntervalSharder.shardIntervals(this.dataSource,locations); - else - filePointerIterator = filePointers.iterator(); - - if(filePointerIterator.hasNext()) - currentFilePointer = filePointerIterator.next(); - - advance(); - } - - /** - * do we have another read shard? - * @return True if any more data is available. False otherwise. - */ - public boolean hasNext() { - return nextShard != null; - } - - /** - * Retrieves the next shard, if available. - * @return The next shard, if available. - * @throws java.util.NoSuchElementException if no such shard is available. - */ - public Shard next() { - if(!hasNext()) - throw new NoSuchElementException("No next read shard available"); - Shard currentShard = nextShard; - advance(); - return currentShard; - } - - public void advance() { - Map shardPosition = new HashMap(); - nextShard = null; - - if(locations != null) { - Map selectedReaders = new HashMap(); - while(selectedReaders.size() == 0 && currentFilePointer != null) { - shardPosition = currentFilePointer.fileSpans; - - for(SAMReaderID id: shardPosition.keySet()) { - SAMFileSpan fileSpan = shardPosition.get(id).removeContentsBefore(position.get(id)); - if(!fileSpan.isEmpty()) - selectedReaders.put(id,fileSpan); - } - - if(selectedReaders.size() > 0) { - Shard shard = new ReadShard(parser, dataSource,selectedReaders,currentFilePointer.locations,currentFilePointer.isRegionUnmapped); - dataSource.fillShard(shard); - - if(!shard.isBufferEmpty()) { - nextShard = shard; - break; - } - } - - selectedReaders.clear(); - currentFilePointer = filePointerIterator.hasNext() ? filePointerIterator.next() : null; - } - } - else { - // todo -- this nulling of intervals is a bit annoying since readwalkers without - // todo -- any -L values need to be special cased throughout the code. - Shard shard = new ReadShard(parser,dataSource,position,null,false); - dataSource.fillShard(shard); - nextShard = !shard.isBufferEmpty() ? shard : null; - } - - this.position = dataSource.getCurrentPosition(); - } - - /** - * @throws UnsupportedOperationException always. - */ - public void remove() { - throw new UnsupportedOperationException("Remove not supported"); - } - - /** - * Convenience method for using ShardStrategy in an foreach loop. - * @return A iterator over shards. - */ - public Iterator iterator() { - return this; - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java deleted file mode 100644 index c76c1d8ae..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.samtools.Bin; -import net.sf.samtools.BrowseableBAMIndex; - -/** - * Created by IntelliJ IDEA. - * User: mhanna - * Date: Feb 2, 2011 - * Time: 4:36:40 PM - * To change this template use File | Settings | File Templates. - */ -class ReaderBin { - public final SAMReaderID id; - public final BrowseableBAMIndex index; - public final int referenceSequence; - public final Bin bin; - - public ReaderBin(final SAMReaderID id, final BrowseableBAMIndex index, final int referenceSequence, final Bin bin) { - this.id = id; - this.index = index; - this.referenceSequence = referenceSequence; - this.bin = bin; - } - - public int getStart() { - return index.getFirstLocusInBin(bin); - } - - public int getStop() { - return index.getLastLocusInBin(bin); - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java index 8452aadfd..0a1eb0563 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java @@ -37,8 +37,10 @@ import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; import org.broadinstitute.sting.gatk.filters.CountingFilteringIterator; import org.broadinstitute.sting.gatk.filters.ReadFilter; import org.broadinstitute.sting.gatk.iterators.*; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLocSortedSet; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.baq.BAQSamIterator; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -71,7 +73,7 @@ public class SAMDataSource { /** * Tools for parsing GenomeLocs, for verifying BAM ordering against general ordering. */ - private final GenomeLocParser genomeLocParser; + protected final GenomeLocParser genomeLocParser; /** * Identifiers for the readers driving this data source. @@ -91,13 +93,18 @@ public class SAMDataSource { /** * How far along is each reader? */ - private final Map readerPositions = new HashMap(); + private final Map readerPositions = new HashMap(); /** * The merged header. */ private final SAMFileHeader mergedHeader; + /** + * The constituent headers of the unmerged files. + */ + private final Map headers = new HashMap(); + /** * The sort order of the BAM files. Files without a sort order tag are assumed to be * in coordinate order. @@ -131,17 +138,24 @@ public class SAMDataSource { private final SAMResourcePool resourcePool; /** - * Whether to enable the new low-memory sharding mechanism. + * Asynchronously loads BGZF blocks. */ - private boolean enableLowMemorySharding = false; + private final BGZFBlockLoadingDispatcher dispatcher; + + /** + * How are threads allocated. + */ + private final ThreadAllocation threadAllocation; /** * Create a new SAM data source given the supplied read metadata. * @param samFiles list of reads files. */ - public SAMDataSource(Collection samFiles,GenomeLocParser genomeLocParser) { + public SAMDataSource(Collection samFiles, ThreadAllocation threadAllocation, Integer numFileHandles, GenomeLocParser genomeLocParser) { this( samFiles, + threadAllocation, + numFileHandles, genomeLocParser, false, SAMFileReader.ValidationStringency.STRICT, @@ -150,8 +164,7 @@ public class SAMDataSource { new ValidationExclusion(), new ArrayList(), false, - false, - true); + false); } /** @@ -159,6 +172,8 @@ public class SAMDataSource { */ public SAMDataSource( Collection samFiles, + ThreadAllocation threadAllocation, + Integer numFileHandles, GenomeLocParser genomeLocParser, boolean useOriginalBaseQualities, SAMFileReader.ValidationStringency strictness, @@ -167,9 +182,10 @@ public class SAMDataSource { ValidationExclusion exclusionList, Collection supplementalFilters, boolean includeReadsWithDeletionAtLoci, - boolean generateExtendedEvents, - boolean enableLowMemorySharding) { + boolean generateExtendedEvents) { this( samFiles, + threadAllocation, + numFileHandles, genomeLocParser, useOriginalBaseQualities, strictness, @@ -182,8 +198,7 @@ public class SAMDataSource { BAQ.CalculationMode.OFF, BAQ.QualityMode.DONT_MODIFY, null, // no BAQ - (byte) -1, - enableLowMemorySharding); + (byte) -1); } /** @@ -205,6 +220,8 @@ public class SAMDataSource { */ public SAMDataSource( Collection samFiles, + ThreadAllocation threadAllocation, + Integer numFileHandles, GenomeLocParser genomeLocParser, boolean useOriginalBaseQualities, SAMFileReader.ValidationStringency strictness, @@ -217,13 +234,19 @@ public class SAMDataSource { BAQ.CalculationMode cmode, BAQ.QualityMode qmode, IndexedFastaSequenceFile refReader, - byte defaultBaseQualities, - boolean enableLowMemorySharding) { - this.enableLowMemorySharding(enableLowMemorySharding); + byte defaultBaseQualities) { this.readMetrics = new ReadMetrics(); this.genomeLocParser = genomeLocParser; readerIDs = samFiles; + + this.threadAllocation = threadAllocation; + // TODO: Consider a borrowed-thread dispatcher implementation. + if(this.threadAllocation.getNumIOThreads() > 0) + dispatcher = new BGZFBlockLoadingDispatcher(this.threadAllocation.getNumIOThreads(), numFileHandles != null ? numFileHandles : 1); + else + dispatcher = null; + validationStringency = strictness; for (SAMReaderID readerID : samFiles) { if (!readerID.samFile.canRead()) @@ -235,10 +258,13 @@ public class SAMDataSource { SAMReaders readers = resourcePool.getAvailableReaders(); // Determine the sort order. - for(SAMFileReader reader: readers.values()) { + for(SAMReaderID readerID: readerIDs) { // Get the sort order, forcing it to coordinate if unsorted. + SAMFileReader reader = readers.getReader(readerID); SAMFileHeader header = reader.getFileHeader(); + headers.put(readerID,header); + if ( header.getReadGroups().isEmpty() ) { throw new UserException.MalformedBAM(readers.getReaderID(reader).samFile, "SAM file doesn't have any read groups defined in the header. The GATK no longer supports SAM files without read groups"); @@ -275,7 +301,7 @@ public class SAMDataSource { qmode, refReader, defaultBaseQualities); - + // cache the read group id (original) -> read group id (merged) // and read group id (merged) -> read group id (original) mappings. for(SAMReaderID id: readerIDs) { @@ -296,12 +322,10 @@ public class SAMDataSource { originalToMergedReadGroupMappings.put(id,mappingToMerged); } - if(enableLowMemorySharding) { - for(SAMReaderID id: readerIDs) { - File indexFile = findIndexFile(id.samFile); - if(indexFile != null) - bamIndices.put(id,new GATKBAMIndex(indexFile)); - } + for(SAMReaderID id: readerIDs) { + File indexFile = findIndexFile(id.samFile); + if(indexFile != null) + bamIndices.put(id,new GATKBAMIndex(indexFile)); } resourcePool.releaseReaders(readers); @@ -314,22 +338,6 @@ public class SAMDataSource { */ public ReadProperties getReadsInfo() { return readProperties; } - /** - * Enable experimental low-memory sharding. - * @param enable True to enable sharding. False otherwise. - */ - public void enableLowMemorySharding(final boolean enable) { - enableLowMemorySharding = enable; - } - - /** - * Returns whether low-memory sharding is enabled. - * @return True if enabled, false otherwise. - */ - public boolean isLowMemoryShardingEnabled() { - return enableLowMemorySharding; - } - /** * Checks to see whether any reads files are supplying data. * @return True if no reads files are supplying data to the traversal; false otherwise. @@ -368,7 +376,7 @@ public class SAMDataSource { * Retrieves the current position within the BAM file. * @return A mapping of reader to current position. */ - public Map getCurrentPosition() { + public Map getCurrentPosition() { return readerPositions; } @@ -381,7 +389,7 @@ public class SAMDataSource { } public SAMFileHeader getHeader(SAMReaderID id) { - return resourcePool.getReadersWithoutLocking().getReader(id).getFileHeader(); + return headers.get(id); } /** @@ -404,45 +412,21 @@ public class SAMDataSource { return mergedToOriginalReadGroupMappings.get(mergedReadGroupId); } - /** - * No read group collisions at this time because only one SAM file is currently supported. - * @return False always. - */ - public boolean hasReadGroupCollisions() { - return hasReadGroupCollisions; - } - /** * True if all readers have an index. * @return True if all readers have an index. */ public boolean hasIndex() { - if(enableLowMemorySharding) - return readerIDs.size() == bamIndices.size(); - else { - for(SAMFileReader reader: resourcePool.getReadersWithoutLocking()) { - if(!reader.hasIndex()) - return false; - } - return true; - } + return readerIDs.size() == bamIndices.size(); } /** * Gets the index for a particular reader. Always preloaded. - * TODO: Should return object of type GATKBAMIndex, but cannot because there - * TODO: is no parent class of both BAMIndex and GATKBAMIndex. Change when new - * TODO: sharding system goes live. * @param id Id of the reader. * @return The index. Will preload the index if necessary. */ - public Object getIndex(final SAMReaderID id) { - if(enableLowMemorySharding) - return bamIndices.get(id); - else { - SAMReaders readers = resourcePool.getReadersWithoutLocking(); - return readers.getReader(id).getBrowseableIndex(); - } + public GATKBAMIndex getIndex(final SAMReaderID id) { + return bamIndices.get(id); } /** @@ -454,7 +438,7 @@ public class SAMDataSource { } /** - * Gets the cumulative read metrics for shards already processed. + * Gets the cumulative read metrics for shards already processed. * @return Cumulative read metrics. */ public ReadMetrics getCumulativeReadMetrics() { @@ -507,10 +491,6 @@ public class SAMDataSource { } public StingSAMIterator seek(Shard shard) { - // todo: refresh monolithic sharding implementation - if(shard instanceof MonolithicShard) - return seekMonolithic(shard); - if(shard.buffersReads()) { return shard.iterator(); } @@ -540,7 +520,7 @@ public class SAMDataSource { */ private void initializeReaderPositions(SAMReaders readers) { for(SAMReaderID id: getReaderIDs()) - readerPositions.put(id,readers.getReader(id).getFilePointerSpanningReads()); + readerPositions.put(id,new GATKBAMFileSpan(readers.getReader(id).getFilePointerSpanningReads())); } /** @@ -548,7 +528,6 @@ public class SAMDataSource { * @param readers Readers from which to load data. * @param shard The shard specifying the data limits. * @param enableVerification True to verify. For compatibility with old sharding strategy. - * TODO: Collapse this flag when the two sharding systems are merged. * @return An iterator over the selected data. */ private StingSAMIterator getIterator(SAMReaders readers, Shard shard, boolean enableVerification) { @@ -559,14 +538,20 @@ public class SAMDataSource { for(SAMReaderID id: getReaderIDs()) { CloseableIterator iterator = null; - if(!shard.isUnmapped() && shard.getFileSpans().get(id) == null) - continue; - iterator = shard.getFileSpans().get(id) != null ? - readers.getReader(id).iterator(shard.getFileSpans().get(id)) : - readers.getReader(id).queryUnmapped(); + + // TODO: null used to be the signal for unmapped, but we've replaced that with a simple index query for the last bin. + // TODO: Kill this check once we've proven that the design elements are gone. + if(shard.getFileSpans().get(id) == null) + throw new ReviewedStingException("SAMDataSource: received null location for reader " + id + ", but null locations are no longer supported."); + + if(threadAllocation.getNumIOThreads() > 0) { + BlockInputStream inputStream = readers.getInputStream(id); + inputStream.submitAccessPlan(new SAMReaderPosition(id,inputStream,(GATKBAMFileSpan)shard.getFileSpans().get(id))); + } + iterator = readers.getReader(id).iterator(shard.getFileSpans().get(id)); if(readProperties.getReadBufferSize() != null) iterator = new BufferingReadIterator(iterator,readProperties.getReadBufferSize()); - if(shard.getGenomeLocs() != null) + if(shard.getGenomeLocs().size() > 0) iterator = new IntervalOverlapFilteringIterator(iterator,shard.getGenomeLocs()); mergingIterator.addIterator(readers.getReader(id),iterator); } @@ -584,33 +569,6 @@ public class SAMDataSource { readProperties.defaultBaseQualities()); } - /** - * A stopgap measure to handle monolithic sharding - * @param shard the (monolithic) shard. - * @return An iterator over the monolithic shard. - */ - private StingSAMIterator seekMonolithic(Shard shard) { - SAMReaders readers = resourcePool.getAvailableReaders(); - - // Set up merging and filtering to dynamically merge together multiple BAMs and filter out records not in the shard set. - SamFileHeaderMerger headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,readers.headers(),true); - MergingSamRecordIterator mergingIterator = new MergingSamRecordIterator(headerMerger,readers.values(),true); - for(SAMReaderID id: getReaderIDs()) - mergingIterator.addIterator(readers.getReader(id),readers.getReader(id).iterator()); - - return applyDecoratingIterators(shard.getReadMetrics(), - shard instanceof ReadShard, - readProperties.useOriginalBaseQualities(), - new ReleasingIterator(readers,StingSAMIteratorAdapter.adapt(mergingIterator)), - readProperties.getDownsamplingMethod().toFraction, - readProperties.getValidationExclusionList().contains(ValidationExclusion.TYPE.NO_READ_ORDER_VERIFICATION), - readProperties.getSupplementalFilters(), - readProperties.getBAQCalculationMode(), - readProperties.getBAQQualityMode(), - readProperties.getRefReader(), - readProperties.defaultBaseQualities()); - } - /** * Adds this read to the given shard. * @param shard The shard to which to add the read. @@ -618,7 +576,7 @@ public class SAMDataSource { * @param read The read to add to the shard. */ private void addReadToBufferingShard(Shard shard,SAMReaderID id,SAMRecord read) { - SAMFileSpan endChunk = read.getFileSource().getFilePointer().getContentsFollowing(); + GATKBAMFileSpan endChunk = new GATKBAMFileSpan(read.getFileSource().getFilePointer().getContentsFollowing()); shard.addRead(read); readerPositions.put(id,endChunk); } @@ -689,19 +647,6 @@ public class SAMDataSource { this.maxEntries = maxEntries; } - /** - * Dangerous internal method; retrieves any set of readers, whether in iteration or not. - * Used to handle non-exclusive, stateless operations, such as index queries. - * @return Any collection of SAMReaders, whether in iteration or not. - */ - protected SAMReaders getReadersWithoutLocking() { - synchronized(this) { - if(allResources.size() == 0) - createNewResource(); - } - return allResources.get(0); - } - /** * Choose a set of readers from the pool to use for this query. When complete, * @return @@ -753,6 +698,11 @@ public class SAMDataSource { */ private final Map readers = new LinkedHashMap(); + /** + * The inptu streams backing + */ + private final Map inputStreams = new LinkedHashMap(); + /** * Derive a new set of readers from the Reads metadata. * @param readerIDs reads to load. @@ -760,12 +710,20 @@ public class SAMDataSource { */ public SAMReaders(Collection readerIDs, SAMFileReader.ValidationStringency validationStringency) { for(SAMReaderID readerID: readerIDs) { - SAMFileReader reader = new SAMFileReader(readerID.samFile); + File indexFile = findIndexFile(readerID.samFile); + + SAMFileReader reader = null; + + if(threadAllocation.getNumIOThreads() > 0) { + BlockInputStream blockInputStream = new BlockInputStream(dispatcher,readerID,false); + reader = new SAMFileReader(blockInputStream,indexFile,false); + inputStreams.put(readerID,blockInputStream); + } + else + reader = new SAMFileReader(readerID.samFile,indexFile,false); reader.setSAMRecordFactory(factory); + reader.enableFileSource(true); - reader.enableIndexMemoryMapping(false); - if(!enableLowMemorySharding) - reader.enableIndexCaching(true); reader.setValidationStringency(validationStringency); final SAMFileHeader header = reader.getFileHeader(); @@ -786,6 +744,15 @@ public class SAMDataSource { return readers.get(id); } + /** + * Retrieve the input stream backing a reader. + * @param id The ID of the reader to retrieve. + * @return the reader associated with the given id. + */ + public BlockInputStream getInputStream(final SAMReaderID id) { + return inputStreams.get(id); + } + /** * Searches for the reader id of this reader. * @param reader Reader for which to search. @@ -883,7 +850,7 @@ public class SAMDataSource { * Filters out reads that do not overlap the current GenomeLoc. * Note the custom implementation: BAM index querying returns all reads that could * possibly overlap the given region (and quite a few extras). In order not to drag - * down performance, this implementation is highly customized to its task. + * down performance, this implementation is highly customized to its task. */ private class IntervalOverlapFilteringIterator implements CloseableIterator { /** @@ -903,7 +870,7 @@ public class SAMDataSource { /** * Custom representation of interval bounds. - * Makes it simpler to track current position. + * Makes it simpler to track current position. */ private int[] intervalContigIndices; private int[] intervalStarts; @@ -941,7 +908,7 @@ public class SAMDataSource { i++; } } - + advance(); } @@ -1070,6 +1037,40 @@ public class SAMDataSource { return indexFile; } + + /** + * Creates a BAM schedule over all reads in the BAM file, both mapped and unmapped. The outgoing stream + * will be as granular as possible given our current knowledge of the best ways to split up BAM files. + * @return An iterator that spans all reads in all BAM files. + */ + public Iterable createShardIteratorOverAllReads(final ShardBalancer shardBalancer) { + shardBalancer.initialize(this,IntervalSharder.shardOverAllReads(this,genomeLocParser),genomeLocParser); + return shardBalancer; + } + + /** + * Creates a BAM schedule over all mapped reads in the BAM file, when a 'mapped' read is defined as any + * read that has been assigned + * @return + */ + public Iterable createShardIteratorOverMappedReads(final SAMSequenceDictionary sequenceDictionary, final ShardBalancer shardBalancer) { + shardBalancer.initialize(this,IntervalSharder.shardOverMappedReads(this,sequenceDictionary,genomeLocParser),genomeLocParser); + return shardBalancer; + } + + /** + * Create a schedule for processing the initialized BAM file using the given interval list. + * The returned schedule should be as granular as possible. + * @param intervals The list of intervals for which to create the schedule. + * @return A granular iterator over file pointers. + */ + public Iterable createShardIteratorOverIntervals(final GenomeLocSortedSet intervals,final ShardBalancer shardBalancer) { + if(intervals == null) + throw new ReviewedStingException("Unable to create schedule from intervals; no intervals were provided."); + shardBalancer.initialize(this,IntervalSharder.shardOverIntervals(SAMDataSource.this,intervals),genomeLocParser); + return shardBalancer; + } } + diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java new file mode 100644 index 000000000..f9f6539a7 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import net.sf.picard.util.PeekableIterator; +import net.sf.samtools.GATKBAMFileSpan; +import net.sf.samtools.GATKChunk; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.List; + +/** +* Created by IntelliJ IDEA. +* User: mhanna +* Date: 10/14/11 +* Time: 10:47 PM +* To change this template use File | Settings | File Templates. +*/ +class SAMReaderPosition { + private final SAMReaderID reader; + private final BlockInputStream inputStream; + + private final List positions; + private PeekableIterator positionIterator; + + /** + * Stores the next block address to read, or -1 if no such block is available. + */ + private long nextBlockAddress; + + + SAMReaderPosition(final SAMReaderID reader, final BlockInputStream inputStream, GATKBAMFileSpan fileSpan) { + this.reader = reader; + this.inputStream = inputStream; + + this.positions = fileSpan.getGATKChunks(); + initialize(); + } + + public SAMReaderID getReader() { + return reader; + } + + public BlockInputStream getInputStream() { + return inputStream; + } + + /** + * Retrieves the next block address to be read. + * @return Next block address to be read. + */ + public long getBlockAddress() { + return nextBlockAddress; + } + + public void reset() { + initialize(); + } + + /** + * Resets the SAM reader position to its original state. + */ + private void initialize() { + this.positionIterator = new PeekableIterator(positions.iterator()); + if(positionIterator.hasNext()) + nextBlockAddress = positionIterator.peek().getBlockStart(); + else + nextBlockAddress = -1; + } + + /** + * Advances the current position to the next block to read, given the current position in the file. + * @param filePosition The current position within the file. + */ + void advancePosition(final long filePosition) { + nextBlockAddress = filePosition; + + // Check the current file position against the iterator; if the iterator is before the current file position, + // draw the iterator forward. Remember when performing the check that coordinates are half-open! + try { + while(positionIterator.hasNext() && isFilePositionPastEndOfChunk(filePosition,positionIterator.peek())) { + positionIterator.next(); + // Check to see if the iterator has more data available. + if(positionIterator.hasNext() && filePosition < positionIterator.peek().getBlockStart()) { + nextBlockAddress = positionIterator.peek().getBlockStart(); + break; + } + } + } + catch(Exception ex) { + throw new ReviewedStingException(""); + } + } + + private boolean isFilePositionPastEndOfChunk(final long filePosition, final GATKChunk chunk) { + return (filePosition > chunk.getBlockEnd() || (filePosition == chunk.getBlockEnd() && chunk.getBlockOffsetEnd() == 0)); + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java new file mode 100644 index 000000000..962208086 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java @@ -0,0 +1,21 @@ +package org.broadinstitute.sting.gatk.datasources.reads; + +import net.sf.picard.util.PeekableIterator; +import org.broadinstitute.sting.utils.GenomeLocParser; + +import java.util.Iterator; + +/** + * Balances maximally granular file pointers into shards of reasonable size. + */ +public abstract class ShardBalancer implements Iterable { + protected SAMDataSource readsDataSource; + protected PeekableIterator filePointers; + protected GenomeLocParser parser; + + public void initialize(final SAMDataSource readsDataSource, final Iterator filePointers, final GenomeLocParser parser) { + this.readsDataSource = readsDataSource; + this.filePointers = new PeekableIterator(filePointers); + this.parser = parser; + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java deleted file mode 100644 index 989cf9fce..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import java.util.Iterator; -/** - * - * User: aaron - * Date: Apr 10, 2009 - * Time: 4:55:37 PM - * - * The Broad Institute - * SOFTWARE COPYRIGHT NOTICE AGREEMENT - * This software and its documentation are copyright 2009 by the - * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. - * - * This software is supplied without any warranty or guaranteed support whatsoever. Neither - * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. - * - */ - -/** - * @author aaron - * @version 1.0 - * @date Apr 10, 2009 - *

- * Interface ShardStrategy - *

- * The base interface for the sharding strategy; before we had a base abstract - * class, but not this will be an interface to accomidate read based sharding - */ -public interface ShardStrategy extends Iterator, Iterable { -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java deleted file mode 100644 index 780b41ef7..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.picard.reference.IndexedFastaSequenceFile; -import net.sf.samtools.SAMSequenceDictionary; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -/** - * - * User: aaron - * Date: Apr 6, 2009 - * Time: 7:09:22 PM - * - * The Broad Institute - * SOFTWARE COPYRIGHT NOTICE AGREEMENT - * This software and its documentation are copyright 2009 by the - * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. - * - * This software is supplied without any warranty or guaranteed support whatsoever. Neither - * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. - * - */ - - -/** - * @author aaron - * @version 1.0 - * @date Apr 6, 2009 - *

- * Class ShardStrategyFactory - *

- * The Shard Strategy Factory, use this class to create and transfer shard strategies - * between different approaches. - */ -public class ShardStrategyFactory { - public enum SHATTER_STRATEGY { - MONOLITHIC, // Put all of the available data into one shard. - LOCUS_EXPERIMENTAL, - READS_EXPERIMENTAL - } - - /** - * get a new shatter strategy - * - * @param readsDataSource File pointer to BAM. - * @param referenceDataSource File pointer to reference. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return a shard strategy capable of dividing input data into shards. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser) { - return ShardStrategyFactory.shatter(readsDataSource, referenceDataSource, strat, dic, startingSize, genomeLocParser, -1L); - } - - /** - * get a new shatter strategy - * - * @param readsDataSource File pointer to BAM. - * @param referenceDataSource File pointer to reference. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return a shard strategy capable of dividing input data into shards. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser, long limitByCount) { - switch (strat) { - case LOCUS_EXPERIMENTAL: - return new LocusShardStrategy(readsDataSource,referenceDataSource,genomeLocParser,null); - case READS_EXPERIMENTAL: - return new ReadShardStrategy(genomeLocParser,readsDataSource,null); - default: - throw new ReviewedStingException("Strategy: " + strat + " isn't implemented for this type of shatter request"); - } - - } - - - /** - * get a new shatter strategy - * - * @param readsDataSource File pointer to BAM. - * @param referenceDataSource File pointer to reference. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return a shard strategy capable of dividing input data into shards. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser, GenomeLocSortedSet lst) { - return ShardStrategyFactory.shatter(readsDataSource, referenceDataSource, strat, dic, startingSize, genomeLocParser, lst, -1l); - - } - - /** - * get a new shatter strategy - * - * @param readsDataSource The reads used to shatter this file. - * @param referenceDataSource The reference used to shatter this file. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return A strategy for shattering this data. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser, GenomeLocSortedSet lst, long limitDataCount) { - switch (strat) { - case LOCUS_EXPERIMENTAL: - return new LocusShardStrategy(readsDataSource,referenceDataSource,genomeLocParser,lst); - case READS_EXPERIMENTAL: - return new ReadShardStrategy(genomeLocParser, readsDataSource,lst); - default: - throw new ReviewedStingException("Strategy: " + strat + " isn't implemented"); - } - - } - -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java index 673df6dfa..577db0965 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java @@ -30,10 +30,12 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.commandline.CommandLineProgram; import org.broadinstitute.sting.commandline.Input; import org.broadinstitute.sting.commandline.Output; +import org.broadinstitute.sting.gatk.datasources.reads.BAMScheduler; import org.broadinstitute.sting.gatk.datasources.reads.FilePointer; -import org.broadinstitute.sting.gatk.datasources.reads.LowMemoryIntervalSharder; +import org.broadinstitute.sting.gatk.datasources.reads.IntervalSharder; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.GenomeLocSortedSet; @@ -92,7 +94,7 @@ public class FindLargeShards extends CommandLineProgram { // initialize reads List bamReaders = ListFileUtils.unpackBAMFileList(samFiles,parser); - SAMDataSource dataSource = new SAMDataSource(bamReaders,genomeLocParser); + SAMDataSource dataSource = new SAMDataSource(bamReaders,new ThreadAllocation(),null,genomeLocParser); // intervals GenomeLocSortedSet intervalSortedSet = null; @@ -106,7 +108,7 @@ public class FindLargeShards extends CommandLineProgram { logger.info(String.format("PROGRESS: Calculating mean and variance: Contig\tRegion.Start\tRegion.Stop\tSize")); - LowMemoryIntervalSharder sharder = new LowMemoryIntervalSharder(dataSource,intervalSortedSet); + IntervalSharder sharder = IntervalSharder.shardOverIntervals(dataSource,intervalSortedSet); while(sharder.hasNext()) { FilePointer filePointer = sharder.next(); @@ -135,7 +137,7 @@ public class FindLargeShards extends CommandLineProgram { logger.warn(String.format("PROGRESS: Searching for large shards: Contig\tRegion.Start\tRegion.Stop\tSize")); out.printf("Contig\tRegion.Start\tRegion.Stop\tSize%n"); - sharder = new LowMemoryIntervalSharder(dataSource,intervalSortedSet); + sharder = IntervalSharder.shardOverIntervals(dataSource,intervalSortedSet); while(sharder.hasNext()) { FilePointer filePointer = sharder.next(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java index c8c79bb14..2c33a19b8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java @@ -29,6 +29,14 @@ import net.sf.picard.reference.FastaSequenceIndex; import net.sf.picard.reference.FastaSequenceIndexBuilder; import net.sf.picard.reference.IndexedFastaSequenceFile; import net.sf.picard.sam.CreateSequenceDictionary; +import net.sf.samtools.SAMSequenceRecord; +import org.broadinstitute.sting.gatk.datasources.reads.FilePointer; +import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; +import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; +import org.broadinstitute.sting.gatk.datasources.reads.Shard; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLocSortedSet; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; @@ -36,13 +44,17 @@ import org.broadinstitute.sting.utils.file.FSLockWithShared; import org.broadinstitute.sting.utils.file.FileSystemInabilityToLockException; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; /** * Loads reference data from fasta file * Looks for fai and dict files, and tries to create them if they don't exist */ public class ReferenceDataSource { - private IndexedFastaSequenceFile index; + private IndexedFastaSequenceFile reference; /** our log, which we want to capture anything from this class */ protected static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ReferenceDataSource.class); @@ -173,7 +185,7 @@ public class ReferenceDataSource { logger.info("Treating existing index file as complete."); } - index = new CachingIndexedFastaSequenceFile(fastaFile); + reference = new CachingIndexedFastaSequenceFile(fastaFile); } catch (IllegalArgumentException e) { throw new UserException.CouldNotReadInputFile(fastaFile, "Could not read reference sequence. The FASTA must have either a .fasta or .fa extension", e); @@ -192,6 +204,52 @@ public class ReferenceDataSource { * @return IndexedFastaSequenceFile that was created from file */ public IndexedFastaSequenceFile getReference() { - return this.index; + return this.reference; + } + + /** + * Creates an iterator for processing the entire reference. + * @param readsDataSource the reads datasource to embed in the locus shard. + * @param parser used to generate/regenerate intervals. TODO: decouple the creation of the shards themselves from the creation of the driving iterator so that datasources need not be passed to datasources. + * @param maxShardSize The maximum shard size which can be used to create this list. + * @return Creates a schedule for performing a traversal over the entire reference. + */ + public Iterable createShardsOverEntireReference(final SAMDataSource readsDataSource, final GenomeLocParser parser, final int maxShardSize) { + List shards = new ArrayList(); + for(SAMSequenceRecord refSequenceRecord: reference.getSequenceDictionary().getSequences()) { + for(int shardStart = 1; shardStart <= refSequenceRecord.getSequenceLength(); shardStart += maxShardSize) { + final int shardStop = Math.min(shardStart+maxShardSize-1, refSequenceRecord.getSequenceLength()); + shards.add(new LocusShard(parser, + readsDataSource, + Collections.singletonList(parser.createGenomeLoc(refSequenceRecord.getSequenceName(),shardStart,shardStop)), + null)); + } + } + return shards; + } + + /** + * Creates an iterator for processing the entire reference. + * @param readsDataSource the reads datasource to embed in the locus shard. TODO: decouple the creation of the shards themselves from the creation of the driving iterator so that datasources need not be passed to datasources. + * @param intervals the list of intervals to use when processing the reference. + * @param maxShardSize The maximum shard size which can be used to create this list. + * @return Creates a schedule for performing a traversal over the entire reference. + */ + public Iterable createShardsOverIntervals(final SAMDataSource readsDataSource, final GenomeLocSortedSet intervals, final int maxShardSize) { + List shards = new ArrayList(); + for(GenomeLoc interval: intervals) { + while(interval.size() > maxShardSize) { + shards.add(new LocusShard(intervals.getGenomeLocParser(), + readsDataSource, + Collections.singletonList(intervals.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart(),interval.getStart()+maxShardSize-1)), + null)); + interval = intervals.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart()+maxShardSize,interval.getStop()); + } + shards.add(new LocusShard(intervals.getGenomeLocParser(), + readsDataSource, + Collections.singletonList(interval), + null)); + } + return shards; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java index 162baed00..b0043e68c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java @@ -5,7 +5,6 @@ import org.broad.tribble.TribbleException; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.io.OutputTracker; import org.broadinstitute.sting.gatk.io.ThreadLocalOutputTracker; @@ -88,7 +87,7 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar this.threadPool = Executors.newFixedThreadPool(nThreadsToUse); } - public Object execute( Walker walker, ShardStrategy shardStrategy ) { + public Object execute( Walker walker, Iterable shardStrategy ) { // Fast fail for walkers not supporting TreeReducible interface. if (!( walker instanceof TreeReducible )) throw new IllegalArgumentException("The GATK can currently run in parallel only with TreeReducible walkers"); diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java index deafcd0cc..ff5e1064b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java @@ -7,7 +7,6 @@ import org.broadinstitute.sting.gatk.datasources.providers.ReadShardDataProvider import org.broadinstitute.sting.gatk.datasources.providers.ShardDataProvider; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.io.DirectOutputTracker; import org.broadinstitute.sting.gatk.io.OutputTracker; @@ -44,7 +43,7 @@ public class LinearMicroScheduler extends MicroScheduler { * @param walker Computation to perform over dataset. * @param shardStrategy A strategy for sharding the data. */ - public Object execute(Walker walker, ShardStrategy shardStrategy) { + public Object execute(Walker walker, Iterable shardStrategy) { walker.initialize(); Accumulator accumulator = Accumulator.create(engine,walker); diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java index e731b9864..d013db7e8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java @@ -30,11 +30,11 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.io.OutputTracker; import org.broadinstitute.sting.gatk.iterators.NullSAMIterator; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.gatk.traversals.*; import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -87,20 +87,20 @@ public abstract class MicroScheduler implements MicroSchedulerMBean { * @param reads the informations associated with the reads * @param reference the reference file * @param rods the rods to include in the traversal - * @param nThreadsToUse Number of threads to utilize. + * @param threadAllocation Number of threads to utilize. * * @return The best-fit microscheduler. */ - public static MicroScheduler create(GenomeAnalysisEngine engine, Walker walker, SAMDataSource reads, IndexedFastaSequenceFile reference, Collection rods, int nThreadsToUse) { - if (walker instanceof TreeReducible && nThreadsToUse > 1) { + public static MicroScheduler create(GenomeAnalysisEngine engine, Walker walker, SAMDataSource reads, IndexedFastaSequenceFile reference, Collection rods, ThreadAllocation threadAllocation) { + if (walker instanceof TreeReducible && threadAllocation.getNumCPUThreads() > 1) { if(walker.isReduceByInterval()) throw new UserException.BadArgumentValue("nt", String.format("The analysis %s aggregates results by interval. Due to a current limitation of the GATK, analyses of this type do not currently support parallel execution. Please run your analysis without the -nt option.", engine.getWalkerName(walker.getClass()))); if(walker instanceof ReadWalker) throw new UserException.BadArgumentValue("nt", String.format("The analysis %s is a read walker. Due to a current limitation of the GATK, analyses of this type do not currently support parallel execution. Please run your analysis without the -nt option.", engine.getWalkerName(walker.getClass()))); - logger.info(String.format("Running the GATK in parallel mode with %d concurrent threads",nThreadsToUse)); - return new HierarchicalMicroScheduler(engine, walker, reads, reference, rods, nThreadsToUse); + logger.info(String.format("Running the GATK in parallel mode with %d concurrent threads",threadAllocation.getNumCPUThreads())); + return new HierarchicalMicroScheduler(engine, walker, reads, reference, rods, threadAllocation.getNumCPUThreads()); } else { - if(nThreadsToUse > 1) + if(threadAllocation.getNumCPUThreads() > 1) throw new UserException.BadArgumentValue("nt", String.format("The analysis %s currently does not support parallel execution. Please run your analysis without the -nt option.", engine.getWalkerName(walker.getClass()))); return new LinearMicroScheduler(engine, walker, reads, reference, rods); } @@ -156,7 +156,7 @@ public abstract class MicroScheduler implements MicroSchedulerMBean { * * @return the return type of the walker */ - public abstract Object execute(Walker walker, ShardStrategy shardStrategy); + public abstract Object execute(Walker walker, Iterable shardStrategy); /** * Retrieves the object responsible for tracking and managing output. diff --git a/public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java b/public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java new file mode 100644 index 000000000..0c81af07b --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.resourcemanagement; + +import org.broadinstitute.sting.utils.exceptions.UserException; + +/** + * Models how threads are distributed between various components of the GATK. + */ +public class ThreadAllocation { + /** + * The number of CPU threads to be used by the GATK. + */ + private final int numCPUThreads; + + /** + * Number of threads to devote exclusively to IO. Default is 0. + */ + private final int numIOThreads; + + public int getNumCPUThreads() { + return numCPUThreads; + } + + public int getNumIOThreads() { + return numIOThreads; + } + + /** + * Construct the default thread allocation. + */ + public ThreadAllocation() { + this(1,null,null); + } + + /** + * Set up the thread allocation. Default allocation is 1 CPU thread, 0 IO threads. + * (0 IO threads means that no threads are devoted exclusively to IO; they're inline on the CPU thread). + * @param totalThreads Complete number of threads to allocate. + * @param numCPUThreads Total number of threads allocated to the traversal. + * @param numIOThreads Total number of threads allocated exclusively to IO. + */ + public ThreadAllocation(final int totalThreads, final Integer numCPUThreads, final Integer numIOThreads) { + // If no allocation information is present, allocate all threads to CPU + if(numCPUThreads == null && numIOThreads == null) { + this.numCPUThreads = totalThreads; + this.numIOThreads = 0; + } + // If only CPU threads are specified, allocate remainder to IO (minimum 0 dedicated IO threads). + else if(numIOThreads == null) { + if(numCPUThreads > totalThreads) + throw new UserException(String.format("Invalid thread allocation. User requested %d threads in total, but the count of cpu threads (%d) is higher than the total threads",totalThreads,numCPUThreads)); + this.numCPUThreads = numCPUThreads; + this.numIOThreads = totalThreads - numCPUThreads; + } + // If only IO threads are specified, allocate remainder to CPU (minimum 1 dedicated CPU thread). + else if(numCPUThreads == null) { + if(numIOThreads > totalThreads) + throw new UserException(String.format("Invalid thread allocation. User requested %d threads in total, but the count of io threads (%d) is higher than the total threads",totalThreads,numIOThreads)); + this.numCPUThreads = Math.max(1,totalThreads-numIOThreads); + this.numIOThreads = numIOThreads; + } + else { + if(numCPUThreads + numIOThreads != totalThreads) + throw new UserException(String.format("Invalid thread allocation. User requested %d threads in total, but the count of cpu threads (%d) + the count of io threads (%d) does not match",totalThreads,numCPUThreads,numIOThreads)); + this.numCPUThreads = numCPUThreads; + this.numIOThreads = numIOThreads; + } + } + +} diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java index 8d7dd82ac..17a7d1974 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java @@ -11,6 +11,7 @@ import org.broadinstitute.sting.gatk.executive.WindowMaker; import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; @@ -49,7 +50,7 @@ public abstract class LocusViewTemplate extends BaseTest { SAMRecordIterator iterator = new SAMRecordIterator(); GenomeLoc shardBounds = genomeLocParser.createGenomeLoc("chr1", 1, 5); - Shard shard = new LocusShard(genomeLocParser, new SAMDataSource(Collections.emptyList(),genomeLocParser),Collections.singletonList(shardBounds),Collections.emptyMap()); + Shard shard = new LocusShard(genomeLocParser, new SAMDataSource(Collections.emptyList(),new ThreadAllocation(),null,genomeLocParser),Collections.singletonList(shardBounds),Collections.emptyMap()); WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, null, genomeLocParser, window.getLocus(), window, null, null); diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java index dc3a6cafe..62c93bddd 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java @@ -26,6 +26,7 @@ package org.broadinstitute.sting.gatk.datasources.reads; import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -42,7 +43,7 @@ import java.util.Collections; public class MockLocusShard extends LocusShard { public MockLocusShard(final GenomeLocParser genomeLocParser,final List intervals) { super( genomeLocParser, - new SAMDataSource(Collections.emptyList(),genomeLocParser), + new SAMDataSource(Collections.emptyList(),new ThreadAllocation(),null,genomeLocParser), intervals, null); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java deleted file mode 100755 index e41a6b3b7..000000000 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import static org.testng.Assert.fail; -import net.sf.picard.reference.IndexedFastaSequenceFile; -import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.BaseTest; -import org.broadinstitute.sting.commandline.Tags; -import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; -import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; -import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategyFactory; -import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; -import org.broadinstitute.sting.utils.exceptions.UserException; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; - -import org.testng.annotations.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * User: aaron - * Date: Apr 8, 2009 - * Time: 8:14:23 PM - * - * The Broad Institute - * SOFTWARE COPYRIGHT NOTICE AGREEMENT - * This software and its documentation are copyright 2009 by the - * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. - * - * This software is supplied without any warranty or guaranteed support whatsoever. Neither - * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. - * - */ - - -/** - * @author aaron - * @version 1.0 - * @date Apr 8, 2009 - *

- * Class SAMBAMDataSourceUnitTest - *

- * The test of the SAMBAM simple data source. - */ -public class SAMBAMDataSourceUnitTest extends BaseTest { - - private List readers; - private IndexedFastaSequenceFile seq; - private GenomeLocParser genomeLocParser; - - /** - * This function does the setup of our parser, before each method call. - *

- * Called before every test case method. - */ - @BeforeMethod - public void doForEachTest() throws FileNotFoundException { - readers = new ArrayList(); - - // sequence - seq = new CachingIndexedFastaSequenceFile(new File(hg18Reference)); - genomeLocParser = new GenomeLocParser(seq.getSequenceDictionary()); - } - - /** - * Tears down the test fixture after each call. - *

- * Called after every test case method. - */ - @AfterMethod - public void undoForEachTest() { - seq = null; - readers.clear(); - } - - - /** Test out that we can shard the file and iterate over every read */ - @Test - public void testLinearBreakIterateAll() { - logger.warn("Executing testLinearBreakIterateAll"); - - // setup the data - readers.add(new SAMReaderID(new File(validationDataLocation+"/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - - // the sharding strat. - SAMDataSource data = new SAMDataSource(readers,genomeLocParser); - ShardStrategy strat = ShardStrategyFactory.shatter(data,seq,ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, seq.getSequenceDictionary(), 100000,genomeLocParser); - int count = 0; - - try { - for (Shard sh : strat) { - int readCount = 0; - count++; - - GenomeLoc firstLocus = sh.getGenomeLocs().get(0), lastLocus = sh.getGenomeLocs().get(sh.getGenomeLocs().size()-1); - logger.debug("Start : " + firstLocus.getStart() + " stop : " + lastLocus.getStop() + " contig " + firstLocus.getContig()); - logger.debug("count = " + count); - StingSAMIterator datum = data.seek(sh); - - // for the first couple of shards make sure we can see the reads - if (count < 5) { - for (SAMRecord r : datum) { - } - readCount++; - } - datum.close(); - - // if we're over 100 shards, break out - if (count > 100) { - break; - } - } - } - catch (UserException.CouldNotReadInputFile e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); - } - } - - - /** Test out that we can shard the file and iterate over every read */ - @Test - public void testMergingTwoBAMFiles() { - logger.warn("Executing testMergingTwoBAMFiles"); - - // setup the test files - readers.add(new SAMReaderID(new File(validationDataLocation + "/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - - // the sharding strat. - SAMDataSource data = new SAMDataSource(readers,genomeLocParser); - ShardStrategy strat = ShardStrategyFactory.shatter(data,seq,ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, seq.getSequenceDictionary(), 100000,genomeLocParser); - - ArrayList readcountPerShard = new ArrayList(); - ArrayList readcountPerShard2 = new ArrayList(); - - // count up the first hundred shards - int shardsToCount = 100; - int count = 0; - - try { - for (Shard sh : strat) { - int readCount = 0; - count++; - if (count > shardsToCount) { - break; - } - - StingSAMIterator datum = data.seek(sh); - - for (SAMRecord r : datum) { - readCount++; - - } - readcountPerShard.add(readCount); - logger.debug("read count = " + readCount); - datum.close(); - } - } - catch (UserException.CouldNotReadInputFile e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); - } - - - // setup the data and the counter before our second run - readers.clear(); - readers.add(new SAMReaderID(new File(validationDataLocation + "/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - readers.add(new SAMReaderID(new File(validationDataLocation + "/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - - count = 0; - // the sharding strat. - data = new SAMDataSource(readers,genomeLocParser); - strat = ShardStrategyFactory.shatter(data,seq,ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, seq.getSequenceDictionary(), 100000, genomeLocParser); - - logger.debug("Pile two:"); - try { - for (Shard sh : strat) { - int readCount = 0; - count++; - - // can we leave? - if (count > shardsToCount) { - break; - } - - StingSAMIterator datum = data.seek(sh); - - for (SAMRecord r : datum) { - readCount++; - } - - readcountPerShard2.add(readCount); - logger.debug("read count = " + readCount); - datum.close(); - } - } - catch (UserException.CouldNotReadInputFile e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); - } - - /*int pos = 0; - for (; pos < 100; pos++) { - if (!readcountPerShard.get(pos).equals(readcountPerShard2.get(pos))) { - fail("Shard number " + pos + " in the two approaches had different read counts, " + readcountPerShard.get(pos) + " and " + readcountPerShard2.get(pos)); - } - } */ - - } - - - - -} diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java new file mode 100755 index 000000000..ba2d68ec9 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import static org.testng.Assert.fail; +import net.sf.picard.reference.IndexedFastaSequenceFile; +import net.sf.samtools.SAMFileReader; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.commandline.Tags; +import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; +import org.broadinstitute.sting.gatk.filters.ReadFilter; +import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author aaron + * @version 1.0 + * @date Apr 8, 2009 + *

+ * Class SAMDataSourceUnitTest + *

+ * The test of the SAMBAM simple data source. + */ +public class SAMDataSourceUnitTest extends BaseTest { + + private List readers; + private IndexedFastaSequenceFile seq; + private GenomeLocParser genomeLocParser; + + /** + * This function does the setup of our parser, before each method call. + *

+ * Called before every test case method. + */ + @BeforeMethod + public void doForEachTest() throws FileNotFoundException { + readers = new ArrayList(); + + // sequence + seq = new CachingIndexedFastaSequenceFile(new File(b36KGReference)); + genomeLocParser = new GenomeLocParser(seq.getSequenceDictionary()); + } + + /** + * Tears down the test fixture after each call. + *

+ * Called after every test case method. + */ + @AfterMethod + public void undoForEachTest() { + seq = null; + readers.clear(); + } + + + /** Test out that we can shard the file and iterate over every read */ + @Test + public void testLinearBreakIterateAll() { + logger.warn("Executing testLinearBreakIterateAll"); + + // setup the data + readers.add(new SAMReaderID(new File(validationDataLocation+"/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); + + // the sharding strat. + SAMDataSource data = new SAMDataSource(readers, + new ThreadAllocation(), + null, + genomeLocParser, + false, + SAMFileReader.ValidationStringency.SILENT, + null, + null, + new ValidationExclusion(), + new ArrayList(), + false, + false); + + Iterable strat = data.createShardIteratorOverMappedReads(seq.getSequenceDictionary(),new LocusShardBalancer()); + int count = 0; + + try { + for (Shard sh : strat) { + int readCount = 0; + count++; + + GenomeLoc firstLocus = sh.getGenomeLocs().get(0), lastLocus = sh.getGenomeLocs().get(sh.getGenomeLocs().size()-1); + logger.debug("Start : " + firstLocus.getStart() + " stop : " + lastLocus.getStop() + " contig " + firstLocus.getContig()); + logger.debug("count = " + count); + StingSAMIterator datum = data.seek(sh); + + // for the first couple of shards make sure we can see the reads + if (count < 5) { + for (SAMRecord r : datum) { + } + readCount++; + } + datum.close(); + + // if we're over 100 shards, break out + if (count > 100) { + break; + } + } + } + catch (UserException.CouldNotReadInputFile e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); + } + } +} diff --git a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java index 7f4d96add..9226f97e2 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java @@ -5,14 +5,13 @@ import net.sf.picard.reference.IndexedFastaSequenceFile; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.commandline.Tags; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; -import org.broadinstitute.sting.gatk.ReadMetrics; import org.broadinstitute.sting.gatk.datasources.providers.ShardDataProvider; import org.broadinstitute.sting.gatk.datasources.providers.ReadShardDataProvider; +import org.broadinstitute.sting.gatk.datasources.reads.ReadShardBalancer; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategyFactory; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.gatk.walkers.qc.CountReadsWalker; import org.broadinstitute.sting.gatk.walkers.Walker; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -66,7 +65,6 @@ public class TraverseReadsUnitTest extends BaseTest { private List bamList; private Walker countReadWalker; private File output; - private long readSize = 100000; private TraverseReads traversalEngine = null; private IndexedFastaSequenceFile ref = null; @@ -117,18 +115,14 @@ public class TraverseReadsUnitTest extends BaseTest { /** Test out that we can shard the file and iterate over every read */ @Test public void testUnmappedReadCount() { - SAMDataSource dataSource = new SAMDataSource(bamList,genomeLocParser); - ShardStrategy shardStrategy = ShardStrategyFactory.shatter(dataSource,ref, ShardStrategyFactory.SHATTER_STRATEGY.READS_EXPERIMENTAL, - ref.getSequenceDictionary(), - readSize, - genomeLocParser); + SAMDataSource dataSource = new SAMDataSource(bamList,new ThreadAllocation(),null,genomeLocParser); + Iterable shardStrategy = dataSource.createShardIteratorOverAllReads(new ReadShardBalancer()); countReadWalker.initialize(); Object accumulator = countReadWalker.reduceInit(); - while (shardStrategy.hasNext()) { + for(Shard shard: shardStrategy) { traversalEngine.startTimersIfNecessary(); - Shard shard = shardStrategy.next(); if (shard == null) { fail("Shard == null"); From 333e5de812884e9a2109cf69e438025474f305ce Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 18 Nov 2011 16:49:59 -0500 Subject: [PATCH 065/113] returning read instead of GATKSAMRecord Do not create new GATKSAMRecord when read has been fully clipped, because it is essentially the same as returning the currently fully clipped read. --- .../org/broadinstitute/sting/utils/clipreads/ReadClipper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java index e4806838d..8c1061494 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -121,7 +121,7 @@ public class ReadClipper { public GATKSAMRecord hardClipSoftClippedBases () { if (read.isEmpty()) - return new GATKSAMRecord(read.getHeader()); + return read; int readIndex = 0; int cutLeft = -1; // first position to hard clip (inclusive) From b5de1820145f3b2ca4156454dfff79164e4dde72 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 18 Nov 2011 18:34:05 -0500 Subject: [PATCH 066/113] isEmpty now checks if mReadBases is null Since newly created reads have mReadBases == null. This is an effort to centralize the place to check for empty GATKSAMRecords. --- .../src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java index 6d7c8dad9..d3a52167a 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -261,7 +261,7 @@ public class GATKSAMRecord extends BAMRecord { * @return true if the read has no bases */ public boolean isEmpty() { - return this.getReadLength() == 0; + return super.getReadBases() == null || super.getReadLength() == 0; } /** From c7f2d5c7c7d7a1c8ac46afebe321968d3bf78fd4 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 14:49:06 -0500 Subject: [PATCH 067/113] Final minor fix to contract --- .../sting/utils/variantcontext/GenotypesContext.java | 1 - .../sting/utils/variantcontext/VariantContextBuilder.java | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index a639f512e..47e6b2fbe 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -213,7 +213,6 @@ public class GenotypesContext implements List { * @param toCopy the collection of genotypes * @return an mutable GenotypeContext containing genotypes */ - @Requires({"toCopy != null"}) @Ensures({"result != null"}) public static final GenotypesContext copy(final Collection toCopy) { return toCopy == null ? NO_GENOTYPES : create(new ArrayList(toCopy)); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java index 67077e8c3..a2065d458 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java @@ -24,7 +24,7 @@ package org.broadinstitute.sting.utils.variantcontext; -import com.google.java.contract.Requires; +import com.google.java.contract.*; import org.broad.tribble.Feature; import org.broad.tribble.TribbleException; import org.broad.tribble.util.ParsingUtils; @@ -158,10 +158,11 @@ public class VariantContextBuilder { * @return */ @Requires({"key != null"}) + @Ensures({"this.attributes.size() == old(this.attributes.size()) || this.attributes.size() == old(this.attributes.size()+1)"}) public VariantContextBuilder attribute(final String key, final Object value) { if ( ! attributesCanBeModified ) { this.attributesCanBeModified = true; - this.attributes = new HashMap(); + this.attributes = new HashMap(attributes); } attributes.put(key, value); return this; @@ -282,7 +283,7 @@ public class VariantContextBuilder { * @param negLog10PError * @return */ - @Requires("negLog10PError <= 0 || negLog10PError == VariantContext.NO_NEG_LOG_10PERROR") + @Requires("negLog10PError >= 0 || negLog10PError == VariantContext.NO_NEG_LOG_10PERROR") public VariantContextBuilder negLog10PError(final double negLog10PError) { this.negLog10PError = negLog10PError; return this; From 6cf315e17beaa971e4bc606935f922456c4c8fce Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 21:07:30 -0500 Subject: [PATCH 068/113] Change interface to getNegLog10PError to getLog10PError --- .../gatk/refdata/VariantContextAdaptors.java | 14 +++--- .../gatk/walkers/annotator/AlleleBalance.java | 4 +- .../gatk/walkers/annotator/HardyWeinberg.java | 4 +- .../gatk/walkers/annotator/QualByDepth.java | 2 +- .../annotator/VariantAnnotatorEngine.java | 2 +- .../beagle/BeagleOutputToVCFWalker.java | 18 +++----- .../walkers/diffengine/VCFDiffableReader.java | 4 +- .../filters/VariantFiltrationWalker.java | 23 ++++------ .../genotyper/ExactAFCalculationModel.java | 2 +- .../walkers/genotyper/UGCallVariants.java | 3 +- .../genotyper/UnifiedGenotyperEngine.java | 34 ++++++++------- .../indels/SomaticIndelDetectorWalker.java | 21 ++++----- .../walkers/phasing/PhaseByTransmission.java | 12 +++--- .../gatk/walkers/phasing/PhasingUtils.java | 6 +-- .../phasing/ReadBackedPhasingWalker.java | 13 +++--- .../MendelianViolationEvaluator.java | 2 +- .../variantutils/LeftAlignVariants.java | 2 +- .../walkers/variantutils/VariantsToTable.java | 2 +- .../utils/codecs/vcf/AbstractVCFCodec.java | 8 ++-- .../utils/codecs/vcf/StandardVCFWriter.java | 6 +-- .../sting/utils/codecs/vcf/VCF3Codec.java | 2 +- .../sting/utils/codecs/vcf/VCFCodec.java | 2 +- .../broadinstitute/sting/utils/gcf/GCF.java | 21 ++++----- .../sting/utils/gcf/GCFGenotype.java | 4 +- .../utils/variantcontext/CommonInfo.java | 27 ++++++------ .../sting/utils/variantcontext/Genotype.java | 34 +++++++-------- .../utils/variantcontext/VariantContext.java | 43 +++++-------------- .../variantcontext/VariantContextBuilder.java | 18 ++++---- .../variantcontext/VariantContextUtils.java | 36 +++++++++------- .../variantcontext/VariantJEXLContext.java | 2 +- .../utils/genotype/vcf/VCFWriterUnitTest.java | 10 ++--- .../variantcontext/GenotypeUnitTest.java | 4 +- .../VariantContextBenchmark.java | 2 +- .../VariantContextUnitTest.java | 30 ++++++------- .../VariantContextUtilsUnitTest.java | 2 +- 35 files changed, 193 insertions(+), 226 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java index 7953edd7f..09ae02bd9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/VariantContextAdaptors.java @@ -9,7 +9,6 @@ import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.classloader.PluginManager; import org.broadinstitute.sting.utils.codecs.hapmap.RawHapMapFeature; -import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -193,9 +192,12 @@ public class VariantContextAdaptors { return null; // we weren't given enough reference context to create the VariantContext Byte refBaseForIndel = new Byte(ref.getBases()[index]); - GenotypesContext genotypes = null; - VariantContext vc = new VariantContext(name, dbsnp.getRsID(), dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0), alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, attributes, refBaseForIndel); - return vc; + final VariantContextBuilder builder = new VariantContextBuilder(); + builder.source(name).id(dbsnp.getRsID()); + builder.loc(dbsnp.getChr(), dbsnp.getStart() - (sawNullAllele ? 1 : 0), dbsnp.getEnd() - (refAllele.isNull() ? 1 : 0)); + builder.alleles(alleles); + builder.referenceBaseForIndel(refBaseForIndel); + return builder.make(); } else return null; // can't handle anything else } @@ -255,7 +257,7 @@ public class VariantContextAdaptors { genotypes.add(call); alleles.add(refAllele); GenomeLoc loc = ref.getGenomeLocParser().createGenomeLoc(geli.getChr(),geli.getStart()); - return new VariantContextBuilder(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles).genotypes(genotypes).negLog10PError(geli.getLODBestToReference()).attributes(attributes).make(); + return new VariantContextBuilder(name, loc.getContig(), loc.getStart(), loc.getStop(), alleles).genotypes(genotypes).log10PError(-1 * geli.getLODBestToReference()).attributes(attributes).make(); } else return null; // can't handle anything else } @@ -349,7 +351,7 @@ public class VariantContextAdaptors { long end = hapmap.getEnd(); if ( deletionLength > 0 ) end += deletionLength; - VariantContext vc = new VariantContext(name, hapmap.getName(), hapmap.getChr(), hapmap.getStart(), end, alleles, genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null, refBaseForIndel); + VariantContext vc = new VariantContextBuilder(name, hapmap.getChr(), hapmap.getStart(), end, alleles).id(hapmap.getName()).genotypes(genotypes).referenceBaseForIndel(refBaseForIndel).make(); return vc; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java index 4a13fccc6..833107bd3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalance.java @@ -85,8 +85,8 @@ public class AlleleBalance extends InfoFieldAnnotation { continue; // weight the allele balance by genotype quality so that e.g. mis-called homs don't affect the ratio too much - ratio += genotype.getNegLog10PError() * ((double)refCount / (double)(refCount + altCount)); - totalWeights += genotype.getNegLog10PError(); + ratio += genotype.getLog10PError() * ((double)refCount / (double)(refCount + altCount)); + totalWeights += genotype.getLog10PError(); } else if ( vc.isIndel() && context.hasExtendedEventPileup() ) { final ReadBackedExtendedEventPileup indelPileup = context.getExtendedEventPileup(); if ( indelPileup == null ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java index 33f2f1dd3..795cdbeb5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/HardyWeinberg.java @@ -27,7 +27,7 @@ public class HardyWeinberg extends InfoFieldAnnotation implements WorkInProgress private static final int MIN_SAMPLES = 10; private static final int MIN_GENOTYPE_QUALITY = 10; - private static final int MIN_NEG_LOG10_PERROR = MIN_GENOTYPE_QUALITY / 10; + private static final int MIN_LOG10_PERROR = MIN_GENOTYPE_QUALITY / 10; public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { @@ -46,7 +46,7 @@ public class HardyWeinberg extends InfoFieldAnnotation implements WorkInProgress // Right now we just ignore genotypes that are not confident, but this throws off // our HW ratios. More analysis is needed to determine the right thing to do when // the genotyper cannot decide whether a given sample is het or hom var. - if ( g.getNegLog10PError() < MIN_NEG_LOG10_PERROR ) + if ( g.getLog10PError() > MIN_LOG10_PERROR ) continue; if ( g.isHomRef() ) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java index 0653b6015..d555463bc 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/QualByDepth.java @@ -51,7 +51,7 @@ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotati if ( depth == 0 ) return null; - double QD = 10.0 * vc.getNegLog10PError() / (double)depth; + double QD = -10.0 * vc.getLog10PError() / (double)depth; Map map = new HashMap(); map.put(getKeyNames().get(0), String.format("%.2f", QD)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java index b782de15f..d4442dc5d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorEngine.java @@ -244,7 +244,7 @@ public class VariantAnnotatorEngine { if ( result != null ) genotypeAnnotations.putAll(result); } - genotypes.add(new Genotype(genotype.getSampleName(), genotype.getAlleles(), genotype.getNegLog10PError(), genotype.getFilters(), genotypeAnnotations, genotype.isPhased())); + genotypes.add(new Genotype(genotype.getSampleName(), genotype.getAlleles(), genotype.getLog10PError(), genotype.getFilters(), genotypeAnnotations, genotype.isPhased())); } return genotypes; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index d4aa21097..b95b35b4a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -331,18 +331,16 @@ public class BeagleOutputToVCFWalker extends RodWalker { genotypes.add(imputedGenotype); } - VariantContext filteredVC; - if ( beagleVarCounts > 0 || DONT_FILTER_MONOMORPHIC_SITES ) - filteredVC = new VariantContext("outputvcf", vc_input.getID(), vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), vc_input.getAlleles(), genotypes, vc_input.getNegLog10PError(), vc_input.filtersWereApplied() ? vc_input.getFilters() : null, vc_input.getAttributes()); - else { + final VariantContextBuilder builder = new VariantContextBuilder(vc_input).source("outputvcf").genotypes(genotypes); + if ( ! ( beagleVarCounts > 0 || DONT_FILTER_MONOMORPHIC_SITES ) ) { Set removedFilters = vc_input.filtersWereApplied() ? new HashSet(vc_input.getFilters()) : new HashSet(1); removedFilters.add(String.format("BGL_RM_WAS_%s",vc_input.getAlternateAllele(0))); - filteredVC = new VariantContext("outputvcf", vc_input.getID(), vc_input.getChr(), vc_input.getStart(), vc_input.getEnd(), new HashSet(Arrays.asList(vc_input.getReference())), genotypes, vc_input.getNegLog10PError(), removedFilters, vc_input.getAttributes()); + builder.alleles(new HashSet(Arrays.asList(vc_input.getReference()))).filters(removedFilters); } - HashMap attributes = new HashMap(filteredVC.getAttributes()); + HashMap attributes = new HashMap(vc_input.getAttributes()); // re-compute chromosome counts - VariantContextUtils.calculateChromosomeCounts(filteredVC, attributes, false); + VariantContextUtils.calculateChromosomeCounts(vc_input, attributes, false); // Get Hapmap AC and AF if (vc_comp != null) { @@ -356,13 +354,11 @@ public class BeagleOutputToVCFWalker extends RodWalker { if( !beagleR2Feature.getR2value().equals(Double.NaN) ) { attributes.put("R2", beagleR2Feature.getR2value().toString() ); } + builder.attributes(attributes); - - vcfWriter.add(new VariantContextBuilder(filteredVC).attributes(attributes).make()); - + vcfWriter.add(builder.make()); return 1; - } public Integer reduceInit() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java index 6b5ffd3ed..efa57c0aa 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/VCFDiffableReader.java @@ -99,7 +99,7 @@ public class VCFDiffableReader implements DiffableReader { vcRoot.add("ID", vc.getID()); vcRoot.add("REF", vc.getReference()); vcRoot.add("ALT", vc.getAlternateAlleles()); - vcRoot.add("QUAL", vc.hasNegLog10PError() ? vc.getNegLog10PError() * 10 : VCFConstants.MISSING_VALUE_v4); + vcRoot.add("QUAL", vc.hasLog10PError() ? vc.getLog10PError() * -10 : VCFConstants.MISSING_VALUE_v4); vcRoot.add("FILTER", vc.getFilters()); // add info fields @@ -111,7 +111,7 @@ public class VCFDiffableReader implements DiffableReader { for (Genotype g : vc.getGenotypes() ) { DiffNode gRoot = DiffNode.empty(g.getSampleName(), vcRoot); gRoot.add("GT", g.getGenotypeString()); - gRoot.add("GQ", g.hasNegLog10PError() ? g.getNegLog10PError() * 10 : VCFConstants.MISSING_VALUE_v4 ); + gRoot.add("GQ", g.hasLog10PError() ? g.getLog10PError() * -10 : VCFConstants.MISSING_VALUE_v4 ); for (Map.Entry attribute : g.getAttributes().entrySet()) { if ( ! attribute.getKey().startsWith("_") ) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java index 049b92084..8278dbab7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationWalker.java @@ -277,14 +277,12 @@ public class VariantFiltrationWalker extends RodWalker { if ( context == null ) return; - VariantContext vc = context.getVariantContext(); + final VariantContext vc = context.getVariantContext(); + final VariantContextBuilder builder = new VariantContextBuilder(vc); // make new Genotypes based on filters - GenotypesContext genotypes; - if ( genotypeFilterExps.size() == 0 ) { - genotypes = null; - } else { - genotypes = GenotypesContext.create(vc.getGenotypes().size()); + if ( genotypeFilterExps.size() > 0 ) { + GenotypesContext genotypes = GenotypesContext.create(vc.getGenotypes().size()); // for each genotype, check filters then create a new object for ( final Genotype g : vc.getGenotypes() ) { @@ -295,11 +293,13 @@ public class VariantFiltrationWalker extends RodWalker { if ( VariantContextUtils.match(vc, g, exp) ) filters.add(exp.name); } - genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), filters, g.getAttributes(), g.isPhased())); + genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getLog10PError(), filters, g.getAttributes(), g.isPhased())); } else { genotypes.add(g); } } + + builder.genotypes(genotypes); } // make a new variant context based on filters @@ -319,14 +319,9 @@ public class VariantFiltrationWalker extends RodWalker { filters.add(exp.name); } } + builder.filters(filters); - VariantContext filteredVC; - if ( genotypes == null ) - filteredVC = new VariantContextBuilder(vc).filters(filters).make(); - else - filteredVC = new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), vc.getAlleles(), genotypes, vc.getNegLog10PError(), filters, vc.getAttributes()); - - writer.add(filteredVC); + writer.add(builder.make()); } public Integer reduce(Integer value, Integer sum) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index a7240ae88..e071de15b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -431,7 +431,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { ArrayList myAlleles = new ArrayList(); - double qual = Genotype.NO_NEG_LOG_10PERROR; + double qual = Genotype.NO_LOG10_PERROR; myAlleles.add(Allele.NO_CALL); myAlleles.add(Allele.NO_CALL); //System.out.println(myAlleles.toString()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index 60513ca5f..83da319ea 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -137,7 +137,8 @@ public class UGCallVariants extends RodWalker { VariantContext vc = VCs.get(0); throw new UserException("There is no ALT allele in any of the VCF records passed in at " + vc.getChr() + ":" + vc.getStart()); } - return new VariantContext("VCwithGLs", VCFConstants.EMPTY_ID_FIELD, variantVC.getChr(), variantVC.getStart(), variantVC.getEnd(), variantVC.getAlleles(), genotypes, VariantContext.NO_NEG_LOG_10PERROR, null, null); + + return new VariantContextBuilder(variantVC).source("VCwithGLs").genotypes(genotypes).make(); } private static GenotypesContext getGenotypesWithGLs(GenotypesContext genotypes) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java index 0fe7e1fc2..c38bb5b42 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java @@ -280,22 +280,13 @@ public class UnifiedGenotyperEngine { attributes.put(VCFConstants.DEPTH_KEY, GL.getDepth()); attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, likelihoods); - genotypes.add(new Genotype(GL.getSample(), noCall, Genotype.NO_NEG_LOG_10PERROR, null, attributes, false)); + genotypes.add(new Genotype(GL.getSample(), noCall, Genotype.NO_LOG10_PERROR, null, attributes, false)); } GenomeLoc loc = refContext.getLocus(); int endLoc = calculateEndPos(alleles, refAllele, loc); - return new VariantContext("UG_call", - VCFConstants.EMPTY_ID_FIELD, loc.getContig(), - loc.getStart(), - endLoc, - alleles, - genotypes, - VariantContext.NO_NEG_LOG_10PERROR, - null, - null, - refContext.getBase()); + return new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), endLoc, alleles).genotypes(genotypes).referenceBaseForIndel(refContext.getBase()).make(); } // private method called by both UnifiedGenotyper and UGCallVariants entry points into the engine @@ -419,8 +410,14 @@ public class UnifiedGenotyperEngine { myAlleles = new HashSet(1); myAlleles.add(vc.getReference()); } - VariantContext vcCall = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), endLoc, - myAlleles, genotypes, phredScaledConfidence/10.0, passesCallThreshold(phredScaledConfidence) ? null : filter, attributes, refContext.getBase()); + + VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), endLoc, myAlleles); + builder.genotypes(genotypes); + builder.log10PError(phredScaledConfidence/-10.0); + if ( ! passesCallThreshold(phredScaledConfidence) ) builder.filters(filter); + builder.attributes(attributes); + builder.referenceBaseForIndel(refContext.getBase()); + VariantContext vcCall = builder.make(); if ( annotationEngine != null ) { // Note: we want to use the *unfiltered* and *unBAQed* context for the annotations @@ -503,10 +500,15 @@ public class UnifiedGenotyperEngine { myAlleles = new HashSet(1); myAlleles.add(vc.getReference()); } - VariantContext vcCall = new VariantContext("UG_call", VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), endLoc, - myAlleles, genotypes, phredScaledConfidence/10.0, passesCallThreshold(phredScaledConfidence) ? null : filter, attributes, vc.getReferenceBaseForIndel()); - return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); + VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), endLoc, myAlleles); + builder.genotypes(genotypes); + builder.log10PError(phredScaledConfidence/-10.0); + if ( ! passesCallThreshold(phredScaledConfidence) ) builder.filters(filter); + builder.attributes(attributes); + builder.referenceBaseForIndel(vc.getReferenceBaseForIndel()); + + return new VariantCallContext(builder.make(), confidentlyCalled(phredScaledConfidence, PofF)); } private int calculateEndPos(Collection alleles, Allele refAllele, GenomeLoc loc) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java index e3dc59b19..aa9ae1517 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/SomaticIndelDetectorWalker.java @@ -58,10 +58,7 @@ import org.broadinstitute.sting.utils.interval.IntervalUtils; import org.broadinstitute.sting.utils.interval.OverlappingIntervalIterator; import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.*; import java.util.*; @@ -1064,9 +1061,9 @@ public class SomaticIndelDetectorWalker extends ReadWalker { Map attrs = call.makeStatsAttributes(null); if ( call.isCall() ) // we made a call - put actual het genotype here: - genotypes.add(new Genotype(sample,alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); + genotypes.add(new Genotype(sample,alleles,Genotype.NO_LOG10_PERROR,null,attrs,false)); else // no call: genotype is ref/ref (but alleles still contain the alt if we observed anything at all) - genotypes.add(new Genotype(sample, homref_alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrs,false)); + genotypes.add(new Genotype(sample, homref_alleles,Genotype.NO_LOG10_PERROR,null,attrs,false)); } Set filters = null; @@ -1074,8 +1071,8 @@ public class SomaticIndelDetectorWalker extends ReadWalker { filters = new HashSet(); filters.add("NoCall"); } - VariantContext vc = new VariantContext("IGv2_Indel_call", VCFConstants.EMPTY_ID_FIELD, refName, start, stop, alleles, genotypes, - -1.0 /* log error */, filters, null, refBases[(int)start-1]); + VariantContext vc = new VariantContextBuilder("IGv2_Indel_call", refName, start, stop, alleles) + .genotypes(genotypes).filters(filters).referenceBaseForIndel(refBases[(int)start-1]).make(); vcf.add(vc); } @@ -1150,11 +1147,11 @@ public class SomaticIndelDetectorWalker extends ReadWalker { GenotypesContext genotypes = GenotypesContext.create(); for ( String sample : normalSamples ) { - genotypes.add(new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsNormal,false)); + genotypes.add(new Genotype(sample, homRefN ? homRefAlleles : alleles,Genotype.NO_LOG10_PERROR,null,attrsNormal,false)); } for ( String sample : tumorSamples ) { - genotypes.add(new Genotype(sample, homRefT ? homRefAlleles : alleles,Genotype.NO_NEG_LOG_10PERROR,null,attrsTumor,false) ); + genotypes.add(new Genotype(sample, homRefT ? homRefAlleles : alleles,Genotype.NO_LOG10_PERROR,null,attrsTumor,false) ); } Set filters = null; @@ -1171,8 +1168,8 @@ public class SomaticIndelDetectorWalker extends ReadWalker { filters.add("TCov"); } - VariantContext vc = new VariantContext("IGv2_Indel_call", VCFConstants.EMPTY_ID_FIELD, refName, start, stop, alleles, genotypes, - -1.0 /* log error */, filters, attrs, refBases[(int)start-1]); + VariantContext vc = new VariantContextBuilder("IGv2_Indel_call", refName, start, stop, alleles) + .genotypes(genotypes).filters(filters).attributes(attrs).referenceBaseForIndel(refBases[(int)start-1]).make(); vcf.add(vc); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 088ff4c71..2d71ea8a8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -132,17 +132,17 @@ public class PhaseByTransmission extends RodWalker { List homRefAlleles = new ArrayList(); homRefAlleles.add(refAllele); homRefAlleles.add(refAllele); - Genotype homRef = new Genotype(g.getSampleName(), homRefAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); + Genotype homRef = new Genotype(g.getSampleName(), homRefAlleles, g.getLog10PError(), null, g.getAttributes(), false); List hetAlleles = new ArrayList(); hetAlleles.add(refAllele); hetAlleles.add(altAllele); - Genotype het = new Genotype(g.getSampleName(), hetAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); + Genotype het = new Genotype(g.getSampleName(), hetAlleles, g.getLog10PError(), null, g.getAttributes(), false); List homVarAlleles = new ArrayList(); homVarAlleles.add(altAllele); homVarAlleles.add(altAllele); - Genotype homVar = new Genotype(g.getSampleName(), homVarAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); + Genotype homVar = new Genotype(g.getSampleName(), homVarAlleles, g.getLog10PError(), null, g.getAttributes(), false); ArrayList genotypes = new ArrayList(); genotypes.add(homRef); @@ -187,7 +187,7 @@ public class PhaseByTransmission extends RodWalker { possiblePhasedChildAlleles.add(momAllele); possiblePhasedChildAlleles.add(dadAllele); - Genotype possiblePhasedChildGenotype = new Genotype(child.getSampleName(), possiblePhasedChildAlleles, child.getNegLog10PError(), child.getFilters(), child.getAttributes(), true); + Genotype possiblePhasedChildGenotype = new Genotype(child.getSampleName(), possiblePhasedChildAlleles, child.getLog10PError(), child.getFilters(), child.getAttributes(), true); possiblePhasedChildGenotypes.add(possiblePhasedChildGenotype); } @@ -204,7 +204,7 @@ public class PhaseByTransmission extends RodWalker { phasedMomAlleles.add(momTransmittedAllele); phasedMomAlleles.add(momUntransmittedAllele); - Genotype phasedMomGenotype = new Genotype(mom.getSampleName(), phasedMomAlleles, mom.getNegLog10PError(), mom.getFilters(), mom.getAttributes(), true); + Genotype phasedMomGenotype = new Genotype(mom.getSampleName(), phasedMomAlleles, mom.getLog10PError(), mom.getFilters(), mom.getAttributes(), true); Allele dadTransmittedAllele = phasedChildGenotype.getAllele(1); Allele dadUntransmittedAllele = dad.getAllele(0) != dadTransmittedAllele ? dad.getAllele(0) : dad.getAllele(1); @@ -213,7 +213,7 @@ public class PhaseByTransmission extends RodWalker { phasedDadAlleles.add(dadTransmittedAllele); phasedDadAlleles.add(dadUntransmittedAllele); - Genotype phasedDadGenotype = new Genotype(dad.getSampleName(), phasedDadAlleles, dad.getNegLog10PError(), dad.getFilters(), dad.getAttributes(), true); + Genotype phasedDadGenotype = new Genotype(dad.getSampleName(), phasedDadAlleles, dad.getLog10PError(), dad.getFilters(), dad.getAttributes(), true); finalGenotypes.add(phasedMomGenotype); finalGenotypes.add(phasedDadGenotype); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java index cac171948..9aa23fdfb 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java @@ -108,7 +108,7 @@ class PhasingUtils { mergedAllelesForSample.add(mergedAllele); } - double mergedGQ = Math.max(gt1.getNegLog10PError(), gt2.getNegLog10PError()); + double mergedGQ = Math.max(gt1.getLog10PError(), gt2.getLog10PError()); Set mergedGtFilters = new HashSet(); // Since gt1 and gt2 were unfiltered, the Genotype remains unfiltered Map mergedGtAttribs = new HashMap(); @@ -121,7 +121,7 @@ class PhasingUtils { } String mergedName = mergeVariantContextNames(vc1.getSource(), vc2.getSource()); - double mergedNegLog10PError = Math.max(vc1.getNegLog10PError(), vc2.getNegLog10PError()); + double mergedLog10PError = Math.min(vc1.getLog10PError(), vc2.getLog10PError()); Set mergedFilters = new HashSet(); // Since vc1 and vc2 were unfiltered, the merged record remains unfiltered Map mergedAttribs = mergeVariantContextAttributes(vc1, vc2); @@ -131,7 +131,7 @@ class PhasingUtils { if ( vc2.hasID() ) mergedIDs.add(vc2.getID()); String mergedID = mergedIDs.isEmpty() ? VCFConstants.EMPTY_ID_FIELD : Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); - VariantContext mergedVc = new VariantContext(mergedName, mergedID, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles(), mergedGenotypes, mergedNegLog10PError, mergedFilters, mergedAttribs); + VariantContext mergedVc = new VariantContextBuilder(mergedName, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles()).id(mergedID).genotypes(mergedGenotypes).log10PError(mergedLog10PError).filters(mergedFilters).attributes(mergedAttribs).make(); mergedAttribs = new HashMap(mergedVc.getAttributes()); VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index 155b37af2..dc0acfb6a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -361,7 +361,7 @@ public class ReadBackedPhasingWalker extends RodWalker can trivially phase a hom site relative to ANY previous site: - Genotype phasedGt = new Genotype(gt.getSampleName(), gt.getAlleles(), gt.getNegLog10PError(), gt.getFilters(), gt.getAttributes(), true); + Genotype phasedGt = new Genotype(gt.getSampleName(), gt.getAlleles(), gt.getLog10PError(), gt.getFilters(), gt.getAttributes(), true); uvc.setGenotype(samp, phasedGt); } else if (gt.isHet()) { // Attempt to phase this het genotype relative to the previous het genotype @@ -397,7 +397,7 @@ public class ReadBackedPhasingWalker extends RodWalker gtAttribs = new HashMap(gt.getAttributes()); gtAttribs.put(PQ_KEY, pr.phaseQuality); - Genotype phasedGt = new Genotype(gt.getSampleName(), allelePair.getAllelesAsList(), gt.getNegLog10PError(), gt.getFilters(), gtAttribs, genotypesArePhased); + Genotype phasedGt = new Genotype(gt.getSampleName(), allelePair.getAllelesAsList(), gt.getLog10PError(), gt.getFilters(), gtAttribs, genotypesArePhased); uvc.setGenotype(samp, phasedGt); } @@ -417,7 +417,7 @@ public class ReadBackedPhasingWalker extends RodWalker handledGtAttribs = new HashMap(handledGt.getAttributes()); handledGtAttribs.put(PQ_KEY, pr.phaseQuality); - Genotype phasedHomGt = new Genotype(handledGt.getSampleName(), handledGt.getAlleles(), handledGt.getNegLog10PError(), handledGt.getFilters(), handledGtAttribs, genotypesArePhased); + Genotype phasedHomGt = new Genotype(handledGt.getSampleName(), handledGt.getAlleles(), handledGt.getLog10PError(), handledGt.getFilters(), handledGtAttribs, genotypesArePhased); interiorUvc.setGenotype(samp, phasedHomGt); } } @@ -1123,7 +1123,7 @@ public class ReadBackedPhasingWalker extends RodWalker alleles; private GenotypesContext genotypes; - private double negLog10PError; + private double log10PError; private Set filters; private Map attributes; private String id; @@ -1136,13 +1136,14 @@ public class ReadBackedPhasingWalker extends RodWalker(vc.getAttributes()); } public VariantContext toVariantContext() { - return new VariantContext(name, id, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes); + return new VariantContextBuilder(name, contig, start, stop, alleles).id(id) + .genotypes(genotypes).log10PError(log10PError).filters(filters).attributes(attributes).make(); } public GenomeLoc getLocation() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/MendelianViolationEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/MendelianViolationEvaluator.java index a0cc393d9..0cadf6c0d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/MendelianViolationEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/MendelianViolationEvaluator.java @@ -147,7 +147,7 @@ public class MendelianViolationEvaluator extends VariantEvaluator { } private boolean includeGenotype(Genotype g) { - return g.getNegLog10PError() > getQThreshold() && g.isCalled(); + return g.getLog10PError() > getQThreshold() && g.isCalled(); } public static boolean isViolation(VariantContext vc, Genotype momG, Genotype dadG, Genotype childG) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java index f357f8a40..edbfb557a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/LeftAlignVariants.java @@ -220,6 +220,6 @@ public class LeftAlignVariants extends RodWalker { newGenotypes.add(Genotype.modifyAlleles(genotype, newAlleles)); } - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes(), refBaseForIndel); + return new VariantContextBuilder(vc).alleles(alleleMap.values()).genotypes(newGenotypes).referenceBaseForIndel(refBaseForIndel).make(); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java index 2d5f0c14f..f1f61d071 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/VariantsToTable.java @@ -326,7 +326,7 @@ public class VariantsToTable extends RodWalker { getters.put("NCALLED", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getNSamples() - vc.getNoCallCount()); } }); getters.put("GQ", new Getter() { public String get(VariantContext vc) { if ( vc.getNSamples() > 1 ) throw new UserException("Cannot get GQ values for multi-sample VCF"); - return String.format("%.2f", 10 * vc.getGenotype(0).getNegLog10PError()); + return String.format("%.2f", -10 * vc.getGenotype(0).getLog10PError()); }}); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index 0f21e1505..abd81fe61 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -272,7 +272,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, String ref = getCachedString(parts[3].toUpperCase()); String alts = getCachedString(parts[4].toUpperCase()); - builder.negLog10PError(parseQual(parts[5])); + builder.log10PError(parseQual(parts[5])); builder.filters(parseFilters(getCachedString(parts[6]))); builder.attributes(parseInfo(parts[7])); @@ -448,16 +448,16 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, protected static Double parseQual(String qualString) { // if we're the VCF 4 missing char, return immediately if ( qualString.equals(VCFConstants.MISSING_VALUE_v4)) - return VariantContext.NO_NEG_LOG_10PERROR; + return VariantContext.NO_LOG10_PERROR; Double val = Double.valueOf(qualString); // check to see if they encoded the missing qual score in VCF 3 style, with either the -1 or -1.0. check for val < 0 to save some CPU cycles if ((val < 0) && (Math.abs(val - VCFConstants.MISSING_QUALITY_v3_DOUBLE) < VCFConstants.VCF_ENCODING_EPSILON)) - return VariantContext.NO_NEG_LOG_10PERROR; + return VariantContext.NO_LOG10_PERROR; // scale and return the value - return val / 10.0; + return val / -10.0; } /** diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java index 37137f716..1aafafc27 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java @@ -204,7 +204,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { mWriter.write(VCFConstants.FIELD_SEPARATOR); // QUAL - if ( !vc.hasNegLog10PError() ) + if ( !vc.hasLog10PError() ) mWriter.write(VCFConstants.MISSING_VALUE_v4); else mWriter.write(getQualValue(vc.getPhredScaledQual())); @@ -353,7 +353,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { // some exceptions if ( key.equals(VCFConstants.GENOTYPE_QUALITY_KEY) ) { - if ( Math.abs(g.getNegLog10PError() - Genotype.NO_NEG_LOG_10PERROR) < 1e-6) + if ( Math.abs(g.getLog10PError() + Genotype.NO_LOG10_PERROR) < 1e-6) val = VCFConstants.MISSING_VALUE_v4; else { val = getQualValue(Math.min(g.getPhredScaledQual(), VCFConstants.MAX_GENOTYPE_QUAL)); @@ -447,7 +447,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { keys.addAll(g.getAttributes().keySet()); if ( g.isAvailable() ) sawGoodGT = true; - if ( g.hasNegLog10PError() ) + if ( g.hasLog10PError() ) sawGoodQual = true; if (g.isFiltered() && g.isCalled()) sawGenotypeFilter = true; diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index 971400ca0..7d71a9c5a 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -139,7 +139,7 @@ public class VCF3Codec extends AbstractVCFCodec { for (int genotypeOffset = 1; genotypeOffset < nParts; genotypeOffset++) { int GTValueSplitSize = ParsingUtils.split(genotypeParts[genotypeOffset], GTValueArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); - double GTQual = VariantContext.NO_NEG_LOG_10PERROR; + double GTQual = VariantContext.NO_LOG10_PERROR; Set genotypeFilters = null; Map gtAttributes = null; String sampleName = sampleNameIterator.next(); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index 42c224fe9..b7b7eb5f7 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -166,7 +166,7 @@ public class VCFCodec extends AbstractVCFCodec { for (int genotypeOffset = 1; genotypeOffset < nParts; genotypeOffset++) { int GTValueSplitSize = ParsingUtils.split(genotypeParts[genotypeOffset], GTValueArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); - double GTQual = VariantContext.NO_NEG_LOG_10PERROR; + double GTQual = VariantContext.NO_LOG10_PERROR; Set genotypeFilters = null; Map gtAttributes = null; String sampleName = sampleNameIterator.next(); diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index b8672a7bd..e754c215d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -27,10 +27,7 @@ package org.broadinstitute.sting.utils.gcf; import org.broadinstitute.sting.utils.codecs.vcf.StandardVCFWriter; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.*; import java.util.*; @@ -72,7 +69,7 @@ public class GCF { alleleOffsets[i+1] = GCFHeaderBuilder.encodeAllele(vc.getAlternateAllele(i)); } - qual = (float)vc.getNegLog10PError(); //qualToByte(vc.getPhredScaledQual()); + qual = (float)vc.getLog10PError(); //qualToByte(vc.getPhredScaledQual()); info = infoFieldString(vc, GCFHeaderBuilder); filterOffset = GCFHeaderBuilder.encodeString(StandardVCFWriter.getFilterString(vc)); @@ -142,14 +139,14 @@ public class GCF { public VariantContext decode(final String source, final GCFHeader header) { final String contig = header.getString(chromOffset); alleleMap = header.getAlleles(alleleOffsets); - double negLog10PError = qual; // QualityUtils.qualToErrorProb(qual); - Set filters = header.getFilters(filterOffset); - Map attributes = new HashMap(); - attributes.put("INFO", info); - Byte refPadByte = refPad == 0 ? null : refPad; - GenotypesContext genotypes = decodeGenotypes(header); - return new VariantContext(source, VCFConstants.EMPTY_ID_FIELD, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); + VariantContextBuilder builder = new VariantContextBuilder(source, contig, start, stop, alleleMap); + builder.genotypes(decodeGenotypes(header)); + builder.log10PError(qual); + builder.filters(header.getFilters(filterOffset)); + builder.attribute("INFO", info); + builder.referenceBaseForIndel(refPad == 0 ? null : refPad); + return builder.make(); } private GenotypesContext decodeGenotypes(final GCFHeader header) { diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java index dd1fb091c..f8fdd9291 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java @@ -84,14 +84,14 @@ public class GCFGenotype { public Genotype decode(final String sampleName, final GCFHeader header, GCF GCF, List alleleIndex) { final List alleles = decodeAlleles(gt, alleleIndex); - final double negLog10PError = gq / 10.0; + final double log10PError = gq / -10.0; final Set filters = Collections.emptySet(); final Map attributes = new HashMap(); attributes.put("DP", dp); attributes.put("AD", ad); attributes.put("PL", pl); - return new Genotype(sampleName, alleles, negLog10PError, filters, attributes, false); + return new Genotype(sampleName, alleles, log10PError, filters, attributes, false); } private static int encodeAlleles(List gtList, List allAlleles) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java index 4ffc8e966..d3cc7d6a5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java @@ -12,19 +12,19 @@ import java.util.*; * @author depristo */ final class CommonInfo { - public static final double NO_NEG_LOG_10PERROR = -1.0; + public static final double NO_LOG10_PERROR = 1.0; private static Set NO_FILTERS = Collections.emptySet(); private static Map NO_ATTRIBUTES = Collections.unmodifiableMap(new HashMap()); - private double negLog10PError = NO_NEG_LOG_10PERROR; + private double log10PError = NO_LOG10_PERROR; private String name = null; private Set filters = null; private Map attributes = NO_ATTRIBUTES; - public CommonInfo(String name, double negLog10PError, Set filters, Map attributes) { + public CommonInfo(String name, double log10PError, Set filters, Map attributes) { this.name = name; - setNegLog10PError(negLog10PError); + setLog10PError(log10PError); if ( filters != null && ! filters.isEmpty() ) this.filters = filters; if ( attributes != null && ! attributes.isEmpty() ) { @@ -97,22 +97,21 @@ final class CommonInfo { // // --------------------------------------------------------------------------------------------------------- - public boolean hasNegLog10PError() { - return getNegLog10PError() != NO_NEG_LOG_10PERROR; + public boolean hasLog10PError() { + return getLog10PError() != NO_LOG10_PERROR; } /** * @return the -1 * log10-based error estimate */ - public double getNegLog10PError() { return negLog10PError; } - public double getPhredScaledQual() { return getNegLog10PError() * 10; } + public double getLog10PError() { return log10PError; } + public double getPhredScaledQual() { return getLog10PError() * -10; } - public void setNegLog10PError(double negLog10PError) { - if ( negLog10PError < 0 && negLog10PError != NO_NEG_LOG_10PERROR ) throw new IllegalArgumentException("BUG: negLog10PError cannot be < than 0 : " + negLog10PError); - if ( Double.isInfinite(negLog10PError) ) throw new IllegalArgumentException("BUG: negLog10PError should not be Infinity"); - if ( Double.isNaN(negLog10PError) ) throw new IllegalArgumentException("BUG: negLog10PError should not be NaN"); - - this.negLog10PError = negLog10PError; + public void setLog10PError(double log10PError) { + if ( this.log10PError > 0 && this.log10PError != NO_LOG10_PERROR) throw new IllegalArgumentException("BUG: log10PError cannot be > 0 : " + this.log10PError); + if ( Double.isInfinite(this.log10PError) ) throw new IllegalArgumentException("BUG: log10PError should not be Infinity"); + if ( Double.isNaN(this.log10PError) ) throw new IllegalArgumentException("BUG: log10PError should not be NaN"); + this.log10PError = log10PError; } // --------------------------------------------------------------------------------------------------------- diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java index f1574cec2..b100d6da5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -18,20 +18,20 @@ public class Genotype { public final static String UNPHASED_ALLELE_SEPARATOR = "/"; protected CommonInfo commonInfo; - public final static double NO_NEG_LOG_10PERROR = CommonInfo.NO_NEG_LOG_10PERROR; + public final static double NO_LOG10_PERROR = CommonInfo.NO_LOG10_PERROR; protected List alleles = null; // new ArrayList(); protected Type type = null; protected boolean isPhased = false; - public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased) { - this(sampleName, alleles, negLog10PError, filters, attributes, isPhased, null); + public Genotype(String sampleName, List alleles, double log10PError, Set filters, Map attributes, boolean isPhased) { + this(sampleName, alleles, log10PError, filters, attributes, isPhased, null); } - public Genotype(String sampleName, List alleles, double negLog10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { + public Genotype(String sampleName, List alleles, double log10PError, Set filters, Map attributes, boolean isPhased, double[] log10Likelihoods) { if ( alleles != null ) this.alleles = Collections.unmodifiableList(alleles); - commonInfo = new CommonInfo(sampleName, negLog10PError, filters, attributes); + commonInfo = new CommonInfo(sampleName, log10PError, filters, attributes); if ( log10Likelihoods != null ) commonInfo.putAttribute(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); this.isPhased = isPhased; @@ -42,23 +42,23 @@ public class Genotype { * Creates a new Genotype for sampleName with genotype according to alleles. * @param sampleName * @param alleles - * @param negLog10PError the confidence in these alleles + * @param log10PError the confidence in these alleles * @param log10Likelihoods a log10 likelihoods for each of the genotype combinations possible for alleles, in the standard VCF ordering, or null if not known */ - public Genotype(String sampleName, List alleles, double negLog10PError, double[] log10Likelihoods) { - this(sampleName, alleles, negLog10PError, null, null, false, log10Likelihoods); + public Genotype(String sampleName, List alleles, double log10PError, double[] log10Likelihoods) { + this(sampleName, alleles, log10PError, null, null, false, log10Likelihoods); } - public Genotype(String sampleName, List alleles, double negLog10PError) { - this(sampleName, alleles, negLog10PError, null, null, false); + public Genotype(String sampleName, List alleles, double log10PError) { + this(sampleName, alleles, log10PError, null, null, false); } public Genotype(String sampleName, List alleles) { - this(sampleName, alleles, NO_NEG_LOG_10PERROR, null, null, false); + this(sampleName, alleles, NO_LOG10_PERROR, null, null, false); } public Genotype(String sampleName, Genotype parent) { - this(sampleName, parent.getAlleles(), parent.getNegLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); + this(sampleName, parent.getAlleles(), parent.getLog10PError(), parent.getFilters(), parent.getAttributes(), parent.isPhased()); } @@ -70,15 +70,15 @@ public class Genotype { // --------------------------------------------------------------------------------------------------------- public static Genotype modifyName(Genotype g, String name) { - return new Genotype(name, g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); + return new Genotype(name, g.getAlleles(), g.getLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); } public static Genotype modifyAttributes(Genotype g, Map attributes) { - return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attributes, g.isPhased()); + return new Genotype(g.getSampleName(), g.getAlleles(), g.getLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attributes, g.isPhased()); } public static Genotype modifyAlleles(Genotype g, List alleles) { - return new Genotype(g.getSampleName(), alleles, g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); + return new Genotype(g.getSampleName(), alleles, g.getLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, g.getAttributes(), g.isPhased()); } /** @@ -335,8 +335,8 @@ public class Genotype { public boolean isFiltered() { return commonInfo.isFiltered(); } public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } public boolean filtersWereApplied() { return commonInfo.filtersWereApplied(); } - public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } - public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } + public boolean hasLog10PError() { return commonInfo.hasLog10PError(); } + public double getLog10PError() { return commonInfo.getLog10PError(); } public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } public Map getAttributes() { return commonInfo.getAttributes(); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 9875680b0..ad9ecace8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -164,7 +164,7 @@ import java.util.*; */ public class VariantContext implements Feature { // to enable tribble intergration protected CommonInfo commonInfo = null; - public final static double NO_NEG_LOG_10PERROR = CommonInfo.NO_NEG_LOG_10PERROR; + public final static double NO_LOG10_PERROR = CommonInfo.NO_LOG10_PERROR; public final static String UNPARSED_GENOTYPE_MAP_KEY = "_UNPARSED_GENOTYPE_MAP_"; public final static String UNPARSED_GENOTYPE_PARSER_KEY = "_UNPARSED_GENOTYPE_PARSER_"; @@ -235,7 +235,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param stop the stop reference base (one based) * @param alleles alleles * @param genotypes genotypes map - * @param negLog10PError qual + * @param log10PError qual * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes * @param referenceBaseForIndel padded reference base @@ -243,29 +243,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @deprecated replaced by {@link VariantContextBuilder} */ @Deprecated - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, referenceBaseForIndel, false, ALL_VALIDATION); - } - - - /** - * the complete constructor. Makes a complete VariantContext from its arguments - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes map - * @param negLog10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * - * @deprecated replaced by {@link VariantContextBuilder} - */ - @Deprecated - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double negLog10PError, Set filters, Map attributes) { - this(source, ID, contig, start, stop, alleles, genotypes, negLog10PError, filters, attributes, null, false, ALL_VALIDATION); + protected VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double log10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { + this(source, ID, contig, start, stop, alleles, genotypes, log10PError, filters, attributes, referenceBaseForIndel, false, ALL_VALIDATION); } /** @@ -281,7 +260,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati */ @Deprecated public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles) { - this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_NEG_LOG_10PERROR, null, null, null, false, ALL_VALIDATION); + this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_LOG10_PERROR, null, null, null, false, ALL_VALIDATION); } /** @@ -290,7 +269,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param other the VariantContext to copy */ protected VariantContext(VariantContext other) { - this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getNegLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, NO_VALIDATION); + this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, NO_VALIDATION); } /** @@ -302,7 +281,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param stop the stop reference base (one based) * @param alleles alleles * @param genotypes genotypes map - * @param negLog10PError qual + * @param log10PError qual * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes * @param referenceBaseForIndel padded reference base @@ -312,7 +291,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati protected VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, - double negLog10PError, Set filters, Map attributes, + double log10PError, Set filters, Map attributes, Byte referenceBaseForIndel, boolean genotypesAreUnparsed, EnumSet validationToPerform ) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } @@ -335,7 +314,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati } } - this.commonInfo = new CommonInfo(source, negLog10PError, filters, attributes); + this.commonInfo = new CommonInfo(source, log10PError, filters, attributes); REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; // todo -- remove me when this check is no longer necessary @@ -600,8 +579,8 @@ public class VariantContext implements Feature { // to enable tribble intergrati public boolean isFiltered() { return commonInfo.isFiltered(); } public boolean isNotFiltered() { return commonInfo.isNotFiltered(); } public boolean filtersWereApplied() { return commonInfo.filtersWereApplied(); } - public boolean hasNegLog10PError() { return commonInfo.hasNegLog10PError(); } - public double getNegLog10PError() { return commonInfo.getNegLog10PError(); } + public boolean hasLog10PError() { return commonInfo.hasLog10PError(); } + public double getLog10PError() { return commonInfo.getLog10PError(); } public double getPhredScaledQual() { return commonInfo.getPhredScaledQual(); } public Map getAttributes() { return commonInfo.getAttributes(); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java index a2065d458..cb2ef4678 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java @@ -29,8 +29,6 @@ import org.broad.tribble.Feature; import org.broad.tribble.TribbleException; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; -import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import java.util.*; @@ -69,7 +67,7 @@ public class VariantContextBuilder { // optional -> these are set to the appropriate default value private String ID = VCFConstants.EMPTY_ID_FIELD; private GenotypesContext genotypes = GenotypesContext.NO_GENOTYPES; - private double negLog10PError = VariantContext.NO_NEG_LOG_10PERROR; + private double log10PError = VariantContext.NO_LOG10_PERROR; private Set filters = null; private Map attributes = null; private boolean attributesCanBeModified = false; @@ -116,7 +114,7 @@ public class VariantContextBuilder { this.genotypes = parent.genotypes; this.genotypesAreUnparsed = parent.hasAttribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY); this.ID = parent.getID(); - this.negLog10PError = parent.getNegLog10PError(); + this.log10PError = parent.getLog10PError(); this.referenceBaseForIndel = parent.getReferenceBaseForIndel(); this.source = parent.getSource(); this.start = parent.getStart(); @@ -279,13 +277,13 @@ public class VariantContextBuilder { } /** - * Tells us that the resulting VariantContext should have negLog10PError - * @param negLog10PError + * Tells us that the resulting VariantContext should have log10PError + * @param log10PError * @return */ - @Requires("negLog10PError >= 0 || negLog10PError == VariantContext.NO_NEG_LOG_10PERROR") - public VariantContextBuilder negLog10PError(final double negLog10PError) { - this.negLog10PError = negLog10PError; + @Requires("log10PError <= 0 || log10PError == VariantContext.NO_LOG10_PERROR") + public VariantContextBuilder log10PError(final double log10PError) { + this.log10PError = log10PError; return this; } @@ -374,7 +372,7 @@ public class VariantContextBuilder { */ public VariantContext make() { return new VariantContext(source, ID, contig, start, stop, alleles, - genotypes, negLog10PError, filters, attributes, + genotypes, log10PError, filters, attributes, referenceBaseForIndel, genotypesAreUnparsed, toValidate); } } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 972f70689..12b2ce406 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -113,7 +113,7 @@ public class VariantContextUtils { Map attrs = new HashMap(g.getAttributes()); attrs.remove(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY); attrs.remove(VCFConstants.GENOTYPE_LIKELIHOODS_KEY); - return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attrs, g.isPhased()); + return new Genotype(g.getSampleName(), g.getAlleles(), g.getLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attrs, g.isPhased()); } public static VariantContext createVariantContextWithPaddedAlleles(VariantContext inputVC, boolean refBaseShouldBeAppliedToEndOfAlleles) { @@ -178,14 +178,12 @@ public class VariantContextUtils { newGenotypeAlleles.add(Allele.NO_CALL); } } - genotypes.add(new Genotype(g.getSampleName(), newGenotypeAlleles, g.getNegLog10PError(), + genotypes.add(new Genotype(g.getSampleName(), newGenotypeAlleles, g.getLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased())); } - // Do not change the filter state if filters were not applied to this context - Set inputVCFilters = inputVC.getFiltersMaybeNull(); - return new VariantContext(inputVC.getSource(), inputVC.getID(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVCFilters, inputVC.getAttributes(),refByte); + return new VariantContextBuilder(inputVC).alleles(alleles).genotypes(genotypes).make(); } else return inputVC; @@ -373,12 +371,12 @@ public class VariantContextUtils { final GenotypesContext genotypes = GenotypesContext.create(vc.getNSamples()); for ( final Genotype g : vc.getGenotypes() ) { Map genotypeAttributes = subsetAttributes(g.commonInfo, keysToPreserve); - genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.getFilters(), + genotypes.add(new Genotype(g.getSampleName(), g.getAlleles(), g.getLog10PError(), g.getFilters(), genotypeAttributes, g.isPhased())); } return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), - vc.getAlleles(), genotypes, vc.getNegLog10PError(), vc.getFilters(), attributes, vc.getReferenceBaseForIndel()); + vc.getAlleles(), genotypes, vc.getLog10PError(), vc.getFilters(), attributes, vc.getReferenceBaseForIndel()); } public enum GenotypeMergeType { @@ -475,7 +473,7 @@ public class VariantContextUtils { int depth = 0; int maxAC = -1; final Map attributesWithMaxAC = new TreeMap(); - double negLog10PError = -1; + double log10PError = 1; VariantContext vcWithMaxAC = null; Set addedSamples = new HashSet(first.getNSamples()); GenotypesContext genotypes = GenotypesContext.create(); @@ -504,7 +502,7 @@ public class VariantContextUtils { mergeGenotypes(genotypes, addedSamples, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); - negLog10PError = Math.max(negLog10PError, vc.isVariant() ? vc.getNegLog10PError() : -1); + log10PError = Math.min(log10PError, vc.isVariant() ? vc.getLog10PError() : 1); filters.addAll(vc.getFilters()); @@ -610,10 +608,15 @@ public class VariantContextUtils { final String ID = rsIDs.isEmpty() ? VCFConstants.EMPTY_ID_FIELD : Utils.join(",", rsIDs); - VariantContext merged = new VariantContext(name, ID, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, negLog10PError, filters, (mergeInfoWithMaxAC ? attributesWithMaxAC : attributes) ); - // Trim the padded bases of all alleles if necessary - merged = createVariantContextWithTrimmedAlleles(merged); + final VariantContextBuilder builder = new VariantContextBuilder().source(name).id(ID); + builder.loc(loc.getContig(), loc.getStart(), loc.getStop()); + builder.alleles(alleles); + builder.genotypes(genotypes); + builder.log10PError(log10PError); + builder.filters(filters).attributes(mergeInfoWithMaxAC ? attributesWithMaxAC : attributes); + // Trim the padded bases of all alleles if necessary + VariantContext merged = createVariantContextWithTrimmedAlleles(builder.make()); if ( printMessages && remapped ) System.out.printf("Remapped => %s%n", merged); return merged; } @@ -648,6 +651,7 @@ public class VariantContextUtils { return true; } + public static VariantContext createVariantContextWithTrimmedAlleles(VariantContext inputVC) { // see if we need to trim common reference base from all alleles boolean trimVC; @@ -713,8 +717,9 @@ public class VariantContextUtils { genotypes.add(Genotype.modifyAlleles(genotype, trimmedAlleles)); } - return new VariantContext(inputVC.getSource(), inputVC.getID(), inputVC.getChr(), inputVC.getStart(), inputVC.getEnd(), alleles, genotypes, inputVC.getNegLog10PError(), inputVC.filtersWereApplied() ? inputVC.getFilters() : null, attributes, new Byte(inputVC.getReference().getBases()[0])); + final VariantContextBuilder builder = new VariantContextBuilder(inputVC); + return builder.alleles(alleles).genotypes(genotypes).attributes(attributes).referenceBaseForIndel(new Byte(inputVC.getReference().getBases()[0])).make(); } return inputVC; @@ -910,7 +915,7 @@ public class VariantContextUtils { if ( uniqifySamples || alleleMapping.needsRemapping() ) { final List alleles = alleleMapping.needsRemapping() ? alleleMapping.remap(g.getAlleles()) : g.getAlleles(); - newG = new Genotype(name, alleles, g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased()); + newG = new Genotype(name, alleles, g.getLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased()); } mergedGenotypes.add(newG); @@ -954,8 +959,7 @@ public class VariantContextUtils { newGenotypes.add(Genotype.modifyAlleles(genotype, newAlleles)); } - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), alleleMap.values(), newGenotypes, vc.getNegLog10PError(), vc.filtersWereApplied() ? vc.getFilters() : null, vc.getAttributes()); - + return new VariantContextBuilder(vc).alleles(alleleMap.values()).genotypes(newGenotypes).make(); } public static VariantContext purgeUnallowedGenotypeAttributes(VariantContext vc, Set allowedAttributes) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContext.java index a59ed7abe..ccce21f52 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContext.java @@ -64,7 +64,7 @@ class VariantJEXLContext implements JexlContext { x.put("CHROM", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getChr(); }}); x.put("POS", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getStart(); }}); x.put("TYPE", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getType().toString(); }}); - x.put("QUAL", new AttributeGetter() { public Object get(VariantContext vc) { return 10 * vc.getNegLog10PError(); }}); + x.put("QUAL", new AttributeGetter() { public Object get(VariantContext vc) { return -10 * vc.getLog10PError(); }}); x.put("ALLELES", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getAlleles(); }}); x.put("N_ALLELES", new AttributeGetter() { public Object get(VariantContext vc) { return vc.getNAlleles(); }}); x.put("FILTER", new AttributeGetter() { public Object get(VariantContext vc) { return vc.isFiltered() ? "1" : "0"; }}); diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java index 4b21e09e3..96a33b738 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/genotype/vcf/VCFWriterUnitTest.java @@ -2,10 +2,7 @@ package org.broadinstitute.sting.utils.genotype.vcf; import org.broad.tribble.Tribble; import org.broad.tribble.readers.AsciiLineReader; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.testng.Assert; @@ -136,9 +133,8 @@ public class VCFWriterUnitTest extends BaseTest { genotypes.add(gt); } - return new VariantContext("RANDOM", VCFConstants.EMPTY_ID_FIELD, loc.getContig(), loc.getStart(), loc.getStop(), alleles, genotypes, 0, filters, attributes, (byte)'A'); - - + return new VariantContextBuilder("RANDOM", loc.getContig(), loc.getStart(), loc.getStop(), alleles) + .genotypes(genotypes).attributes(attributes).referenceBaseForIndel((byte)'A').make(); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java index c4f1efd04..e0a037105 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java @@ -71,8 +71,8 @@ public class GenotypeUnitTest extends BaseTest { // public boolean sameGenotype(Genotype other) // public boolean sameGenotype(Genotype other, boolean ignorePhase) // public String getSampleName() -// public boolean hasNegLog10PError() -// public double getNegLog10PError() +// public boolean hasLog10PError() +// public double getLog10PError() // public double getPhredScaledQual() // public boolean hasAttribute(String key) // public Object getAttribute(String key) diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java index 6bc71a76d..a71949369 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextBenchmark.java @@ -356,7 +356,7 @@ public class VariantContextBenchmark extends SimpleBenchmark { // for ( final org.broadinstitute.sting.utils.variantcontext.v13.Genotype g : vc.getGenotypes().values() ) { // String name = g.getSampleName()+"_"+i; // gc.put(name, new org.broadinstitute.sting.utils.variantcontext.v13.Genotype(name, -// g.getAlleles(), g.getNegLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased(), g.getLikelihoods().getAsVector())); +// g.getAlleles(), g.getLog10PError(), g.getFilters(), g.getAttributes(), g.isPhased(), g.getLikelihoods().getAsVector())); // toMerge.add(org.broadinstitute.sting.utils.variantcontext.v13.VariantContext.modifyGenotypes(vc, gc)); // } // } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 200f3859b..cdbadbbb6 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -91,45 +91,45 @@ public class VariantContextUnitTest extends BaseTest { // test INDELs alleles = Arrays.asList(Aref, ATC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(Tref, TA, TC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, AC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, Allele.create("ATCTC")); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); // test MIXED alleles = Arrays.asList(TAref, T, TC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(TAref, T, AC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(ACref, ATC, AT); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(Aref, T, symbolic); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); // test SYMBOLIC alleles = Arrays.asList(Tref, symbolic); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getType(), VariantContext.Type.SYMBOLIC); } @@ -200,7 +200,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingDeletionVariantContext() { List alleles = Arrays.asList(ATCref, del); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getChr(), delLoc); Assert.assertEquals(vc.getStart(), delLocStart); @@ -227,7 +227,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingInsertionVariantContext() { List alleles = Arrays.asList(delRef, ATC); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getChr(), insLoc); Assert.assertEquals(vc.getStart(), insLocStart); @@ -550,7 +550,7 @@ public class VariantContextUnitTest extends BaseTest { @Test(dataProvider = "getAlleles") public void testMergeAlleles(GetAllelesTest cfg) { final List altAlleles = cfg.alleles.subList(1, cfg.alleles.size()); - final VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, CommonInfo.NO_NEG_LOG_10PERROR, null, null, (byte)'A'); + final VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); Assert.assertEquals(vc.getAlleles(), cfg.alleles, "VC alleles not the same as input alleles"); Assert.assertEquals(vc.getNAlleles(), cfg.alleles.size(), "VC getNAlleles not the same as input alleles size"); @@ -653,7 +653,7 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertEquals(cfg.vc.getAttributes(), cfg.copy.getAttributes()); Assert.assertEquals(cfg.vc.getID(), cfg.copy.getID()); Assert.assertEquals(cfg.vc.getGenotypes(), cfg.copy.getGenotypes()); - Assert.assertEquals(cfg.vc.getNegLog10PError(), cfg.copy.getNegLog10PError()); + Assert.assertEquals(cfg.vc.getLog10PError(), cfg.copy.getLog10PError()); Assert.assertEquals(cfg.vc.getFilters(), cfg.copy.getFilters()); } @@ -705,7 +705,7 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertEquals(sub.getChr(), vc.getChr()); Assert.assertEquals(sub.getStart(), vc.getStart()); Assert.assertEquals(sub.getEnd(), vc.getEnd()); - Assert.assertEquals(sub.getNegLog10PError(), vc.getNegLog10PError()); + Assert.assertEquals(sub.getLog10PError(), vc.getLog10PError()); Assert.assertEquals(sub.getFilters(), vc.getFilters()); Assert.assertEquals(sub.getID(), vc.getID()); Assert.assertEquals(sub.getReferenceBaseForIndel(), vc.getReferenceBaseForIndel()); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index a48596ca1..7857c66fc 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -525,7 +525,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { Genotype expectedValue = expected.get(value.getSampleName()); Assert.assertEquals(value.alleles, expectedValue.alleles, "Alleles in Genotype aren't equal"); - Assert.assertEquals(value.getNegLog10PError(), expectedValue.getNegLog10PError(), "GQ values aren't equal"); + Assert.assertEquals(value.getLog10PError(), expectedValue.getLog10PError(), "GQ values aren't equal"); Assert.assertEquals(value.hasLikelihoods(), expectedValue.hasLikelihoods(), "Either both have likelihoods or both not"); if ( value.hasLikelihoods() ) Assert.assertEquals(value.getLikelihoods().getAsVector(), expectedValue.getLikelihoods().getAsVector(), "Genotype likelihoods aren't equal"); From f685fff79b3e07b430cbfa411775b8a7c422b8f9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 21:32:43 -0500 Subject: [PATCH 069/113] Killing the final versions of old new VariantContext interface --- .../beagle/BeagleOutputToVCFWalker.java | 4 +- .../utils/variantcontext/VariantContext.java | 38 ---------- .../variantcontext/VariantContextUtils.java | 3 +- .../refdata/RefMetaDataTrackerUnitTest.java | 6 +- .../VariantContextUnitTest.java | 69 ++++++++++--------- .../VariantContextUtilsUnitTest.java | 3 +- .../VariantJEXLContextUnitTest.java | 2 +- 7 files changed, 46 insertions(+), 79 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index b95b35b4a..64c4948ff 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -122,7 +122,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { protected static String line = null; private final double MIN_PROB_ERROR = 0.000001; - private final double MAX_GENOTYPE_QUALITY = 6.0; + private final double MAX_GENOTYPE_QUALITY = -6.0; public void initialize() { @@ -292,7 +292,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { if (probWrongGenotype < MIN_PROB_ERROR) genotypeQuality = MAX_GENOTYPE_QUALITY; else - genotypeQuality = -log10(probWrongGenotype); + genotypeQuality = log10(probWrongGenotype); HashMap originalAttributes = new HashMap(g.getAttributes()); diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index ad9ecace8..5ad734b79 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -225,44 +225,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - /** - * the complete constructor. Makes a complete VariantContext from its arguments - * This is the only constructor that is able to create indels! DO NOT USE THE OTHER ONES. - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * @param genotypes genotypes map - * @param log10PError qual - * @param filters filters: use null for unfiltered and empty set for passes filters - * @param attributes attributes - * @param referenceBaseForIndel padded reference base - * - * @deprecated replaced by {@link VariantContextBuilder} - */ - @Deprecated - protected VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double log10PError, Set filters, Map attributes, Byte referenceBaseForIndel) { - this(source, ID, contig, start, stop, alleles, genotypes, log10PError, filters, attributes, referenceBaseForIndel, false, ALL_VALIDATION); - } - - /** - * Create a new variant context without genotypes and no Perror, no filters, and no attributes - * - * @param source source - * @param contig the contig - * @param start the start base (one based) - * @param stop the stop reference base (one based) - * @param alleles alleles - * - * @deprecated replaced by {@link VariantContextBuilder} - */ - @Deprecated - public VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles) { - this(source, ID, contig, start, stop, alleles, NO_GENOTYPES, CommonInfo.NO_LOG10_PERROR, null, null, null, false, ALL_VALIDATION); - } - /** * Copy constructor * diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 12b2ce406..b6e0d2e4d 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -375,8 +375,7 @@ public class VariantContextUtils { genotypeAttributes, g.isPhased())); } - return new VariantContext(vc.getSource(), vc.getID(), vc.getChr(), vc.getStart(), vc.getEnd(), - vc.getAlleles(), genotypes, vc.getLog10PError(), vc.getFilters(), attributes, vc.getReferenceBaseForIndel()); + return new VariantContextBuilder(vc).genotypes(genotypes).attributes(attributes).make(); } public enum GenotypeMergeType { diff --git a/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java index 43e93de1a..9de4d5c04 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/refdata/RefMetaDataTrackerUnitTest.java @@ -66,9 +66,9 @@ public class RefMetaDataTrackerUnitTest { C = Allele.create("C"); G = Allele.create("G"); T = Allele.create("T"); - AC_SNP = new VariantContext("x", VCFConstants.EMPTY_ID_FIELD, "chr1", START_POS, START_POS, Arrays.asList(A, C)); - AG_SNP = new VariantContext("x", VCFConstants.EMPTY_ID_FIELD, "chr1", START_POS, START_POS, Arrays.asList(A, G)); - AT_SNP = new VariantContext("x", VCFConstants.EMPTY_ID_FIELD, "chr1", START_POS, START_POS, Arrays.asList(A, T)); + AC_SNP = new VariantContextBuilder("x", "chr1", START_POS, START_POS, Arrays.asList(A, C).make()); + AG_SNP = new VariantContextBuilder("x", "chr1", START_POS, START_POS, Arrays.asList(A, G).make()); + AT_SNP = new VariantContextBuilder("x", "chr1", START_POS, START_POS, Arrays.asList(A, T).make()); span10_10 = makeSpan(10, 10); span1_20 = makeSpan(1, 20); span10_20 = makeSpan(10, 20); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index cdbadbbb6..4c0d22f70 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -12,6 +12,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.Assert; +import java.lang.reflect.Array; import java.util.*; @@ -39,6 +40,8 @@ public class VariantContextUnitTest extends BaseTest { int mixedLocStart = 20; int mixedLocStop = 23; + VariantContextBuilder basicBuilder, snpBuilder, insBuilder; + @BeforeSuite public void before() { del = Allele.create("-"); @@ -52,6 +55,10 @@ public class VariantContextUnitTest extends BaseTest { ATC = Allele.create("ATC"); ATCref = Allele.create("ATC", true); + + basicBuilder = new VariantContextBuilder("test", snpLoc,snpLocStart, snpLocStop, Arrays.asList(Aref, T)).referenceBaseForIndel((byte)'A'); + snpBuilder = new VariantContextBuilder("test", snpLoc,snpLocStart, snpLocStop, Arrays.asList(Aref, T)).referenceBaseForIndel((byte)'A'); + insBuilder = new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATC)).referenceBaseForIndel((byte)'A'); } @Test @@ -68,68 +75,68 @@ public class VariantContextUnitTest extends BaseTest { // test REF List alleles = Arrays.asList(Tref); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); + VariantContext vc = snpBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.NO_VARIATION); // test SNPs alleles = Arrays.asList(Tref, A); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); + vc = snpBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.SNP); alleles = Arrays.asList(Tref, A, C); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); + vc = snpBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.SNP); // test MNPs alleles = Arrays.asList(ACref, TA); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles); + vc = snpBuilder.alleles(alleles).stop(snpLocStop+1).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.MNP); alleles = Arrays.asList(ATCref, CAT, Allele.create("GGG")); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+2).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.MNP); // test INDELs alleles = Arrays.asList(Aref, ATC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+2).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(Tref, TA, TC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, AC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+2).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); alleles = Arrays.asList(ATCref, A, Allele.create("ATCTC")); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+2, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+2).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.INDEL); // test MIXED alleles = Arrays.asList(TAref, T, TC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+1).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(TAref, T, AC); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+1).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(ACref, ATC, AT); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop+1, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+1).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); alleles = Arrays.asList(Aref, T, symbolic); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.MIXED); // test SYMBOLIC alleles = Arrays.asList(Tref, symbolic); - vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + vc = basicBuilder.alleles(alleles).stop(snpLocStop+2).make(); Assert.assertEquals(vc.getType(), VariantContext.Type.SYMBOLIC); } @@ -137,8 +144,8 @@ public class VariantContextUnitTest extends BaseTest { public void testMultipleSNPAlleleOrdering() { final List allelesNaturalOrder = Arrays.asList(Aref, C, T); final List allelesUnnaturalOrder = Arrays.asList(Aref, T, C); - VariantContext naturalVC = new VariantContext("natural", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, allelesNaturalOrder); - VariantContext unnaturalVC = new VariantContext("unnatural", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, allelesUnnaturalOrder); + VariantContext naturalVC = snpBuilder.alleles(allelesNaturalOrder).make(); + VariantContext unnaturalVC = snpBuilder.alleles(allelesUnnaturalOrder).make(); Assert.assertEquals(new ArrayList(naturalVC.getAlleles()), allelesNaturalOrder); Assert.assertEquals(new ArrayList(unnaturalVC.getAlleles()), allelesUnnaturalOrder); } @@ -147,7 +154,7 @@ public class VariantContextUnitTest extends BaseTest { public void testCreatingSNPVariantContext() { List alleles = Arrays.asList(Aref, T); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); + VariantContext vc = snpBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getChr(), snpLoc); Assert.assertEquals(vc.getStart(), snpLocStart); @@ -174,7 +181,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingRefVariantContext() { List alleles = Arrays.asList(Aref); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc,snpLocStart, snpLocStop, alleles); + VariantContext vc = snpBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getChr(), snpLoc); Assert.assertEquals(vc.getStart(), snpLocStart); @@ -200,7 +207,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingDeletionVariantContext() { List alleles = Arrays.asList(ATCref, del); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + VariantContext vc = new VariantContextBuilder("test", delLoc, delLocStart, delLocStop, alleles).referenceBaseForIndel((byte)'A').make(); Assert.assertEquals(vc.getChr(), delLoc); Assert.assertEquals(vc.getStart(), delLocStart); @@ -227,7 +234,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingInsertionVariantContext() { List alleles = Arrays.asList(delRef, ATC); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + VariantContext vc = insBuilder.alleles(alleles).make(); Assert.assertEquals(vc.getChr(), insLoc); Assert.assertEquals(vc.getStart(), insLocStart); @@ -275,48 +282,48 @@ public class VariantContextUnitTest extends BaseTest { @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs1() { - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATCref)); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATCref)).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs2() { - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(delRef, del)); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, del)).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs3() { - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(del)); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(del)).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs4() { - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Collections.emptyList()); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Collections.emptyList()).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgsDuplicateAlleles1() { - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(Aref, T, T)); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(Aref, T, T)).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgsDuplicateAlleles2() { - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, insLoc, insLocStart, insLocStop, Arrays.asList(Aref, A)); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(Aref, A)).make(); } @Test (expectedExceptions = IllegalStateException.class) public void testBadLoc1() { List alleles = Arrays.asList(Aref, T, del); - new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, delLoc, delLocStart, delLocStop, alleles); + new VariantContextBuilder("test", delLoc, delLocStart, delLocStop, alleles).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadID1() { - new VariantContext("test", null, delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)); + new VariantContextBuilder("test", delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)).id(null).make(); } @Test (expectedExceptions = IllegalArgumentException.class) public void testBadID2() { - new VariantContext("test", "", delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)); + new VariantContextBuilder("test", delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)).id(""); } @Test @@ -550,7 +557,7 @@ public class VariantContextUnitTest extends BaseTest { @Test(dataProvider = "getAlleles") public void testMergeAlleles(GetAllelesTest cfg) { final List altAlleles = cfg.alleles.subList(1, cfg.alleles.size()); - final VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc, snpLocStart, snpLocStop, cfg.alleles, null, CommonInfo.NO_LOG10_PERROR, null, null, (byte)'A'); + final VariantContext vc = snpBuilder.alleles(cfg.alleles).make(); Assert.assertEquals(vc.getAlleles(), cfg.alleles, "VC alleles not the same as input alleles"); Assert.assertEquals(vc.getNAlleles(), cfg.alleles.size(), "VC getNAlleles not the same as input alleles size"); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index 7857c66fc..53a0c8a2a 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -99,8 +99,7 @@ public class VariantContextUtilsUnitTest extends BaseTest { private VariantContext makeVC(String source, List alleles, Collection genotypes, Set filters) { int start = 10; int stop = start; // alleles.contains(ATC) ? start + 3 : start; - return new VariantContext(source, VCFConstants.EMPTY_ID_FIELD, "1", start, stop, alleles, - GenotypesContext.copy(genotypes), 1.0, filters, null, Cref.getBases()[0]); + return new VariantContextBuilder(source, "1", start, stop, alleles).genotypes(genotypes).filters(filters).referenceBaseForIndel(Cref.getBases()[0]).make(); } // -------------------------------------------------------------------------------- diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java index 85a2532ef..6f5756bdc 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantJEXLContextUnitTest.java @@ -144,7 +144,7 @@ public class VariantJEXLContextUnitTest extends BaseTest { private JEXLMap getVarContext() { List alleles = Arrays.asList(Aref, T); - VariantContext vc = new VariantContext("test", VCFConstants.EMPTY_ID_FIELD, snpLoc.getContig(), snpLoc.getStart(), snpLoc.getStop(), alleles); + VariantContext vc = new VariantContextBuilder("test", snpLoc.getContig(), snpLoc.getStart(), snpLoc.getStop(), alleles).make(); return new JEXLMap(Arrays.asList(exp),vc); } From b7b57ef39a313ca2c743a96f92d5951b8e2b390a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 19 Nov 2011 15:57:33 -0500 Subject: [PATCH 070/113] Updating MD5 to reflect canonical ordering of calculation -- We should no longer have md5s changing because of hashmaps changing their sort order on us -- Added GenotypeLikelihoodsUnitTests -- Refactored ExactAFCaclculation to put the PL -> QUAL calculation in the GenotypeLikelihoods class to avoid the code copy. --- .../genotyper/ExactAFCalculationModel.java | 31 ++--------- .../variantcontext/GenotypeLikelihoods.java | 30 ++++++----- ...tTest.java => GenotypePriorsUnitTest.java} | 2 +- .../UnifiedGenotyperIntegrationTest.java | 51 ++++++++++--------- .../GenotypeLikelihoodsUnitTest.java | 16 +++++- 5 files changed, 63 insertions(+), 67 deletions(-) rename public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/{GenotypeLikelihoodsUnitTest.java => GenotypePriorsUnitTest.java} (98%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index e071de15b..354702dad 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -29,10 +29,7 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.PrintStream; import java.util.*; @@ -354,11 +351,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // and will add no-call genotype to GL's in a second pass ArrayList myAlleles = new ArrayList(); - double qual = Double.NEGATIVE_INFINITY; double[] likelihoods = g.getLikelihoods().getAsVector(); if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { - bestGTguess = Utils.findIndexOfMaxEntry(g.getLikelihoods().getAsVector()); + bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); } else { int newIdx = tracebackArray[k][startIdx];; @@ -366,20 +362,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { startIdx = newIdx; } - /* System.out.format("Sample: %s GL:",sample); - for (int i=0; i < likelihoods.length; i++) - System.out.format("%1.4f, ",likelihoods[i]); - */ - - for (int i=0; i < likelihoods.length; i++) { - if (i==bestGTguess) - continue; - if (likelihoods[i] >= qual) - qual = likelihoods[i]; - } - // qual contains now max(likelihoods[k]) for all k != bestGTguess - qual = likelihoods[bestGTguess] - qual; - // likelihoods are stored row-wise in lower triangular matrix. IE // for 2 alleles they have ordering AA,AB,BB // for 3 alleles they are ordered AA,AB,BB,AC,BC,CC @@ -407,16 +389,9 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { break; } - if (qual < 0) { - // QUAL can be negative if the chosen genotype is not the most likely one individually. - // In this case, we compute the actual genotype probability and QUAL is the likelihood of it not being the chosen on - double[] normalized = MathUtils.normalizeFromLog10(likelihoods); - double chosenGenotype = normalized[bestGTguess]; - qual = -1.0 * Math.log10(1.0 - chosenGenotype); - } + final double qual = GenotypeLikelihoods.getQualFromLikelihoods(bestGTguess, likelihoods); //System.out.println(myAlleles.toString()); calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); - } for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java index bbe5308a9..a5e4e5774 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -117,28 +117,34 @@ public class GenotypeLikelihoods { //Return the neg log10 Genotype Quality (GQ) for the given genotype //Returns Double.NEGATIVE_INFINITY in case of missing genotype public double getLog10GQ(Genotype.Type genotype){ - EnumMap likelihoods = getAsMap(false); + return getQualFromLikelihoods(genotype.ordinal() - 1 /* NO_CALL IS FIRST */, getAsVector()); + } + + public static double getQualFromLikelihoods(int iOfChoosenGenotype, double[] likelihoods){ if(likelihoods == null) return Double.NEGATIVE_INFINITY; double qual = Double.NEGATIVE_INFINITY; - for(Map.Entry likelihood : likelihoods.entrySet()){ - if(likelihood.getKey() == genotype) + for (int i=0; i < likelihoods.length; i++) { + if (i==iOfChoosenGenotype) continue; - if(likelihood.getValue() > qual) - qual = likelihood.getValue(); + if (likelihoods[i] >= qual) + qual = likelihoods[i]; } - //Quality of the most likely genotype = likelihood(most likely) - likelihood (2nd best) - qual = likelihoods.get(genotype) - qual; + // qual contains now max(likelihoods[k]) for all k != bestGTguess + qual = likelihoods[iOfChoosenGenotype] - qual; - //Quality of other genotypes 1-P(G) if (qual < 0) { - double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); - double chosenGenotype = normalized[genotype.ordinal()-1]; - qual = Math.log10(1.0 - chosenGenotype); + // QUAL can be negative if the chosen genotype is not the most likely one individually. + // In this case, we compute the actual genotype probability and QUAL is the likelihood of it not being the chosen one + double[] normalized = MathUtils.normalizeFromLog10(likelihoods); + double chosenGenotype = normalized[iOfChoosenGenotype]; + return Math.log10(1.0 - chosenGenotype); + } else { + // invert the size, as this is the probability of making an error + return -1 * qual; } - return -1 * qual; } private final static double[] parsePLsIntoLikelihoods(String likelihoodsAsString_PLs) { diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypePriorsUnitTest.java similarity index 98% rename from public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java rename to public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypePriorsUnitTest.java index 425b969e2..a87f121f6 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypePriorsUnitTest.java @@ -7,7 +7,7 @@ import org.testng.annotations.Test; import static java.lang.Math.log10; -public class GenotypeLikelihoodsUnitTest extends BaseTest { +public class GenotypePriorsUnitTest extends BaseTest { private final static double DELTA = 1e-8; @Test diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index 6d4a971a5..fc234ec24 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -29,7 +29,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testMultiSamplePilot1() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -o %s -L 1:10,022,000-10,025,000", 1, - Arrays.asList("c93def488de12fb3b8199e001b7a24a8")); + Arrays.asList("f5c8bd653aed02059b9f377833eae5bb")); executeTest("test MultiSample Pilot1", spec); } @@ -37,7 +37,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithAllelesPassedIn1() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("cef4bd72cbbe72f28e9c72e2818b4708")); + Arrays.asList("ea5b5dcea3a6eef7ec60070b551c994e")); executeTest("test MultiSample Pilot2 with alleles passed in", spec1); } @@ -45,7 +45,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithAllelesPassedIn2() { WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("9834f0cef1cd6ba4943a5aaee1ee8be8")); + Arrays.asList("030ce4feb4bbcf700caba82a45cc45f2")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } @@ -53,7 +53,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testSingleSamplePilot2() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,100,000", 1, - Arrays.asList("6762b72ae60155ad71738d7c76b80e4b")); + Arrays.asList("3ccce5d909f8f128e496f6841836e5f7")); executeTest("test SingleSample Pilot2", spec); } @@ -63,7 +63,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // // -------------------------------------------------------------------------------------------------------------- - private final static String COMPRESSED_OUTPUT_MD5 = "bc71dba7bbdb23e7d5cc60461fdd897b"; + private final static String COMPRESSED_OUTPUT_MD5 = "890143b366050e78d6c6ba6b2c6b6864"; @Test public void testCompressedOutput() { @@ -84,7 +84,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // Note that we need to turn off any randomization for this to work, so no downsampling and no annotations - String md5 = "b9504e446b9313559c3ed97add7e8dc1"; + String md5 = "95614280c565ad90f8c000376fef822c"; WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " -dt NONE -G none -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,075,000", 1, @@ -115,8 +115,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testCallingParameters() { HashMap e = new HashMap(); - e.put( "--min_base_quality_score 26", "bb3f294eab3e2cf52c70e63b23aac5ee" ); - e.put( "--computeSLOD", "eb34979efaadba1e34bd82bcacf5c722" ); + e.put( "--min_base_quality_score 26", "7acb1a5aee5fdadb0cc0ea07a212efc6" ); + e.put( "--computeSLOD", "e9d23a08472e4e27b4f25e844f5bad57" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -129,9 +129,9 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testOutputParameter() { HashMap e = new HashMap(); - e.put( "-sites_only", "d40114aa201aa33ff5f174f15b6b73af" ); - e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "3c681b053fd2280f3c42041d24243752" ); - e.put( "--output_mode EMIT_ALL_SITES", "eafa6d71c5ecd64dfee5d7a3f60e392e" ); + e.put( "-sites_only", "44f3b5b40e6ad44486cddfdb7e0bfcd8" ); + e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "94e53320f14c5ff29d62f68d36b46fcd" ); + e.put( "--output_mode EMIT_ALL_SITES", "73ad1cc41786b12c5f0e6f3e9ec2b728" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -145,12 +145,15 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testConfidence() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,010,000 -stand_call_conf 10 ", 1, - Arrays.asList("c71ca370947739cb7d87b59452be7a07")); + Arrays.asList("902327e8a45fe585c8dfd1a7c4fcf60f")); executeTest("test confidence 1", spec1); + } + @Test + public void testConfidence2() { WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,010,000 -stand_emit_conf 10 ", 1, - Arrays.asList("1c0a599d475cc7d5e745df6e9b6c0d29")); + Arrays.asList("2343ac8113791f4e79643b333b34afc8")); executeTest("test confidence 2", spec2); } @@ -162,8 +165,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testHeterozyosity() { HashMap e = new HashMap(); - e.put( 0.01, "f84da90c310367bd51f2ab6e346fa3d8" ); - e.put( 1.0 / 1850, "5791e7fef40d4412b6d8f84e0a809c6c" ); + e.put( 0.01, "46243ecc2b9dc716f48ea280c9bb7e72" ); + e.put( 1.0 / 1850, "6b2a59dbc76984db6d4d6d6b5ee5d62c" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -187,7 +190,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("9cc9538ac83770e12bd0830d285bfbd0")); + Arrays.asList("f0fbe472f155baf594b1eeb58166edef")); executeTest(String.format("test multiple technologies"), spec); } @@ -206,7 +209,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -L 1:10,000,000-10,100,000" + " -baq CALCULATE_AS_NECESSARY", 1, - Arrays.asList("eaf8043edb46dfbe9f97ae03baa797ed")); + Arrays.asList("8c87c749a7bb5a76ed8504d4ec254272")); executeTest(String.format("test calling with BAQ"), spec); } @@ -225,7 +228,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("eeba568272f9b42d5450da75c7cc6d2d")); + Arrays.asList("a64d2e65b5927260e4ce0d948760cc5c")); executeTest(String.format("test indel caller in SLX"), spec); } @@ -240,7 +243,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -minIndelCnt 1" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("5fe98ee853586dc9db58f0bc97daea63")); + Arrays.asList("2ad52c2e75b3ffbfd8f03237c444e8e6")); executeTest(String.format("test indel caller in SLX with low min allele count"), spec); } @@ -253,7 +256,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("19ff9bd3139480bdf79dcbf117cf2b24")); + Arrays.asList("69107157632714150fc068d412e31939")); executeTest(String.format("test indel calling, multiple technologies"), spec); } @@ -263,7 +266,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("81a1035e59cd883e413e62d34265c1a2")); + Arrays.asList("4ffda07590e06d58ed867ae326d74b2d")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -273,7 +276,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { baseCommandIndels + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("102b7d915f21dff0a9b6ea64c4c7d409")); + Arrays.asList("6e182a58472ea17c8b0eb01f80562fbd")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -283,7 +286,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2.20101123.indels.sites.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,080,000", 1, - Arrays.asList("5900344f97bbac35d147a0a7c2bf1d0c")); + Arrays.asList("f93f8a35b47bcf96594ada55e2312c73")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -292,7 +295,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec4 = new WalkerTest.WalkerTestSpec( baseCommandIndelsb37 + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2_chr20_100_110K.20101123.indels.sites.vcf -I " + validationDataLocation + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("45e7bf21cd6358921626404e7ae76c69")); + Arrays.asList("1e02f57fafaa41db71c531eb25e148e1")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java index 70a18856f..a66c78f3c 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java @@ -105,8 +105,8 @@ public class GenotypeLikelihoodsUnitTest { double[] test = MathUtils.normalizeFromLog10(gl.getAsVector()); //GQ for the other genotypes - Assert.assertEquals(gl.getLog10GQ(Genotype.Type.HOM_REF), -1 * Math.log10(1.0 - test[Genotype.Type.HOM_REF.ordinal()-1])); - Assert.assertEquals(gl.getLog10GQ(Genotype.Type.HOM_VAR), -1 * Math.log10(1.0 - test[Genotype.Type.HOM_VAR.ordinal()-1])); + Assert.assertEquals(gl.getLog10GQ(Genotype.Type.HOM_REF), Math.log10(1.0 - test[Genotype.Type.HOM_REF.ordinal()-1])); + Assert.assertEquals(gl.getLog10GQ(Genotype.Type.HOM_VAR), Math.log10(1.0 - test[Genotype.Type.HOM_VAR.ordinal()-1])); //Test missing likelihoods gl = new GenotypeLikelihoods("."); @@ -116,6 +116,18 @@ public class GenotypeLikelihoodsUnitTest { } + @Test + public void testgetQualFromLikelihoods(){ + double[] likelihoods = new double[]{-1, 0, -2}; + // qual values we expect for each possible "best" genotype + double[] expectedQuals = new double[]{-0.04100161, -1, -0.003930294}; + + for ( int i = 0; i < likelihoods.length; i++ ) { + Assert.assertEquals(GenotypeLikelihoods.getQualFromLikelihoods(i, likelihoods), expectedQuals[i], 1e-6, + "GQ value for genotype " + i + " was not calculated correctly"); + } + } + private void assertDoubleArraysAreEqual(double[] v1, double[] v2) { Assert.assertEquals(v1.length, v2.length); for ( int i = 0; i < v1.length; i++ ) { From 8f7eebbaaf5fab7f2e5b9e3798d56870f684e4f3 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 19 Nov 2011 15:58:59 -0500 Subject: [PATCH 071/113] Bugfix for pError not being checked correctly in CommonInfo -- UnitTests to ensure correct behavior -- UnitTests to ensure correct behavior for pass filters vs. failed filters vs. unfiltered --- .../utils/variantcontext/CommonInfo.java | 9 +- .../VariantContextUnitTest.java | 75 ++++++++------- .../VariantContextUtilsUnitTest.java | 92 +++++++++---------- 3 files changed, 95 insertions(+), 81 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java index 98032f94d..c0c9f36ce 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/CommonInfo.java @@ -107,9 +107,12 @@ final class CommonInfo { public double getPhredScaledQual() { return getLog10PError() * -10; } public void setLog10PError(double log10PError) { - if ( this.log10PError > 0 && this.log10PError != NO_LOG10_PERROR) throw new IllegalArgumentException("BUG: log10PError cannot be > 0 : " + this.log10PError); - if ( Double.isInfinite(this.log10PError) ) throw new IllegalArgumentException("BUG: log10PError should not be Infinity"); - if ( Double.isNaN(this.log10PError) ) throw new IllegalArgumentException("BUG: log10PError should not be NaN"); + if ( log10PError > 0 && log10PError != NO_LOG10_PERROR) + throw new IllegalArgumentException("BUG: log10PError cannot be > 0 : " + this.log10PError); + if ( Double.isInfinite(this.log10PError) ) + throw new IllegalArgumentException("BUG: log10PError should not be Infinity"); + if ( Double.isNaN(this.log10PError) ) + throw new IllegalArgumentException("BUG: log10PError should not be NaN"); this.log10PError = log10PError; } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index 5f2dacdfb..fdcf01141 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -264,7 +264,7 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testCreatingPartiallyCalledGenotype() { List alleles = Arrays.asList(Aref, C); - Genotype g = new Genotype("foo", Arrays.asList(C, Allele.NO_CALL), 10); + Genotype g = new Genotype("foo", Arrays.asList(C, Allele.NO_CALL)); VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g).make(); Assert.assertTrue(vc.isSNP()); @@ -330,13 +330,18 @@ public class VariantContextUnitTest extends BaseTest { new VariantContextBuilder("test", delLoc, delLocStart, delLocStop, Arrays.asList(Aref, T)).id("").make(); } + @Test (expectedExceptions = Throwable.class) + public void testBadPError() { + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATCref)).log10PError(0.5).make(); + } + @Test public void testAccessingSimpleSNPGenotypes() { List alleles = Arrays.asList(Aref, T); - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T)); VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles) .genotypes(g1, g2, g3).make(); @@ -371,12 +376,12 @@ public class VariantContextUnitTest extends BaseTest { public void testAccessingCompleteGenotypes() { List alleles = Arrays.asList(Aref, T, del); - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); - Genotype g4 = new Genotype("Td", Arrays.asList(T, del), 10); - Genotype g5 = new Genotype("dd", Arrays.asList(del, del), 10); - Genotype g6 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T)); + Genotype g4 = new Genotype("Td", Arrays.asList(T, del)); + Genotype g5 = new Genotype("dd", Arrays.asList(del, del)); + Genotype g6 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL)); VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles) .genotypes(g1, g2, g3, g4, g5, g6).make(); @@ -401,9 +406,9 @@ public class VariantContextUnitTest extends BaseTest { List alleles2 = Arrays.asList(Aref); List alleles3 = Arrays.asList(Aref, T, del); for ( List alleles : Arrays.asList(alleles1, alleles2, alleles3)) { - Genotype g1 = new Genotype("AA1", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AA2", Arrays.asList(Aref, Aref), 10); - Genotype g3 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); + Genotype g1 = new Genotype("AA1", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AA2", Arrays.asList(Aref, Aref)); + Genotype g3 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL)); VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles) .genotypes(g1, g2, g3).make(); @@ -422,20 +427,24 @@ public class VariantContextUnitTest extends BaseTest { @Test public void testFilters() { List alleles = Arrays.asList(Aref, T, del); - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); VariantContext vc = new VariantContextBuilder("test", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g1, g2).make(); Assert.assertTrue(vc.isNotFiltered()); Assert.assertFalse(vc.isFiltered()); Assert.assertEquals(0, vc.getFilters().size()); + Assert.assertFalse(vc.filtersWereApplied()); + Assert.assertNull(vc.getFiltersMaybeNull()); vc = new VariantContextBuilder(vc).filters("BAD_SNP_BAD!").make(); Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); Assert.assertEquals(1, vc.getFilters().size()); + Assert.assertTrue(vc.filtersWereApplied()); + Assert.assertNotNull(vc.getFiltersMaybeNull()); Set filters = new HashSet(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); vc = new VariantContextBuilder(vc).filters(filters).make(); @@ -443,16 +452,18 @@ public class VariantContextUnitTest extends BaseTest { Assert.assertFalse(vc.isNotFiltered()); Assert.assertTrue(vc.isFiltered()); Assert.assertEquals(3, vc.getFilters().size()); + Assert.assertTrue(vc.filtersWereApplied()); + Assert.assertNotNull(vc.getFiltersMaybeNull()); } @Test public void testVCFfromGenotypes() { List alleles = Arrays.asList(Aref, T, del); - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); - Genotype g4 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), 10); - Genotype g5 = new Genotype("--", Arrays.asList(del, del), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T)); + Genotype g4 = new Genotype("..", Arrays.asList(Allele.NO_CALL, Allele.NO_CALL)); + Genotype g5 = new Genotype("--", Arrays.asList(del, del)); VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g1,g2,g3,g4,g5).make(); VariantContext vc12 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g2.getSampleName()))); @@ -503,9 +514,9 @@ public class VariantContextUnitTest extends BaseTest { } public void testGetGenotypeMethods() { - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T)); GenotypesContext gc = GenotypesContext.create(g1, g2, g3); VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); @@ -608,9 +619,9 @@ public class VariantContextUnitTest extends BaseTest { @DataProvider(name = "SitesAndGenotypesVC") public Object[][] MakeSitesAndGenotypesVCs() { - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T)); VariantContext sites = new VariantContextBuilder("sites", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).make(); VariantContext genotypes = new VariantContextBuilder(sites).source("genotypes").genotypes(g1, g2, g3).make(); @@ -647,9 +658,9 @@ public class VariantContextUnitTest extends BaseTest { modified = new VariantContextBuilder(modified).attributes(null).make(); Assert.assertTrue(modified.getAttributes().isEmpty()); - Genotype g1 = new Genotype("AA2", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT2", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT2", Arrays.asList(T, T), 10); + Genotype g1 = new Genotype("AA2", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT2", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT2", Arrays.asList(T, T)); GenotypesContext gc = GenotypesContext.create(g1,g2,g3); modified = new VariantContextBuilder(cfg.vc).genotypes(gc).make(); Assert.assertEquals(modified.getGenotypes(), gc); @@ -704,9 +715,9 @@ public class VariantContextUnitTest extends BaseTest { @Test(dataProvider = "SubContextTest") public void runSubContextTest(SubContextTest cfg) { - Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref), 10); - Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T), 10); - Genotype g3 = new Genotype("TT", Arrays.asList(T, T), 10); + Genotype g1 = new Genotype("AA", Arrays.asList(Aref, Aref)); + Genotype g2 = new Genotype("AT", Arrays.asList(Aref, T)); + Genotype g3 = new Genotype("TT", Arrays.asList(T, T)); GenotypesContext gc = GenotypesContext.create(g1, g2, g3); VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java index 53a0c8a2a..ccf560f83 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -409,44 +409,44 @@ public class VariantContextUtilsUnitTest extends BaseTest { @DataProvider(name = "mergeGenotypes") public Object[][] mergeGenotypesData() { new MergeGenotypesTest("TakeGenotypeByPriority-1,2", "1,2", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1))); new MergeGenotypesTest("TakeGenotypeByPriority-1,2-nocall", "1,2", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, 1))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, -1))); new MergeGenotypesTest("TakeGenotypeByPriority-2,1", "2,1", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2))); new MergeGenotypesTest("NonOverlappingGenotypes", "1,2", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s2", Aref, T, 2)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1), makeG("s2", Aref, T, 2))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s2", Aref, T, -2)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1), makeG("s2", Aref, T, -2))); new MergeGenotypesTest("PreserveNoCall", "1,2", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s2", Aref, T, 2)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, 1), makeG("s2", Aref, T, 2))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s2", Aref, T, -2)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Allele.NO_CALL, Allele.NO_CALL, -1), makeG("s2", Aref, T, -2))); new MergeGenotypesTest("PerserveAlleles", "1,2", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)), - makeVC("2", Arrays.asList(Aref, C), makeG("s2", Aref, C, 2)), - makeVC("3", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, 1), makeG("s2", Aref, C, 2))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)), + makeVC("2", Arrays.asList(Aref, C), makeG("s2", Aref, C, -2)), + makeVC("3", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, -1), makeG("s2", Aref, C, -2))); new MergeGenotypesTest("TakeGenotypePartialOverlap-1,2", "1,2", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2), makeG("s3", Aref, T, 3)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1), makeG("s3", Aref, T, 3))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2), makeG("s3", Aref, T, -3)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1), makeG("s3", Aref, T, -3))); new MergeGenotypesTest("TakeGenotypePartialOverlap-2,1", "2,1", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2), makeG("s3", Aref, T, 3)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2), makeG("s3", Aref, T, 3))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2), makeG("s3", Aref, T, -3)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2), makeG("s3", Aref, T, -3))); // // merging genothpes with PLs @@ -454,41 +454,41 @@ public class VariantContextUtilsUnitTest extends BaseTest { // first, do no harm new MergeGenotypesTest("OrderedPLs", "1", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1, 1, 2, 3)), - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1, 1, 2, 3))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1, 1, 2, 3)), + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1, 1, 2, 3))); // first, do no harm new MergeGenotypesTest("OrderedPLs-3Alleles", "1", - makeVC("1", Arrays.asList(Aref, C, T), makeG("s1", Aref, T, 1, 1, 2, 3, 4, 5, 6)), - makeVC("1", Arrays.asList(Aref, C, T), makeG("s1", Aref, T, 1, 1, 2, 3, 4, 5, 6))); + makeVC("1", Arrays.asList(Aref, C, T), makeG("s1", Aref, T, -1, 1, 2, 3, 4, 5, 6)), + makeVC("1", Arrays.asList(Aref, C, T), makeG("s1", Aref, T, -1, 1, 2, 3, 4, 5, 6))); // first, do no harm new MergeGenotypesTest("OrderedPLs-3Alleles-2", "1", - makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, 1, 1, 2, 3, 4, 5, 6)), - makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, 1, 1, 2, 3, 4, 5, 6))); + makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, -1, 1, 2, 3, 4, 5, 6)), + makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, -1, 1, 2, 3, 4, 5, 6))); // first, do no harm new MergeGenotypesTest("OrderedPLs-3Alleles-2", "1", - makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, 1, 1, 2, 3, 4, 5, 6)), - makeVC("1", Arrays.asList(Aref, T, C), makeG("s2", Aref, C, 1, 1, 2, 3, 4, 5, 6)), - makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, 1, 1, 2, 3, 4, 5, 6), makeG("s2", Aref, C, 1, 1, 2, 3, 4, 5, 6))); + makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, -1, 1, 2, 3, 4, 5, 6)), + makeVC("1", Arrays.asList(Aref, T, C), makeG("s2", Aref, C, -1, 1, 2, 3, 4, 5, 6)), + makeVC("1", Arrays.asList(Aref, T, C), makeG("s1", Aref, T, -1, 1, 2, 3, 4, 5, 6), makeG("s2", Aref, C, -1, 1, 2, 3, 4, 5, 6))); new MergeGenotypesTest("TakeGenotypePartialOverlapWithPLs-2,1", "2,1", - makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1,5,0,3)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2,4,0,2), makeG("s3", Aref, T, 3,3,0,2)), - makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2,4,0,2), makeG("s3", Aref, T, 3,3,0,2))); + makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1,5,0,3)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2,4,0,2), makeG("s3", Aref, T, -3,3,0,2)), + makeVC("3", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2,4,0,2), makeG("s3", Aref, T, -3,3,0,2))); new MergeGenotypesTest("TakeGenotypePartialOverlapWithPLs-1,2", "1,2", - makeVC("1", Arrays.asList(Aref,ATC), makeG("s1", Aref, ATC, 1,5,0,3)), - makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2,4,0,2), makeG("s3", Aref, T, 3,3,0,2)), + makeVC("1", Arrays.asList(Aref,ATC), makeG("s1", Aref, ATC, -1,5,0,3)), + makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2,4,0,2), makeG("s3", Aref, T, -3,3,0,2)), // no likelihoods on result since type changes to mixed multiallelic - makeVC("3", Arrays.asList(Aref, ATC, T), makeG("s1", Aref, ATC, 1), makeG("s3", Aref, T, 3))); + makeVC("3", Arrays.asList(Aref, ATC, T), makeG("s1", Aref, ATC, -1), makeG("s3", Aref, T, -3))); new MergeGenotypesTest("MultipleSamplePLsDifferentOrder", "1,2", - makeVC("1", Arrays.asList(Aref, C, T), makeG("s1", Aref, C, 1, 1, 2, 3, 4, 5, 6)), - makeVC("2", Arrays.asList(Aref, T, C), makeG("s2", Aref, T, 2, 6, 5, 4, 3, 2, 1)), + makeVC("1", Arrays.asList(Aref, C, T), makeG("s1", Aref, C, -1, 1, 2, 3, 4, 5, 6)), + makeVC("2", Arrays.asList(Aref, T, C), makeG("s2", Aref, T, -2, 6, 5, 4, 3, 2, 1)), // no likelihoods on result since type changes to mixed multiallelic - makeVC("3", Arrays.asList(Aref, C, T), makeG("s1", Aref, C, 1), makeG("s2", Aref, T, 2))); + makeVC("3", Arrays.asList(Aref, C, T), makeG("s1", Aref, C, -1), makeG("s2", Aref, T, -2))); return MergeGenotypesTest.getTests(MergeGenotypesTest.class); } @@ -533,8 +533,8 @@ public class VariantContextUtilsUnitTest extends BaseTest { @Test public void testMergeGenotypesUniquify() { - final VariantContext vc1 = makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)); - final VariantContext vc2 = makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2)); + final VariantContext vc1 = makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)); + final VariantContext vc2 = makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2)); final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, Arrays.asList(vc1, vc2), null, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, @@ -546,8 +546,8 @@ public class VariantContextUtilsUnitTest extends BaseTest { @Test(expectedExceptions = UserException.class) public void testMergeGenotypesRequireUnique() { - final VariantContext vc1 = makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, 1)); - final VariantContext vc2 = makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, 2)); + final VariantContext vc1 = makeVC("1", Arrays.asList(Aref, T), makeG("s1", Aref, T, -1)); + final VariantContext vc2 = makeVC("2", Arrays.asList(Aref, T), makeG("s1", Aref, T, -2)); final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, Arrays.asList(vc1, vc2), null, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, From 707bd30b3f867f3c109a8cd33da3ace668cadb44 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 19 Nov 2011 16:10:09 -0500 Subject: [PATCH 072/113] Should have been @BeforeMethod --- .../sting/utils/variantcontext/VariantContextUnitTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index fdcf01141..a7eac7ab9 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -9,6 +9,7 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.testng.annotations.BeforeSuite; import org.testng.annotations.BeforeTest; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.Assert; @@ -58,7 +59,7 @@ public class VariantContextUnitTest extends BaseTest { ATCref = Allele.create("ATC", true); } - @BeforeTest + @BeforeMethod public void beforeTest() { basicBuilder = new VariantContextBuilder("test", snpLoc,snpLocStart, snpLocStop, Arrays.asList(Aref, T)).referenceBaseForIndel((byte)'A'); snpBuilder = new VariantContextBuilder("test", snpLoc,snpLocStart, snpLocStop, Arrays.asList(Aref, T)).referenceBaseForIndel((byte)'A'); From 7d09c0064b29ae0b502fbc4b76f505e0f5a3c4a3 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 19 Nov 2011 18:40:15 -0500 Subject: [PATCH 073/113] Bug fixes and code cleanup throughout -- chromosomeCounts now takes builder as well, cleaning up a lot of code throughout the codebase. --- .../walkers/annotator/ChromosomeCounts.java | 6 +- .../beagle/BeagleOutputToVCFWalker.java | 14 ++-- .../walkers/genotyper/UGCallVariants.java | 6 +- .../gatk/walkers/phasing/PhasingUtils.java | 10 +-- .../varianteval/util/VariantEvalUtils.java | 17 ++--- .../walkers/variantutils/CombineVariants.java | 13 ++-- .../walkers/variantutils/SelectVariants.java | 7 +- .../variantcontext/VariantContextBuilder.java | 26 ++++++- .../variantcontext/VariantContextUtils.java | 74 +++++++++++++++++-- .../VariantAnnotatorIntegrationTest.java | 10 ++- 10 files changed, 125 insertions(+), 58 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ChromosomeCounts.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ChromosomeCounts.java index 5ed2a6761..0acd3e841 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ChromosomeCounts.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ChromosomeCounts.java @@ -59,10 +59,8 @@ public class ChromosomeCounts extends InfoFieldAnnotation implements StandardAnn public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { if ( ! vc.hasGenotypes() ) return null; - - Map map = new HashMap(); - VariantContextUtils.calculateChromosomeCounts(vc, map, true); - return map; + + return VariantContextUtils.calculateChromosomeCounts(vc, new HashMap(), true); } public List getKeyNames() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 64c4948ff..8c6038e7e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -338,23 +338,21 @@ public class BeagleOutputToVCFWalker extends RodWalker { builder.alleles(new HashSet(Arrays.asList(vc_input.getReference()))).filters(removedFilters); } - HashMap attributes = new HashMap(vc_input.getAttributes()); // re-compute chromosome counts - VariantContextUtils.calculateChromosomeCounts(vc_input, attributes, false); + VariantContextUtils.calculateChromosomeCounts(builder, false); // Get Hapmap AC and AF if (vc_comp != null) { - attributes.put("ACH", alleleCountH.toString() ); - attributes.put("ANH", chrCountH.toString() ); - attributes.put("AFH", String.format("%4.2f", (double)alleleCountH/chrCountH) ); + builder.attribute("ACH", alleleCountH.toString() ); + builder.attribute("ANH", chrCountH.toString() ); + builder.attribute("AFH", String.format("%4.2f", (double)alleleCountH/chrCountH) ); } - attributes.put("NumGenotypesChanged", numGenotypesChangedByBeagle ); + builder.attribute("NumGenotypesChanged", numGenotypesChangedByBeagle ); if( !beagleR2Feature.getR2value().equals(Double.NaN) ) { - attributes.put("R2", beagleR2Feature.getR2value().toString() ); + builder.attribute("R2", beagleR2Feature.getR2value().toString() ); } - builder.attributes(attributes); vcfWriter.add(builder.make()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java index 83da319ea..97f7b21eb 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCallVariants.java @@ -106,9 +106,9 @@ public class UGCallVariants extends RodWalker { return sum; try { - Map attrs = new HashMap(value.getAttributes()); - VariantContextUtils.calculateChromosomeCounts(value, attrs, true); - writer.add(new VariantContextBuilder(value).attributes(attrs).make()); + VariantContextBuilder builder = new VariantContextBuilder(value); + VariantContextUtils.calculateChromosomeCounts(builder, true); + writer.add(builder.make()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(e.getMessage() + "; this is often caused by using the --assume_single_sample_reads argument with the wrong sample name"); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java index 9aa23fdfb..75d0773f1 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhasingUtils.java @@ -131,13 +131,9 @@ class PhasingUtils { if ( vc2.hasID() ) mergedIDs.add(vc2.getID()); String mergedID = mergedIDs.isEmpty() ? VCFConstants.EMPTY_ID_FIELD : Utils.join(VCFConstants.ID_FIELD_SEPARATOR, mergedIDs); - VariantContext mergedVc = new VariantContextBuilder(mergedName, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles()).id(mergedID).genotypes(mergedGenotypes).log10PError(mergedLog10PError).filters(mergedFilters).attributes(mergedAttribs).make(); - - mergedAttribs = new HashMap(mergedVc.getAttributes()); - VariantContextUtils.calculateChromosomeCounts(mergedVc, mergedAttribs, true); - mergedVc = new VariantContextBuilder(mergedVc).attributes(mergedAttribs).make(); - - return mergedVc; + VariantContextBuilder mergedBuilder = new VariantContextBuilder(mergedName, vc1.getChr(), vc1.getStart(), vc2.getEnd(), mergeData.getAllMergedAlleles()).id(mergedID).genotypes(mergedGenotypes).log10PError(mergedLog10PError).filters(mergedFilters).attributes(mergedAttribs); + VariantContextUtils.calculateChromosomeCounts(mergedBuilder, true); + return mergedBuilder.make(); } static String mergeVariantContextNames(String name1, String name2) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index 6da693c7a..aa246b58d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -279,22 +279,17 @@ public class VariantEvalUtils { */ public VariantContext getSubsetOfVariantContext(VariantContext vc, Set sampleNames) { VariantContext vcsub = vc.subContextFromSamples(sampleNames, vc.getAlleles()); + VariantContextBuilder builder = new VariantContextBuilder(vcsub); - HashMap newAts = new HashMap(vcsub.getAttributes()); - - int originalAlleleCount = vc.getHetCount() + 2 * vc.getHomVarCount(); - int newAlleleCount = vcsub.getHetCount() + 2 * vcsub.getHomVarCount(); + final int originalAlleleCount = vc.getHetCount() + 2 * vc.getHomVarCount(); + final int newAlleleCount = vcsub.getHetCount() + 2 * vcsub.getHomVarCount(); if (originalAlleleCount == newAlleleCount && newAlleleCount == 1) { - newAts.put("ISSINGLETON", true); + builder.attribute("ISSINGLETON", true); } - VariantContextUtils.calculateChromosomeCounts(vcsub, newAts, true); - vcsub = new VariantContextBuilder(vcsub).attributes(newAts).make(); - - //VariantEvalWalker.logger.debug(String.format("VC %s subset to %s AC%n", vc.getSource(), vc.getAttributeAsString(VCFConstants.ALLELE_COUNT_KEY))); - - return vcsub; + VariantContextUtils.calculateChromosomeCounts(builder, true); + return builder.make(); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java index d74f5a269..096085330 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariants.java @@ -222,7 +222,7 @@ public class CombineVariants extends RodWalker { for ( final VariantContext vc : vcs ) { vcfWriter.add(vc); } - + return vcs.isEmpty() ? 0 : 1; } @@ -245,18 +245,17 @@ public class CombineVariants extends RodWalker { SET_KEY, filteredAreUncalled, MERGE_INFO_WITH_MAX_AC)); } - for ( VariantContext mergedVC : mergedVCs ) { + for ( VariantContext mergedVC : mergedVCs ) { // only operate at the start of events if ( mergedVC == null ) continue; - HashMap attributes = new HashMap(mergedVC.getAttributes()); + final VariantContextBuilder builder = new VariantContextBuilder(mergedVC); // re-compute chromosome counts - VariantContextUtils.calculateChromosomeCounts(mergedVC, attributes, false); - VariantContext annotatedMergedVC = new VariantContextBuilder(mergedVC).attributes(attributes).make(); + VariantContextUtils.calculateChromosomeCounts(builder, false); if ( minimalVCF ) - annotatedMergedVC = VariantContextUtils.pruneVariantContext(annotatedMergedVC, Arrays.asList(SET_KEY)); - vcfWriter.add(annotatedMergedVC); + VariantContextUtils.pruneVariantContext(builder, Arrays.asList(SET_KEY)); + vcfWriter.add(builder.make()); } return vcs.isEmpty() ? 0 : 1; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index babc88966..b0016ff4b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -684,11 +684,10 @@ public class SelectVariants extends RodWalker { builder.attribute("AN_Orig", sub.getAttribute(VCFConstants.ALLELE_NUMBER_KEY)); } - Map attributes = new HashMap(builder.make().getAttributes()); - VariantContextUtils.calculateChromosomeCounts(sub, attributes, false); - attributes.put("DP", depth); + VariantContextUtils.calculateChromosomeCounts(builder, false); + builder.attribute("DP", depth); - return new VariantContextBuilder(builder.make()).attributes(attributes).make(); + return builder.make(); } private void randomlyAddVariant(int rank, VariantContext vc) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java index cb2ef4678..379a01bb4 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java @@ -158,12 +158,34 @@ public class VariantContextBuilder { @Requires({"key != null"}) @Ensures({"this.attributes.size() == old(this.attributes.size()) || this.attributes.size() == old(this.attributes.size()+1)"}) public VariantContextBuilder attribute(final String key, final Object value) { + makeAttributesModifiable(); + attributes.put(key, value); + return this; + } + + /** + * Removes key if present in the attributes + * + * @param key + * @return + */ + @Requires({"key != null"}) + @Ensures({"this.attributes.size() == old(this.attributes.size()) || this.attributes.size() == old(this.attributes.size()-1)"}) + public VariantContextBuilder rmAttribute(final String key) { + makeAttributesModifiable(); + attributes.remove(key); + return this; + } + + /** + * Makes the attributes field modifiable. In many cases attributes is just a pointer to an immutable + * collection, so methods that want to add / remove records require the attributes to be copied first + */ + private void makeAttributesModifiable() { if ( ! attributesCanBeModified ) { this.attributesCanBeModified = true; this.attributes = new HashMap(attributes); } - attributes.put(key, value); - return this; } /** diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index b6e0d2e4d..07c0f7c32 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -55,13 +55,15 @@ public class VariantContextUtils { } /** - * Update the attributes of the attributes map given the VariantContext to reflect the proper chromosome-based VCF tags + * Update the attributes of the attributes map given the VariantContext to reflect the + * proper chromosome-based VCF tags * * @param vc the VariantContext * @param attributes the attributes map to populate; must not be null; may contain old values * @param removeStaleValues should we remove stale values from the mapping? + * @return the attributes map provided as input, returned for programming convenience */ - public static void calculateChromosomeCounts(VariantContext vc, Map attributes, boolean removeStaleValues) { + public static Map calculateChromosomeCounts(VariantContext vc, Map attributes, boolean removeStaleValues) { // if everyone is a no-call, remove the old attributes if requested if ( vc.getCalledChrCount() == 0 && removeStaleValues ) { if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) @@ -70,7 +72,7 @@ public class VariantContextUtils { attributes.remove(VCFConstants.ALLELE_FREQUENCY_KEY); if ( attributes.containsKey(VCFConstants.ALLELE_NUMBER_KEY) ) attributes.remove(VCFConstants.ALLELE_NUMBER_KEY); - return; + return attributes; } if ( vc.hasGenotypes() ) { @@ -96,6 +98,54 @@ public class VariantContextUtils { attributes.put(VCFConstants.ALLELE_FREQUENCY_KEY, 0.0); } } + + return attributes; + } + + /** + * Update the attributes of the attributes map in the VariantContextBuilder to reflect the proper + * chromosome-based VCF tags based on the current VC produced by builder.make() + * + * @param builder the VariantContextBuilder we are updating + * @param removeStaleValues should we remove stale values from the mapping? + */ + public static void calculateChromosomeCounts(VariantContextBuilder builder, boolean removeStaleValues) { + final VariantContext vc = builder.make(); + + // if everyone is a no-call, remove the old attributes if requested + if ( vc.getCalledChrCount() == 0 && removeStaleValues ) { + if ( vc.hasAttribute(VCFConstants.ALLELE_COUNT_KEY) ) + builder.rmAttribute(VCFConstants.ALLELE_COUNT_KEY); + if ( vc.hasAttribute(VCFConstants.ALLELE_FREQUENCY_KEY) ) + builder.rmAttribute(VCFConstants.ALLELE_FREQUENCY_KEY); + if ( vc.hasAttribute(VCFConstants.ALLELE_NUMBER_KEY) ) + builder.rmAttribute(VCFConstants.ALLELE_NUMBER_KEY); + return; + } + + if ( vc.hasGenotypes() ) { + builder.attribute(VCFConstants.ALLELE_NUMBER_KEY, vc.getCalledChrCount()); + + // if there are alternate alleles, record the relevant tags + if ( vc.getAlternateAlleles().size() > 0 ) { + ArrayList alleleFreqs = new ArrayList(); + ArrayList alleleCounts = new ArrayList(); + double totalChromosomes = (double)vc.getCalledChrCount(); + for ( Allele allele : vc.getAlternateAlleles() ) { + int altChromosomes = vc.getCalledChrCount(allele); + alleleCounts.add(altChromosomes); + String freq = String.format(makePrecisionFormatStringFromDenominatorValue(totalChromosomes), ((double)altChromosomes / totalChromosomes)); + alleleFreqs.add(freq); + } + + builder.attribute(VCFConstants.ALLELE_COUNT_KEY, alleleCounts.size() == 1 ? alleleCounts.get(0) : alleleCounts); + builder.attribute(VCFConstants.ALLELE_FREQUENCY_KEY, alleleFreqs.size() == 1 ? alleleFreqs.get(0) : alleleFreqs); + } + else { + builder.attribute(VCFConstants.ALLELE_COUNT_KEY, 0); + builder.attribute(VCFConstants.ALLELE_FREQUENCY_KEY, 0.0); + } + } } private static String makePrecisionFormatStringFromDenominatorValue(double maxValue) { @@ -348,10 +398,6 @@ public class VariantContextUtils { return r; } - public static VariantContext pruneVariantContext(VariantContext vc) { - return pruneVariantContext(vc, null); - } - private final static Map subsetAttributes(final CommonInfo igc, final Collection keysToPreserve) { Map attributes = new HashMap(keysToPreserve.size()); for ( final String key : keysToPreserve ) { @@ -361,7 +407,19 @@ public class VariantContextUtils { return attributes; } + /** + * @deprecated use variant context builder version instead + * @param vc + * @param keysToPreserve + * @return + */ + @Deprecated public static VariantContext pruneVariantContext(final VariantContext vc, Collection keysToPreserve ) { + return pruneVariantContext(new VariantContextBuilder(vc), keysToPreserve).make(); + } + + public static VariantContextBuilder pruneVariantContext(final VariantContextBuilder builder, Collection keysToPreserve ) { + final VariantContext vc = builder.make(); if ( keysToPreserve == null ) keysToPreserve = Collections.emptyList(); // VC info @@ -375,7 +433,7 @@ public class VariantContextUtils { genotypeAttributes, g.isPhased())); } - return new VariantContextBuilder(vc).genotypes(genotypes).attributes(attributes).make(); + return builder.genotypes(genotypes).attributes(attributes); } public enum GenotypeMergeType { diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index 3bfb81dd0..75c27e429 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -32,7 +32,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testHasAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("9beb795536e95954f810835c6058f2ad")); + Arrays.asList("fbb656369eaa48153d127bd12db59d8f")); executeTest("test file has annotations, asking for annotations, #1", spec); } @@ -54,9 +54,11 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { @Test public void testNoAnnotsNotAsking2() { + // this genotype annotations in this file are actually out of order. If you don't parse the genotypes + // they don't get reordered. It's a good test of the genotype ordering system. WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000", 1, - Arrays.asList("f2ddfa8105c290b1f34b7a261a02a1ac")); + Arrays.asList("0cc0ec59f0328792e6413b6ff3f71780")); executeTest("test file doesn't have annotations, not asking for annotations, #2", spec); } @@ -64,7 +66,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("49d989f467b8d6d8f98f7c1b67cd4a05")); + Arrays.asList("42dd979a0a931c18dc9be40308bac321")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -80,7 +82,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testExcludeAnnotations() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard -XA FisherStrand -XA ReadPosRankSumTest --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("33062eccd6eb73bc49440365430454c4")); + Arrays.asList("477eac07989593b58bb361f3429c085a")); executeTest("test exclude annotations", spec); } From f392d330c3ba13a0ed8bb83f7b99f4d2a2cde522 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 19 Nov 2011 22:09:56 -0500 Subject: [PATCH 074/113] Proper use of builder. Previous conversion attempt was flawed --- .../walkers/phasing/ReadBackedPhasingWalker.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java index dc0acfb6a..9470ce2f4 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/ReadBackedPhasingWalker.java @@ -1122,7 +1122,7 @@ public class ReadBackedPhasingWalker extends RodWalker alleles; - private GenotypesContext genotypes; + private Map genotypes; private double log10PError; private Set filters; private Map attributes; @@ -1135,15 +1135,21 @@ public class ReadBackedPhasingWalker extends RodWalker(); + for ( final Genotype g : vc.getGenotypes() ) { + this.genotypes.put(g.getSampleName(), g); + } + this.log10PError = vc.getLog10PError(); this.filters = vc.filtersWereApplied() ? vc.getFilters() : null; this.attributes = new HashMap(vc.getAttributes()); } public VariantContext toVariantContext() { + GenotypesContext gc = GenotypesContext.copy(this.genotypes.values()); return new VariantContextBuilder(name, contig, start, stop, alleles).id(id) - .genotypes(genotypes).log10PError(log10PError).filters(filters).attributes(attributes).make(); + .genotypes(gc).log10PError(log10PError).filters(filters).attributes(attributes).make(); } public GenomeLoc getLocation() { @@ -1155,7 +1161,7 @@ public class ReadBackedPhasingWalker extends RodWalker Date: Sun, 20 Nov 2011 08:23:09 -0500 Subject: [PATCH 075/113] Vastly better way of doing on-demand genotyping loading -- With our GenotypesContext class we can naturally create a LazyGenotypesContext subclass that does the on-demand loading. -- This new class was replaced all of the old, complex functionality -- Better still, there were many cases were the genotypes were being loaded unnecessarily, resulting in efficiency. This was detected because some of the integration tests changed as the genotypes were no longer being parsing unnecessarily -- Misc. bug fixes throughout the system -- Bug fixes for PhaseByTransmission with new GenotypesContext --- .../walkers/phasing/PhaseByTransmission.java | 20 ++- .../utils/codecs/vcf/AbstractVCFCodec.java | 15 +- .../utils/codecs/vcf/StandardVCFWriter.java | 8 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 4 +- .../broadinstitute/sting/utils/gcf/GCF.java | 2 - .../variantcontext/GenotypesContext.java | 113 ++++++++++------ .../variantcontext/LazyGenotypesContext.java | 128 ++++++++++++++++++ .../utils/variantcontext/VariantContext.java | 54 +------- .../variantcontext/VariantContextBuilder.java | 20 +-- .../VariantFiltrationIntegrationTest.java | 50 +++++-- 10 files changed, 274 insertions(+), 140 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 8585104d5..fee87b21f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -746,11 +746,9 @@ public class PhaseByTransmission extends RodWalker, HashMa if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); + VariantContextBuilder builder = new VariantContextBuilder(vc); - GenotypesContext genotypeMap = vc.getGenotypes(); - - int mvCount; - + GenotypesContext genotypesContext = GenotypesContext.copy(vc.getGenotypes()); for (Sample sample : trios) { Genotype mother = vc.getGenotype(sample.getMaternalID()); Genotype father = vc.getGenotype(sample.getPaternalID()); @@ -761,18 +759,18 @@ public class PhaseByTransmission extends RodWalker, HashMa continue; ArrayList trioGenotypes = new ArrayList(3); - mvCount = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); + final int mvCount = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); Genotype phasedMother = trioGenotypes.get(0); Genotype phasedFather = trioGenotypes.get(1); Genotype phasedChild = trioGenotypes.get(2); //Fill the genotype map with the new genotypes and increment metrics counters - genotypeMap.add(phasedChild); + genotypesContext.replace(phasedChild); if(mother != null){ - genotypeMap.add(phasedMother); + genotypesContext.replace(phasedMother); if(father != null){ - genotypeMap.add(phasedFather); + genotypesContext.replace(phasedFather); updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); if(!(phasedMother.getType()==mother.getType() && phasedFather.getType()==father.getType() && phasedChild.getType()==child.getType())) @@ -786,7 +784,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } } else{ - genotypeMap.add(phasedFather); + genotypesContext.replace(phasedFather); updatePairMetricsCounters(phasedFather,phasedChild,mvCount,metricsCounters); if(!(phasedFather.getType()==father.getType() && phasedChild.getType()==child.getType())) metricsCounters.put(NUM_GENOTYPES_MODIFIED,metricsCounters.get(NUM_GENOTYPES_MODIFIED)+1); @@ -797,10 +795,10 @@ public class PhaseByTransmission extends RodWalker, HashMa //TODO: ADAPT FOR PAIRS TOO!! if(mvCount>0 && mvFile != null) mvFile.println(mvfLine); - } - vcfWriter.add(new VariantContextBuilder(vc).genotypes(genotypeMap).make()); + builder.genotypes(genotypesContext); + vcfWriter.add(builder.make()); } return metricsCounters; } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index abd81fe61..ee3184dc2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -10,10 +10,7 @@ import org.broad.tribble.util.BlockCompressedInputStream; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.*; import java.util.*; @@ -255,11 +252,14 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, */ private VariantContext parseVCFLine(String[] parts) { VariantContextBuilder builder = new VariantContextBuilder(); + builder.source(getName()); + // increment the line count lineNo++; // parse out the required fields - builder.chr(getCachedString(parts[0])); + final String chr = getCachedString(parts[0]); + builder.chr(chr); int pos = Integer.valueOf(parts[1]); builder.start(pos); @@ -294,9 +294,8 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, // do we have genotyping data if (parts.length > NUM_STANDARD_FIELDS) { - builder.attribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, new String(parts[8])); - builder.attribute(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY, this); - builder.genotypesAreUnparsed(); + LazyGenotypesContext lazy = new LazyGenotypesContext(this, parts[8], chr, pos, alleles, header.getGenotypeSamples().size()); + builder.genotypesNoValidation(lazy); } VariantContext vc = null; diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java index 92c8840fb..7a496cb7c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java @@ -219,9 +219,6 @@ public class StandardVCFWriter extends IndexingVCFWriter { Map infoFields = new TreeMap(); for ( Map.Entry field : vc.getAttributes().entrySet() ) { String key = field.getKey(); - if ( key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) - continue; - String outputValue = formatVCFField(field.getValue()); if ( outputValue != null ) infoFields.put(key, outputValue); @@ -229,9 +226,10 @@ public class StandardVCFWriter extends IndexingVCFWriter { writeInfoString(infoFields); // FORMAT - if ( vc.hasAttribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) ) { + final GenotypesContext gc = vc.getGenotypes(); + if ( gc instanceof LazyGenotypesContext && ((LazyGenotypesContext)gc).getUnparsedGenotypeData() != null) { mWriter.write(VCFConstants.FIELD_SEPARATOR); - mWriter.write(vc.getAttributeAsString(VariantContext.UNPARSED_GENOTYPE_MAP_KEY, "")); + mWriter.write(((LazyGenotypesContext)gc).getUnparsedGenotypeData()); } else { List genotypeAttributeKeys = new ArrayList(); if ( vc.hasGenotypes() ) { diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index 7d71a9c5a..5e6e2e94a 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -124,7 +124,7 @@ public class VCF3Codec extends AbstractVCFCodec { int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); - GenotypesContext genotypes = GenotypesContext.create(nParts); + ArrayList genotypes = new ArrayList(nParts); // get the format keys int nGTKeys = ParsingUtils.split(genotypeParts[0], genotypeKeyArray, VCFConstants.GENOTYPE_FIELD_SEPARATOR_CHAR); @@ -191,7 +191,7 @@ public class VCF3Codec extends AbstractVCFCodec { } } - return genotypes; + return GenotypesContext.create(genotypes, header.sampleNameToOffset, header.sampleNamesInOrder); } @Override diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index e754c215d..b4ad81c02 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -191,8 +191,6 @@ public class GCF { boolean first = true; for ( Map.Entry field : vc.getAttributes().entrySet() ) { String key = field.getKey(); - if ( key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) - continue; int stringIndex = GCFHeaderBuilder.encodeString(key); String outputValue = StandardVCFWriter.formatVCFField(field.getValue()); if ( outputValue != null ) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 47e6b2fbe..846e6c89c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -55,8 +55,14 @@ public class GenotypesContext implements List { /** if true, then we need to reinitialize sampleNamesInOrder and sampleNameToOffset before we use them /*/ boolean cacheIsInvalid = true; - /** An ArrayList of genotypes contained in this context */ - List genotypes; + /** + * An ArrayList of genotypes contained in this context + * + * WARNING: TO ENABLE THE LAZY VERSION OF THIS CLASS, NO METHODS SHOULD DIRECTLY + * ACCESS THIS VARIABLE. USE getGenotypes() INSTEAD. + * + */ + ArrayList notToBeDirectlyAccessedGenotypes; /** Are we allowing users to modify the list? */ boolean immutable = false; @@ -70,7 +76,7 @@ public class GenotypesContext implements List { /** * Create an empty GenotypeContext */ - private GenotypesContext() { + protected GenotypesContext() { this(10, false); } @@ -78,7 +84,7 @@ public class GenotypesContext implements List { * Create an empty GenotypeContext, with initial capacity for n elements */ @Requires("n >= 0") - private GenotypesContext(final int n, final boolean immutable) { + protected GenotypesContext(final int n, final boolean immutable) { this(new ArrayList(n), immutable); } @@ -86,8 +92,8 @@ public class GenotypesContext implements List { * Create an GenotypeContext containing genotypes */ @Requires("genotypes != null") - private GenotypesContext(final ArrayList genotypes, final boolean immutable) { - this.genotypes = genotypes; + protected GenotypesContext(final ArrayList genotypes, final boolean immutable) { + this.notToBeDirectlyAccessedGenotypes = genotypes; this.immutable = immutable; this.sampleNameToOffset = null; this.cacheIsInvalid = true; @@ -110,11 +116,11 @@ public class GenotypesContext implements List { "sampleNamesInOrder != null", "genotypes.size() == sampleNameToOffset.size()", "genotypes.size() == sampleNamesInOrder.size()"}) - private GenotypesContext(final ArrayList genotypes, + protected GenotypesContext(final ArrayList genotypes, final Map sampleNameToOffset, final List sampleNamesInOrder, final boolean immutable) { - this.genotypes = genotypes; + this.notToBeDirectlyAccessedGenotypes = genotypes; this.immutable = immutable; this.sampleNameToOffset = sampleNameToOffset; this.sampleNamesInOrder = sampleNamesInOrder; @@ -203,7 +209,7 @@ public class GenotypesContext implements List { @Requires({"toCopy != null"}) @Ensures({"result != null"}) public static final GenotypesContext copy(final GenotypesContext toCopy) { - return create(new ArrayList(toCopy.genotypes)); + return create(new ArrayList(toCopy.getGenotypes())); } /** @@ -225,7 +231,6 @@ public class GenotypesContext implements List { // --------------------------------------------------------------------------- public final GenotypesContext immutable() { - this.genotypes = Collections.unmodifiableList(genotypes); immutable = true; return this; } @@ -255,16 +260,16 @@ public class GenotypesContext implements List { @Ensures({"cacheIsInvalid == false", "sampleNamesInOrder != null", "sampleNameToOffset != null", - "sameSamples(genotypes, sampleNamesInOrder)", - "sameSamples(genotypes, sampleNameToOffset.keySet())"}) - private synchronized void buildCache() { + "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNamesInOrder)", + "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNameToOffset.keySet())"}) + protected synchronized void buildCache() { if ( cacheIsInvalid ) { cacheIsInvalid = false; - sampleNamesInOrder = new ArrayList(genotypes.size()); - sampleNameToOffset = new HashMap(genotypes.size()); + sampleNamesInOrder = new ArrayList(size()); + sampleNameToOffset = new HashMap(size()); - for ( int i = 0; i < genotypes.size(); i++ ) { - final Genotype g = genotypes.get(i); + for ( int i = 0; i < size(); i++ ) { + final Genotype g = getGenotypes().get(i); sampleNamesInOrder.add(g.getSampleName()); sampleNameToOffset.put(g.getSampleName(), i); } @@ -279,20 +284,24 @@ public class GenotypesContext implements List { // // --------------------------------------------------------------------------- + protected ArrayList getGenotypes() { + return notToBeDirectlyAccessedGenotypes; + } + @Override public void clear() { checkImmutability(); - genotypes.clear(); + getGenotypes().clear(); } @Override public int size() { - return genotypes.size(); + return getGenotypes().size(); } @Override public boolean isEmpty() { - return genotypes.isEmpty(); + return getGenotypes().isEmpty(); } @Override @@ -300,14 +309,14 @@ public class GenotypesContext implements List { public boolean add(final Genotype genotype) { checkImmutability(); invalidateCaches(); - return genotypes.add(genotype); + return getGenotypes().add(genotype); } @Requires("genotype != null") public boolean add(final Genotype ... genotype) { checkImmutability(); invalidateCaches(); - return genotypes.addAll(Arrays.asList(genotype)); + return getGenotypes().addAll(Arrays.asList(genotype)); } @Override @@ -319,7 +328,7 @@ public class GenotypesContext implements List { public boolean addAll(final Collection genotypes) { checkImmutability(); invalidateCaches(); - return this.genotypes.addAll(genotypes); + return getGenotypes().addAll(genotypes); } @Override @@ -329,38 +338,43 @@ public class GenotypesContext implements List { @Override public boolean contains(final Object o) { - return this.genotypes.contains(o); + return getGenotypes().contains(o); } @Override public boolean containsAll(final Collection objects) { - return this.genotypes.containsAll(objects); + return getGenotypes().containsAll(objects); } @Override public Genotype get(final int i) { - return genotypes.get(i); + return getGenotypes().get(i); } public Genotype get(final String sampleName) { buildCache(); - Integer offset = sampleNameToOffset.get(sampleName); - return offset == null ? null : genotypes.get(offset); + Integer offset = getSampleI(sampleName); + return offset == null ? null : getGenotypes().get(offset); + } + + private Integer getSampleI(final String sampleName) { + buildCache(); + return sampleNameToOffset.get(sampleName); } @Override public int indexOf(final Object o) { - return genotypes.indexOf(o); + return getGenotypes().indexOf(o); } @Override public Iterator iterator() { - return genotypes.iterator(); + return getGenotypes().iterator(); } @Override public int lastIndexOf(final Object o) { - return genotypes.lastIndexOf(o); + return getGenotypes().lastIndexOf(o); } @Override @@ -381,50 +395,67 @@ public class GenotypesContext implements List { public Genotype remove(final int i) { checkImmutability(); invalidateCaches(); - return genotypes.remove(i); + return getGenotypes().remove(i); } @Override public boolean remove(final Object o) { checkImmutability(); invalidateCaches(); - return genotypes.remove(o); + return getGenotypes().remove(o); } @Override public boolean removeAll(final Collection objects) { checkImmutability(); invalidateCaches(); - return genotypes.removeAll(objects); + return getGenotypes().removeAll(objects); } @Override public boolean retainAll(final Collection objects) { checkImmutability(); invalidateCaches(); - return genotypes.retainAll(objects); + return getGenotypes().retainAll(objects); } @Override public Genotype set(final int i, final Genotype genotype) { checkImmutability(); invalidateCaches(); - return genotypes.set(i, genotype); + return getGenotypes().set(i, genotype); + } + + /** + * Replaces the genotype in this context -- note for efficiency + * reasons we do not add the genotype if it's not present. The + * return value will be null indicating this happened. + * @param genotype a non null genotype to bind in this context + * @return null if genotype was not added, otherwise returns the previous genotype + */ + @Requires("genotype != null") + public Genotype replace(final Genotype genotype) { + checkImmutability(); + Integer offset = getSampleI(genotype.getSampleName()); + if ( offset == null ) + return null; + else + return getGenotypes().set(offset, genotype); } @Override public List subList(final int i, final int i1) { - return genotypes.subList(i, i1); + return getGenotypes().subList(i, i1); } @Override public Object[] toArray() { - return genotypes.toArray(); + return getGenotypes().toArray(); } @Override public T[] toArray(final T[] ts) { - return genotypes.toArray(ts); + return getGenotypes().toArray(ts); } /** @@ -528,13 +559,13 @@ public class GenotypesContext implements List { @Requires("samples != null") @Ensures("result != null") public GenotypesContext subsetToSamples( final Set samples ) { - if ( samples.size() == genotypes.size() ) + if ( samples.size() == size() ) return this; else if ( samples.isEmpty() ) return NO_GENOTYPES; else { GenotypesContext subset = create(samples.size()); - for ( final Genotype g : genotypes ) { + for ( final Genotype g : getGenotypes() ) { if ( samples.contains(g.getSampleName()) ) { subset.add(g); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java new file mode 100644 index 000000000..ca2d7a812 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.utils.variantcontext; + +import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * [Short one sentence description of this walker] + *

+ *

+ * [Functionality of this walker] + *

+ *

+ *

Input

+ *

+ * [Input description] + *

+ *

+ *

Output

+ *

+ * [Output description] + *

+ *

+ *

Examples

+ *
+ *    java
+ *      -jar GenomeAnalysisTK.jar
+ *      -T $WalkerName
+ *  
+ * + * @author Your Name + * @since Date created + */ +public class LazyGenotypesContext extends GenotypesContext { + final VCFParser parser; + String unparsedGenotypeData; + final List alleles; + final String contig; + final int start; + final int nUnparsedGenotypes; + + boolean loaded = false; + + private final static ArrayList EMPTY = new ArrayList(0); + + public LazyGenotypesContext(final VCFParser parser, final String unparsedGenotypeData, + final String contig, final int start, final List alleles, + int nUnparsedGenotypes ) { + super(EMPTY, false); + this.unparsedGenotypeData = unparsedGenotypeData; + this.start = start; + this.parser = parser; + this.contig = contig; + this.alleles = alleles; + this.nUnparsedGenotypes = nUnparsedGenotypes; + } + + @Override + protected ArrayList getGenotypes() { + if ( ! loaded ) { + //System.out.printf("Loading genotypes... %s:%d%n", contig, start); + GenotypesContext subcontext = parser.createGenotypeMap(unparsedGenotypeData, alleles, contig, start); + notToBeDirectlyAccessedGenotypes = subcontext.notToBeDirectlyAccessedGenotypes; + sampleNamesInOrder = subcontext.sampleNamesInOrder; + sampleNameToOffset = subcontext.sampleNameToOffset; + cacheIsInvalid = false; + loaded = true; + unparsedGenotypeData = null; + + // warning -- this path allows us to create a VariantContext that doesn't run validateGenotypes() + // That said, it's not such an important routine -- it's just checking that the genotypes + // are well formed w.r.t. the alleles list, but this will be enforced within the VCFCodec + } + + return notToBeDirectlyAccessedGenotypes; + } + + protected synchronized void buildCache() { + if ( cacheIsInvalid ) { + getGenotypes(); // will load up all of the necessary data + } + } + + @Override + public boolean isEmpty() { + // optimization -- we know the number of samples in the unparsed data, so use it here to + // avoid parsing just to know if the genotypes context is empty + return loaded ? super.isEmpty() : nUnparsedGenotypes == 0; + } + + @Override + public int size() { + // optimization -- we know the number of samples in the unparsed data, so use it here to + // avoid parsing just to know the size of the context + return loaded ? super.size() : nUnparsedGenotypes; + } + + public String getUnparsedGenotypeData() { + return unparsedGenotypeData; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 34131b9c4..8d74f5220 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -165,8 +165,6 @@ import java.util.*; public class VariantContext implements Feature { // to enable tribble intergration protected CommonInfo commonInfo = null; public final static double NO_LOG10_PERROR = CommonInfo.NO_LOG10_PERROR; - public final static String UNPARSED_GENOTYPE_MAP_KEY = "_UNPARSED_GENOTYPE_MAP_"; - public final static String UNPARSED_GENOTYPE_PARSER_KEY = "_UNPARSED_GENOTYPE_PARSER_"; @Deprecated // ID is no longer stored in the attributes map private final static String ID_KEY = "ID"; @@ -231,7 +229,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param other the VariantContext to copy */ protected VariantContext(VariantContext other) { - this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd() , other.getAlleles(), other.getGenotypes(), other.getLog10PError(), other.filtersWereApplied() ? other.getFilters() : null, other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, false, NO_VALIDATION); + this(other.getSource(), other.getID(), other.getChr(), other.getStart(), other.getEnd(), + other.getAlleles(), other.getGenotypes(), other.getLog10PError(), + other.getFiltersMaybeNull(), + other.getAttributes(), other.REFERENCE_BASE_FOR_INDEL, + NO_VALIDATION); } /** @@ -247,14 +249,13 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @param filters filters: use null for unfiltered and empty set for passes filters * @param attributes attributes * @param referenceBaseForIndel padded reference base - * @param genotypesAreUnparsed true if the genotypes have not yet been parsed * @param validationToPerform set of validation steps to take */ protected VariantContext(String source, String ID, String contig, long start, long stop, Collection alleles, GenotypesContext genotypes, double log10PError, Set filters, Map attributes, - Byte referenceBaseForIndel, boolean genotypesAreUnparsed, + Byte referenceBaseForIndel, EnumSet validationToPerform ) { if ( contig == null ) { throw new IllegalArgumentException("Contig cannot be null"); } this.contig = contig; @@ -265,17 +266,6 @@ public class VariantContext implements Feature { // to enable tribble intergrati if ( ID == null || ID.equals("") ) throw new IllegalArgumentException("ID field cannot be the null or the empty string"); this.ID = ID.equals(VCFConstants.EMPTY_ID_FIELD) ? VCFConstants.EMPTY_ID_FIELD : ID; - if ( !genotypesAreUnparsed && attributes != null ) { - if ( attributes.containsKey(UNPARSED_GENOTYPE_MAP_KEY) ) { - attributes = new HashMap(attributes); - attributes.remove(UNPARSED_GENOTYPE_MAP_KEY); - } - if ( attributes.containsKey(UNPARSED_GENOTYPE_PARSER_KEY) ) { - attributes = new HashMap(attributes); - attributes.remove(UNPARSED_GENOTYPE_PARSER_KEY); - } - } - this.commonInfo = new CommonInfo(source, log10PError, filters, attributes); REFERENCE_BASE_FOR_INDEL = referenceBaseForIndel; @@ -316,13 +306,11 @@ public class VariantContext implements Feature { // to enable tribble intergrati // --------------------------------------------------------------------------------------------------------- public VariantContext subContextFromSamples(Set sampleNames, Collection alleles) { - loadGenotypes(); VariantContextBuilder builder = new VariantContextBuilder(this); return builder.genotypes(genotypes.subsetToSamples(sampleNames)).alleles(alleles).make(); } public VariantContext subContextFromSamples(Set sampleNames) { - loadGenotypes(); VariantContextBuilder builder = new VariantContextBuilder(this); GenotypesContext newGenotypes = genotypes.subsetToSamples(sampleNames); return builder.genotypes(newGenotypes).alleles(allelesOfGenotypes(newGenotypes)).make(); @@ -698,35 +686,10 @@ public class VariantContext implements Feature { // to enable tribble intergrati // // --------------------------------------------------------------------------------------------------------- - private void loadGenotypes() { - if ( !hasAttribute(UNPARSED_GENOTYPE_MAP_KEY) ) { - if ( genotypes == null ) - genotypes = NO_GENOTYPES; - return; - } - - Object parserObj = getAttribute(UNPARSED_GENOTYPE_PARSER_KEY); - if ( parserObj == null || !(parserObj instanceof VCFParser) ) - throw new IllegalStateException("There is no VCF parser stored to unparse the genotype data"); - VCFParser parser = (VCFParser)parserObj; - - Object mapObj = getAttribute(UNPARSED_GENOTYPE_MAP_KEY); - if ( mapObj == null ) - throw new IllegalStateException("There is no mapping string stored to unparse the genotype data"); - - genotypes = parser.createGenotypeMap(mapObj.toString(), new ArrayList(alleles), getChr(), getStart()); - - commonInfo.removeAttribute(UNPARSED_GENOTYPE_MAP_KEY); - commonInfo.removeAttribute(UNPARSED_GENOTYPE_PARSER_KEY); - - validateGenotypes(); - } - /** * @return the number of samples in the context */ public int getNSamples() { - loadGenotypes(); return genotypes.size(); } @@ -734,12 +697,10 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return true if the context has associated genotypes */ public boolean hasGenotypes() { - loadGenotypes(); return ! genotypes.isEmpty(); } public boolean hasGenotypes(Collection sampleNames) { - loadGenotypes(); return genotypes.containsSamples(sampleNames); } @@ -747,17 +708,14 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @return set of all Genotypes associated with this context */ public GenotypesContext getGenotypes() { - loadGenotypes(); return genotypes; } public Iterable getGenotypesOrderedByName() { - loadGenotypes(); return genotypes.iterateInSampleNameOrder(); } public Iterable getGenotypesOrderedBy(Iterable sampleOrdering) { - loadGenotypes(); return genotypes.iterateInSampleNameOrder(sampleOrdering); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java index 379a01bb4..b79584df8 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextBuilder.java @@ -72,7 +72,6 @@ public class VariantContextBuilder { private Map attributes = null; private boolean attributesCanBeModified = false; private Byte referenceBaseForIndel = null; - private boolean genotypesAreUnparsed = false; /** enum of what must be validated */ final private EnumSet toValidate = EnumSet.noneOf(VariantContext.Validation.class); @@ -112,7 +111,6 @@ public class VariantContextBuilder { this.contig = parent.contig; this.filters = parent.getFiltersMaybeNull(); this.genotypes = parent.genotypes; - this.genotypesAreUnparsed = parent.hasAttribute(VariantContext.UNPARSED_GENOTYPE_MAP_KEY); this.ID = parent.getID(); this.log10PError = parent.getLog10PError(); this.referenceBaseForIndel = parent.getReferenceBaseForIndel(); @@ -179,7 +177,7 @@ public class VariantContextBuilder { /** * Makes the attributes field modifiable. In many cases attributes is just a pointer to an immutable - * collection, so methods that want to add / remove records require the attributes to be copied first + * collection, so methods that want to add / remove records require the attributes to be copied to a */ private void makeAttributesModifiable() { if ( ! attributesCanBeModified ) { @@ -243,6 +241,11 @@ public class VariantContextBuilder { return this; } + public VariantContextBuilder genotypesNoValidation(final GenotypesContext genotypes) { + this.genotypes = genotypes; + return this; + } + /** * Tells this builder that the resulting VariantContext should use a GenotypeContext containing genotypes * @@ -270,15 +273,6 @@ public class VariantContextBuilder { return this; } - /** - * ADVANCED! tells us that the genotypes data is stored as an unparsed attribute - * @return - */ - public VariantContextBuilder genotypesAreUnparsed() { - this.genotypesAreUnparsed = true; - return this; - } - /** * Tells us that the resulting VariantContext should have ID * @param ID @@ -395,6 +389,6 @@ public class VariantContextBuilder { public VariantContext make() { return new VariantContext(source, ID, contig, start, stop, alleles, genotypes, log10PError, filters, attributes, - referenceBaseForIndel, genotypesAreUnparsed, toValidate); + referenceBaseForIndel, toValidate); } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java index 1cb43ceb1..c2348b4a3 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java @@ -14,9 +14,12 @@ public class VariantFiltrationIntegrationTest extends WalkerTest { @Test public void testNoAction() { + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("8a105fa5eebdfffe7326bc5b3d8ffd1c")); + Arrays.asList("b7b7c218e219cd923ce5b6eefc5b7171")); executeTest("test no action", spec); } @@ -24,59 +27,86 @@ public class VariantFiltrationIntegrationTest extends WalkerTest { public void testClusteredSnps() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -window 10 --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("27b13f179bb4920615dff3a32730d845")); + Arrays.asList("6d45a19e4066e7de6ff6a61f43ffad2b")); executeTest("test clustered SNPs", spec); } @Test - public void testMasks() { + public void testMask1() { + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output WalkerTestSpec spec1 = new WalkerTestSpec( baseTestString() + " -maskName foo --mask:VCF3 " + validationDataLocation + "vcfexample2.vcf --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("578f9e774784c25871678e6464fd212b")); + Arrays.asList("65b5006bf3ee9d9d08a36d6b854773f2")); executeTest("test mask all", spec1); + } + @Test + public void testMask2() { + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output WalkerTestSpec spec2 = new WalkerTestSpec( baseTestString() + " -maskName foo --mask:VCF " + validationDataLocation + "vcfMask.vcf --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("bfa86a674aefca1b13d341cb14ab3c4f")); + Arrays.asList("a275d36baca81a1ce03dbb528e95a069")); executeTest("test mask some", spec2); + } + @Test + public void testMask3() { + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output WalkerTestSpec spec3 = new WalkerTestSpec( baseTestString() + " -maskName foo -maskExtend 10 --mask:VCF " + validationDataLocation + "vcfMask.vcf --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("5939f80d14b32d88587373532d7b90e5")); + Arrays.asList("c9489e1c1342817c36ab4f0770609bdb")); executeTest("test mask extend", spec3); } @Test public void testFilter1() { WalkerTestSpec spec = new WalkerTestSpec( + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output baseTestString() + " -filter 'DoC < 20 || FisherStrand > 20.0' -filterName foo --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("45219dbcfb6f81bba2ea0c35f5bfd368")); + Arrays.asList("327a611bf82c6c4ae77fbb6d06359f9d")); executeTest("test filter #1", spec); } @Test public void testFilter2() { + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -filter 'AlleleBalance < 70.0 && FisherStrand == 1.4' -filterName bar --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("c95845e817da7352b9b72bc9794f18fb")); + Arrays.asList("7612b3460575402ad78fa4173178bdcc")); executeTest("test filter #2", spec); } @Test public void testFilterWithSeparateNames() { + // note that this input if slightly malformed, but with the new properly + // only when really needed genotype loading of VCF files we don't actually + // fix the file in the output WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --filterName ABF -filter 'AlleleBalance < 0.7' --filterName FSF -filter 'FisherStrand == 1.4' --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("b8cdd7f44ff1a395e0a9b06a87e1e530")); + Arrays.asList("dce33441f58b284ac9ab94f8e64b84e3")); executeTest("test filter with separate names #2", spec); } @Test - public void testGenotypeFilters() { + public void testGenotypeFilters1() { WalkerTestSpec spec1 = new WalkerTestSpec( baseTestString() + " -G_filter 'GQ == 0.60' -G_filterName foo --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, Arrays.asList("96b61e4543a73fe725e433f007260039")); executeTest("test genotype filter #1", spec1); + } + @Test + public void testGenotypeFilters2() { WalkerTestSpec spec2 = new WalkerTestSpec( baseTestString() + " -G_filter 'AF == 0.04 && isHomVar == 1' -G_filterName foo --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, Arrays.asList("6c8112ab17ce39c8022c891ae73bf38e")); From f9e25081aba3fa9e5cc4bceb5d2cf184084a397a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sun, 20 Nov 2011 08:35:52 -0500 Subject: [PATCH 076/113] Completed documented LazyGenotypesContext --- .../variantcontext/LazyGenotypesContext.java | 88 +++++++++++++------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java index ca2d7a812..fccc8c667 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -24,6 +24,8 @@ package org.broadinstitute.sting.utils.variantcontext; +import com.google.java.contract.Ensures; +import com.google.java.contract.Requires; import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; import java.util.ArrayList; @@ -32,44 +34,55 @@ import java.util.List; import java.util.Map; /** - * [Short one sentence description of this walker] - *

- *

- * [Functionality of this walker] - *

- *

- *

Input

- *

- * [Input description] - *

- *

- *

Output

- *

- * [Output description] - *

- *

- *

Examples

- *
- *    java
- *      -jar GenomeAnalysisTK.jar
- *      -T $WalkerName
- *  
- * - * @author Your Name - * @since Date created + * Lazy-loading GenotypesContext. A lazy-loading context has access to the + * VCFParser and a unparsed string of genotype data. If the user attempts to manipulate + * the genotypes contained in this context, we decode the data and become a full blown + * GenotypesContext. However, if the user never does this we are spared a lot of expense + * decoding the genotypes unnecessarily. */ public class LazyGenotypesContext extends GenotypesContext { + /** parser the VCF parser we'll use to decode unparsedGenotypeData if necessary */ final VCFParser parser; + + /** a string containing the unparsed VCF genotypes */ String unparsedGenotypeData; + + /** alleles the current list of alleles at the site (known already in the parser) */ final List alleles; + + /** contig the current contig (known already in the parser) */ final String contig; + + /** the current start position (known already in the parser) */ final int start; + + /** + * nUnparsedGenotypes the number of genotypes contained in the unparsedGenotypes data + * (known already in the parser). Useful for isEmpty and size() optimizations + */ final int nUnparsedGenotypes; + /** + * True if we've already decoded the values in unparsedGenotypeData + */ boolean loaded = false; private final static ArrayList EMPTY = new ArrayList(0); + /** + * Creates a new lazy loading genotypes context + * + * @param parser the VCF parser we'll use to decode unparsedGenotypeData if necessary + * @param unparsedGenotypeData a string containing the unparsed VCF genotypes + * @param contig the current contig (known already in the parser) + * @param start the current start position (known already in the parser) + * @param alleles the current list of alleles at the site (known already in the parser) + * @param nUnparsedGenotypes the number of genotypes contained in the unparsedGenotypes data + * (known already in the parser). Useful for isEmpty and size() optimizations + */ + @Requires({"parser != null", "unparsedGenotypeData != null", + "contig != null", "start >= 0", "alleles != null && alleles.size() > 0", + "nUnparsedGenotypes > 0"}) public LazyGenotypesContext(final VCFParser parser, final String unparsedGenotypeData, final String contig, final int start, final List alleles, int nUnparsedGenotypes ) { @@ -82,7 +95,16 @@ public class LazyGenotypesContext extends GenotypesContext { this.nUnparsedGenotypes = nUnparsedGenotypes; } + /** + * Overrides the genotypes accessor. If we haven't already, decode the genotypes data + * and store the decoded results in the appropriate variables. Otherwise we just + * returned the decoded result directly. Note some care needs to be taken here as + * the value in notToBeDirectlyAccessedGenotypes may diverge from what would be produced + * by decode, if after the first decode the genotypes themselves are replaced + * @return + */ @Override + @Ensures("result != null") protected ArrayList getGenotypes() { if ( ! loaded ) { //System.out.printf("Loading genotypes... %s:%d%n", contig, start); @@ -90,9 +112,9 @@ public class LazyGenotypesContext extends GenotypesContext { notToBeDirectlyAccessedGenotypes = subcontext.notToBeDirectlyAccessedGenotypes; sampleNamesInOrder = subcontext.sampleNamesInOrder; sampleNameToOffset = subcontext.sampleNameToOffset; - cacheIsInvalid = false; + cacheIsInvalid = false; // these values build the cache loaded = true; - unparsedGenotypeData = null; + unparsedGenotypeData = null; // don't hold the unparsed data any longer // warning -- this path allows us to create a VariantContext that doesn't run validateGenotypes() // That said, it's not such an important routine -- it's just checking that the genotypes @@ -102,9 +124,19 @@ public class LazyGenotypesContext extends GenotypesContext { return notToBeDirectlyAccessedGenotypes; } + /** + * Overrides the buildCache functionality. If the data hasn't been loaded + * yet and we want to build the cache, just decode it and we're done. If we've + * already decoded the data, though, go through the super class + */ + @Override protected synchronized void buildCache() { if ( cacheIsInvalid ) { - getGenotypes(); // will load up all of the necessary data + if ( ! loaded ) { + getGenotypes(); // will load up all of the necessary data + } else { + super.buildCache(); + } } } From 9445326c6cbcc68fc46f8109d2e173824197706c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sun, 20 Nov 2011 18:26:27 -0500 Subject: [PATCH 077/113] Genotype is Comparable via sampleName --- build.xml | 4 ++-- .../sting/utils/variantcontext/Genotype.java | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index 232b074f6..66d99ac67 100644 --- a/build.xml +++ b/build.xml @@ -927,8 +927,8 @@ - - + + diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java index b100d6da5..1691129c9 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -12,7 +12,7 @@ import java.util.*; * * @author Mark DePristo */ -public class Genotype { +public class Genotype implements Comparable { public final static String PHASED_ALLELE_SEPARATOR = "|"; public final static String UNPHASED_ALLELE_SEPARATOR = "/"; @@ -351,4 +351,14 @@ public class Genotype { public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } + + /** + * comparable genotypes -> compareTo on the sample names + * @param genotype + * @return + */ + @Override + public int compareTo(final Genotype genotype) { + return getSampleName().compareTo(genotype.getSampleName()); + } } \ No newline at end of file From bc44f6fd9ef5327869c6c2add774cee475606cf0 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sun, 20 Nov 2011 18:26:56 -0500 Subject: [PATCH 078/113] Utility function Collection -> Collection --- .../sting/utils/variantcontext/VariantContextUtils.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 07c0f7c32..21a371e2f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -1065,4 +1065,11 @@ public class VariantContextUtils { public static final GenomeLoc getLocation(GenomeLocParser genomeLocParser,VariantContext vc) { return genomeLocParser.createGenomeLoc(vc.getChr(), vc.getStart(), vc.getEnd(), true); } + + public static final Set genotypeNames(final Collection genotypes) { + final Set names = new HashSet(genotypes.size()); + for ( final Genotype g : genotypes ) + names.add(g.getSampleName()); + return names; + } } From f0ac588d32b4381c55a169d7659e46a1a933b582 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sun, 20 Nov 2011 18:28:01 -0500 Subject: [PATCH 079/113] Extensive unit test for GenotypeContextUnitTest -- Currently only tests base class. Adding subclass testing in a bit --- build.xml | 4 +- .../GenotypesContextUnitTest.java | 290 ++++++++++++++++++ 2 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java diff --git a/build.xml b/build.xml index 66d99ac67..232b074f6 100644 --- a/build.xml +++ b/build.xml @@ -927,8 +927,8 @@ - - + + diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java new file mode 100644 index 000000000..a65051fae --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +// our package +package org.broadinstitute.sting.utils.variantcontext; + + +// the imports for unit testing. + + +import org.broad.tribble.util.ParsingUtils; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.Utils; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.*; + + +public class GenotypesContextUnitTest extends BaseTest { + Allele Aref, C, T; + Genotype AA, AT, TT, AC, CT, CC, MISSING; + List allGenotypes; + + @BeforeSuite + public void before() { + C = Allele.create("C"); + Aref = Allele.create("A", true); + T = Allele.create("T"); + AA = new Genotype("AA", Arrays.asList(Aref, Aref)); + AT = new Genotype("AT", Arrays.asList(Aref, T)); + TT = new Genotype("TT", Arrays.asList(T, T)); + AC = new Genotype("AC", Arrays.asList(Aref, C)); + CT = new Genotype("CT", Arrays.asList(C, T)); + CC = new Genotype("CC", Arrays.asList(C, C)); + MISSING = new Genotype("MISSING", Arrays.asList(C, C)); + + allGenotypes = Arrays.asList(AA, AT, TT, AC, CT, CC); + } + + // -------------------------------------------------------------------------------- + // + // Provider + // + // -------------------------------------------------------------------------------- + + private interface ContextMaker { + public GenotypesContext make(List initialSamples); + } + + private ContextMaker baseMaker = new ContextMaker() { + @Override + public GenotypesContext make(final List initialSamples) { + return GenotypesContext.copy(initialSamples); + } + }; + + private Collection allMakers = + Arrays.asList(baseMaker); + + private class GenotypesContextProvider extends TestDataProvider { + ContextMaker maker; + final List initialSamples; + + private GenotypesContextProvider(ContextMaker maker, List initialSamples) { + super(GenotypesContextProvider.class); + this.maker = maker; + this.initialSamples = initialSamples; + } + + public GenotypesContext makeContext() { + return maker.make(initialSamples); + } + } + + @DataProvider(name = "GenotypesContextProvider") + public Object[][] MakeSampleNamesTest() { + for ( ContextMaker maker : allMakers ) { + for ( int i = 0; i < allGenotypes.size(); i++ ) { + List samples = allGenotypes.subList(0, i); + // sorted + new GenotypesContextProvider(maker, samples); + // unsorted + new GenotypesContextProvider(maker, Utils.reverse(samples)); + } + } + + return GenotypesContextProvider.getTests(GenotypesContextProvider.class); + } + + private final static void testIterable(Iterable genotypeIterable, Set expectedNames) { + int count = 0; + for ( final Genotype g : genotypeIterable ) { + Assert.assertTrue(expectedNames.contains(g.getSampleName())); + count++; + } + Assert.assertEquals(count, expectedNames.size(), "Iterable returned unexpected number of genotypes"); + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testInitialSamplesAreAsExpected(GenotypesContextProvider cfg) { + testGenotypesContextContainsExpectedSamples(cfg.makeContext(), cfg.initialSamples); + } + + private final void testGenotypesContextContainsExpectedSamples(GenotypesContext gc, List expectedSamples) { + Assert.assertEquals(gc.isEmpty(), expectedSamples.isEmpty()); + Assert.assertEquals(gc.size(), expectedSamples.size()); + + // get(index) is doing the right thing + for ( int i = 0; i < expectedSamples.size(); i++ ) { + Assert.assertEquals(gc.get(i), expectedSamples.get(i)); + } + Assert.assertFalse(gc.containsSample(MISSING.getSampleName())); + + // we can fetch samples by name + final Set genotypeNames = VariantContextUtils.genotypeNames(expectedSamples); + for ( final String name : genotypeNames ) { + Assert.assertTrue(gc.containsSample(name)); + } + Assert.assertFalse(gc.containsSample(MISSING.getSampleName())); + + // all of the iterators are working + testIterable(gc.iterateInSampleNameOrder(), genotypeNames); + testIterable(gc, genotypeNames); + testIterable(gc.iterateInSampleNameOrder(genotypeNames), genotypeNames); + if ( ! genotypeNames.isEmpty() ) { + Set first = Collections.singleton(genotypeNames.iterator().next()); + testIterable(gc.iterateInSampleNameOrder(first), first); + } + + // misc. utils are working as expected + Assert.assertEquals(gc.getSampleNames(), genotypeNames); + Assert.assertTrue(ParsingUtils.isSorted(gc.getSampleNamesOrderedByName())); + Assert.assertTrue(ParsingUtils.isSorted(gc.iterateInSampleNameOrder())); + Assert.assertTrue(gc.containsSamples(genotypeNames)); + + final Set withMissing = new HashSet(Arrays.asList(MISSING.getSampleName())); + withMissing.addAll(genotypeNames); + Assert.assertFalse(gc.containsSamples(withMissing)); + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testImmutable(GenotypesContextProvider cfg) { + GenotypesContext gc = cfg.makeContext(); + Assert.assertEquals(gc.isMutable(), true); + gc.immutable(); + Assert.assertEquals(gc.isMutable(), false); + } + + @Test(dataProvider = "GenotypesContextProvider", expectedExceptions = Throwable.class ) + public void testImmutableCall1(GenotypesContextProvider cfg) { + GenotypesContext gc = cfg.makeContext(); + gc.immutable(); + gc.add(MISSING); + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testClear(GenotypesContextProvider cfg) { + GenotypesContext gc = cfg.makeContext(); + gc.clear(); + testGenotypesContextContainsExpectedSamples(gc, Collections.emptyList()); + } + + private static final List with(List genotypes, Genotype ... add) { + List l = new ArrayList(genotypes); + l.addAll(Arrays.asList(add)); + return l; + } + + private static final List without(List genotypes, Genotype ... remove) { + List l = new ArrayList(genotypes); + l.removeAll(Arrays.asList(remove)); + return l; + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testAdds(GenotypesContextProvider cfg) { + Genotype add1 = new Genotype("add1", Arrays.asList(Aref, Aref)); + Genotype add2 = new Genotype("add2", Arrays.asList(Aref, Aref)); + + GenotypesContext gc = cfg.makeContext(); + gc.add(add1); + testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1)); + + gc = cfg.makeContext(); + gc.add(add1); + gc.add(add2); + testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); + + gc = cfg.makeContext(); + gc.add(add1, add2); + testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); + + gc = cfg.makeContext(); + gc.addAll(Arrays.asList(add1, add2)); + testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testRemoves(GenotypesContextProvider cfg) { + Genotype rm1 = AA; + Genotype rm2 = AC; + + GenotypesContext gc = cfg.makeContext(); + if (gc.size() > 1) { + Genotype rm = gc.get(0); + gc.remove(rm); + testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm)); + } + + gc = cfg.makeContext(); + gc.remove(rm1); + testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm1)); + + gc = cfg.makeContext(); + gc.remove(rm1); + gc.remove(rm2); + testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm1, rm2)); + + gc = cfg.makeContext(); + gc.removeAll(Arrays.asList(rm1, rm2)); + testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm1, rm2)); + + gc = cfg.makeContext(); + HashSet expected = new HashSet(); + if ( gc.contains(rm1) ) expected.add(rm1); + if ( gc.contains(rm2) ) expected.add(rm2); + gc.retainAll(Arrays.asList(rm1, rm2)); + + // ensure that the two lists are the same + Assert.assertEquals(new HashSet(gc.getGenotypes()), expected); + // because the list order can change, we use the gc's list itself + testGenotypesContextContainsExpectedSamples(gc, gc.getGenotypes()); + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testSet(GenotypesContextProvider cfg) { + Genotype set = new Genotype("replace", Arrays.asList(Aref, Aref)); + int n = cfg.makeContext().size(); + for ( int i = 0; i < n; i++ ) { + GenotypesContext gc = cfg.makeContext(); + Genotype setted = gc.set(i, set); + Assert.assertNotNull(setted); + ArrayList l = new ArrayList(cfg.initialSamples); + l.set(i, set); + testGenotypesContextContainsExpectedSamples(gc, l); + } + } + + @Test(dataProvider = "GenotypesContextProvider") + public void testReplace(GenotypesContextProvider cfg) { + int n = cfg.makeContext().size(); + for ( int i = 0; i < n; i++ ) { + GenotypesContext gc = cfg.makeContext(); + Genotype toReplace = gc.get(i); + Genotype replacement = new Genotype(toReplace.getSampleName(), Arrays.asList(Aref, Aref)); + gc.replace(replacement); + ArrayList l = new ArrayList(cfg.initialSamples); + l.set(i, replacement); + Assert.assertEquals(replacement, gc.get(i)); + testGenotypesContextContainsExpectedSamples(gc, l); + } + } + + // subset to samples tested in VariantContextUnitTest +} \ No newline at end of file From 2e9ecf639ef8846faa40e1200c032bf80c38406e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 09:30:40 -0500 Subject: [PATCH 080/113] Generalized interface to LazyGenotypesContext -- Now you provide a LazyParsing object -- LazyGenotypesContext now knows nothing about the VCF parser itself. The parser holds all of the necessary data to parse the VCF genotypes when necessarily, and the LGC only has a pointer to this object -- Using new interface added LazyGenotypesContext to unit tests with a simple lazy version -- Deleted VCFParser interface, as it was no longer necessary --- .../utils/codecs/vcf/AbstractVCFCodec.java | 32 ++++++- .../utils/codecs/vcf/StandardVCFWriter.java | 2 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 10 +- .../sting/utils/codecs/vcf/VCFCodec.java | 10 +- .../sting/utils/codecs/vcf/VCFParser.java | 24 ----- .../variantcontext/GenotypesContext.java | 5 +- .../variantcontext/LazyGenotypesContext.java | 93 +++++++++++-------- .../utils/variantcontext/VariantContext.java | 2 - .../GenotypesContextUnitTest.java | 31 +++++-- 9 files changed, 117 insertions(+), 92 deletions(-) delete mode 100755 public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index ee3184dc2..216ee3fb6 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -17,7 +17,7 @@ import java.util.*; import java.util.zip.GZIPInputStream; -public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, VCFParser { +public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec { protected final static Logger log = Logger.getLogger(VCFCodec.class); protected final static int NUM_STANDARD_FIELDS = 8; // INFO is the 8th column @@ -59,6 +59,29 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, protected Map stringCache = new HashMap(); + /** + * Creates a LazyParser for a LazyGenotypesContext to use to decode + * our genotypes only when necessary. We do this instead of eagarly + * decoding the genotypes just to turn around and reencode in the frequent + * case where we don't actually want to manipulate the genotypes + */ + class LazyVCFGenotypesParser implements LazyGenotypesContext.LazyParser { + final List alleles; + final String contig; + final int start; + + LazyVCFGenotypesParser(final List alleles, final String contig, final int start) { + this.alleles = alleles; + this.contig = contig; + this.start = start; + } + + @Override + public LazyGenotypesContext.LazyData parse(final Object data) { + //System.out.printf("Loading genotypes... %s:%d%n", contig, start); + return createGenotypeMap((String) data, alleles, contig, start); + } + } /** * @param reader the line reader to take header lines from @@ -68,13 +91,14 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, /** * create a genotype map + * * @param str the string * @param alleles the list of alleles * @param chr chrom * @param pos position * @return a mapping of sample name to genotype object */ - public abstract GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos); + public abstract LazyGenotypesContext.LazyData createGenotypeMap(String str, List alleles, String chr, int pos); /** @@ -294,7 +318,9 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, // do we have genotyping data if (parts.length > NUM_STANDARD_FIELDS) { - LazyGenotypesContext lazy = new LazyGenotypesContext(this, parts[8], chr, pos, alleles, header.getGenotypeSamples().size()); + final LazyGenotypesContext.LazyParser lazyParser = new LazyVCFGenotypesParser(alleles, chr, pos); + final int nGenotypes = header.getGenotypeSamples().size(); + LazyGenotypesContext lazy = new LazyGenotypesContext(lazyParser, parts[8], nGenotypes); builder.genotypesNoValidation(lazy); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java index 7a496cb7c..ac1da7110 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/StandardVCFWriter.java @@ -229,7 +229,7 @@ public class StandardVCFWriter extends IndexingVCFWriter { final GenotypesContext gc = vc.getGenotypes(); if ( gc instanceof LazyGenotypesContext && ((LazyGenotypesContext)gc).getUnparsedGenotypeData() != null) { mWriter.write(VCFConstants.FIELD_SEPARATOR); - mWriter.write(((LazyGenotypesContext)gc).getUnparsedGenotypeData()); + mWriter.write(((LazyGenotypesContext)gc).getUnparsedGenotypeData().toString()); } else { List genotypeAttributeKeys = new ArrayList(); if ( vc.hasGenotypes() ) { diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java index 5e6e2e94a..6f8e64e55 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java @@ -3,10 +3,7 @@ package org.broadinstitute.sting.utils.codecs.vcf; import org.broad.tribble.TribbleException; import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.File; import java.io.IOException; @@ -112,13 +109,14 @@ public class VCF3Codec extends AbstractVCFCodec { /** * create a genotype map + * * @param str the string * @param alleles the list of alleles * @param chr chrom * @param pos position * @return a mapping of sample name to genotype object */ - public GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos) { + public LazyGenotypesContext.LazyData createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; @@ -191,7 +189,7 @@ public class VCF3Codec extends AbstractVCFCodec { } } - return GenotypesContext.create(genotypes, header.sampleNameToOffset, header.sampleNamesInOrder); + return new LazyGenotypesContext.LazyData(genotypes, header.sampleNamesInOrder, header.sampleNameToOffset); } @Override diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java index b7b7eb5f7..407c4bc41 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java @@ -3,10 +3,7 @@ package org.broadinstitute.sting.utils.codecs.vcf; import org.broad.tribble.TribbleException; import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.io.File; import java.io.IOException; @@ -141,11 +138,12 @@ public class VCFCodec extends AbstractVCFCodec { /** * create a genotype map + * * @param str the string * @param alleles the list of alleles * @return a mapping of sample name to genotype object */ - public GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos) { + public LazyGenotypesContext.LazyData createGenotypeMap(String str, List alleles, String chr, int pos) { if (genotypeParts == null) genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; @@ -215,7 +213,7 @@ public class VCFCodec extends AbstractVCFCodec { } } - return GenotypesContext.create(genotypes, header.sampleNameToOffset, header.sampleNamesInOrder); + return new LazyGenotypesContext.LazyData(genotypes, header.sampleNamesInOrder, header.sampleNameToOffset); } @Override diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java deleted file mode 100755 index 8903a176a..000000000 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFParser.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.broadinstitute.sting.utils.codecs.vcf; - -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; - -import java.util.List; - - -/** - * All VCF codecs need to implement this interface so that we can perform lazy loading. - */ -public interface VCFParser { - - /** - * create a genotype map - * @param str the string - * @param alleles the list of alleles - * @param chr chrom - * @param pos position - * @return a mapping of sample name to genotype object - */ - public GenotypesContext createGenotypeMap(String str, List alleles, String chr, int pos); - -} diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 846e6c89c..ab5ab9465 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -251,7 +251,7 @@ public class GenotypesContext implements List { // --------------------------------------------------------------------------- @Ensures({"cacheIsInvalid == true"}) - private synchronized void invalidateCaches() { + protected void invalidateCaches() { cacheIsInvalid = true; sampleNamesInOrder = null; sampleNameToOffset = null; @@ -262,7 +262,7 @@ public class GenotypesContext implements List { "sampleNameToOffset != null", "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNamesInOrder)", "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNameToOffset.keySet())"}) - protected synchronized void buildCache() { + protected void buildCache() { if ( cacheIsInvalid ) { cacheIsInvalid = false; sampleNamesInOrder = new ArrayList(size()); @@ -291,6 +291,7 @@ public class GenotypesContext implements List { @Override public void clear() { checkImmutability(); + invalidateCaches(); getGenotypes().clear(); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java index fccc8c667..7facfacf6 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -26,10 +26,8 @@ package org.broadinstitute.sting.utils.variantcontext; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; -import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -41,20 +39,10 @@ import java.util.Map; * decoding the genotypes unnecessarily. */ public class LazyGenotypesContext extends GenotypesContext { - /** parser the VCF parser we'll use to decode unparsedGenotypeData if necessary */ - final VCFParser parser; + /** The LazyParser we'll use to decode unparsedGenotypeData if necessary */ + final LazyParser parser; - /** a string containing the unparsed VCF genotypes */ - String unparsedGenotypeData; - - /** alleles the current list of alleles at the site (known already in the parser) */ - final List alleles; - - /** contig the current contig (known already in the parser) */ - final String contig; - - /** the current start position (known already in the parser) */ - final int start; + Object unparsedGenotypeData; /** * nUnparsedGenotypes the number of genotypes contained in the unparsedGenotypes data @@ -70,28 +58,48 @@ public class LazyGenotypesContext extends GenotypesContext { private final static ArrayList EMPTY = new ArrayList(0); /** - * Creates a new lazy loading genotypes context - * - * @param parser the VCF parser we'll use to decode unparsedGenotypeData if necessary - * @param unparsedGenotypeData a string containing the unparsed VCF genotypes - * @param contig the current contig (known already in the parser) - * @param start the current start position (known already in the parser) - * @param alleles the current list of alleles at the site (known already in the parser) - * @param nUnparsedGenotypes the number of genotypes contained in the unparsedGenotypes data - * (known already in the parser). Useful for isEmpty and size() optimizations + * Simple lazy parser interface. Provide an object implementing this + * interface to LazyGenotypesContext, and it's parse method will be called + * when the use of the lazy context requires the underlying genotypes data + * be parsed into Genotype objects. The data argument is the data provided + * to the LazyGenotypesContext holding encoded genotypes data */ - @Requires({"parser != null", "unparsedGenotypeData != null", - "contig != null", "start >= 0", "alleles != null && alleles.size() > 0", - "nUnparsedGenotypes > 0"}) - public LazyGenotypesContext(final VCFParser parser, final String unparsedGenotypeData, - final String contig, final int start, final List alleles, - int nUnparsedGenotypes ) { + public interface LazyParser { + public LazyData parse(Object data); + } + + /** + * Returns the data used in the full GenotypesContext constructor + * + * {@link GenotypesContext#GenotypesContext(java.util.ArrayList, java.util.Map, java.util.List, boolean)} + */ + public static class LazyData { + final ArrayList genotypes; + final Map sampleNameToOffset; + final List sampleNamesInOrder; + + public LazyData(final ArrayList genotypes, + final List sampleNamesInOrder, + final Map sampleNameToOffset) { + this.genotypes = genotypes; + this.sampleNamesInOrder = sampleNamesInOrder; + this.sampleNameToOffset = sampleNameToOffset; + } + } + + /** + * Creates a new lazy loading genotypes context using the LazyParser to create + * genotypes data on demand. + * + * @param parser the parser to be used to load on-demand genotypes data + * @param unparsedGenotypeData the encoded genotypes data that we will decode if necessary + * @param nUnparsedGenotypes the number of genotypes that will be produced if / when we actually decode the genotypes data + */ + @Requires({"parser != null", "unparsedGenotypeData != null", "nUnparsedGenotypes >= 0"}) + public LazyGenotypesContext(final LazyParser parser, final Object unparsedGenotypeData, final int nUnparsedGenotypes) { super(EMPTY, false); - this.unparsedGenotypeData = unparsedGenotypeData; - this.start = start; this.parser = parser; - this.contig = contig; - this.alleles = alleles; + this.unparsedGenotypeData = unparsedGenotypeData; this.nUnparsedGenotypes = nUnparsedGenotypes; } @@ -108,10 +116,10 @@ public class LazyGenotypesContext extends GenotypesContext { protected ArrayList getGenotypes() { if ( ! loaded ) { //System.out.printf("Loading genotypes... %s:%d%n", contig, start); - GenotypesContext subcontext = parser.createGenotypeMap(unparsedGenotypeData, alleles, contig, start); - notToBeDirectlyAccessedGenotypes = subcontext.notToBeDirectlyAccessedGenotypes; - sampleNamesInOrder = subcontext.sampleNamesInOrder; - sampleNameToOffset = subcontext.sampleNameToOffset; + LazyData parsed = parser.parse(unparsedGenotypeData); + notToBeDirectlyAccessedGenotypes = parsed.genotypes; + sampleNamesInOrder = parsed.sampleNamesInOrder; + sampleNameToOffset = parsed.sampleNameToOffset; cacheIsInvalid = false; // these values build the cache loaded = true; unparsedGenotypeData = null; // don't hold the unparsed data any longer @@ -140,6 +148,13 @@ public class LazyGenotypesContext extends GenotypesContext { } } + @Override + protected void invalidateCaches() { + // if the cache is invalidated, and we haven't loaded our data yet, do so + if ( ! loaded ) getGenotypes(); + super.invalidateCaches(); + } + @Override public boolean isEmpty() { // optimization -- we know the number of samples in the unparsed data, so use it here to @@ -154,7 +169,7 @@ public class LazyGenotypesContext extends GenotypesContext { return loaded ? super.size() : nUnparsedGenotypes; } - public String getUnparsedGenotypeData() { + public Object getUnparsedGenotypeData() { return unparsedGenotypeData; } } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 8d74f5220..331ca97d3 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -1,11 +1,9 @@ package org.broadinstitute.sting.utils.variantcontext; -import org.apache.commons.lang.Validate; import org.broad.tribble.Feature; import org.broad.tribble.TribbleException; import org.broad.tribble.util.ParsingUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; -import org.broadinstitute.sting.utils.codecs.vcf.VCFParser; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import java.util.*; diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java index a65051fae..c12fbac9c 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java @@ -79,8 +79,21 @@ public class GenotypesContextUnitTest extends BaseTest { } }; - private Collection allMakers = - Arrays.asList(baseMaker); + private final class lazyMaker implements LazyGenotypesContext.LazyParser, ContextMaker { + @Override + public LazyGenotypesContext.LazyData parse(final Object data) { + GenotypesContext gc = GenotypesContext.copy((List)data); + gc.buildCache(); + return new LazyGenotypesContext.LazyData(gc.notToBeDirectlyAccessedGenotypes, gc.sampleNamesInOrder, gc.sampleNameToOffset); + } + + @Override + public GenotypesContext make(final List initialSamples) { + return new LazyGenotypesContext(this, initialSamples, initialSamples.size()); + } + } + + private Collection allMakers = Arrays.asList(baseMaker, new lazyMaker()); private class GenotypesContextProvider extends TestDataProvider { ContextMaker maker; @@ -163,7 +176,7 @@ public class GenotypesContextUnitTest extends BaseTest { Assert.assertFalse(gc.containsSamples(withMissing)); } - @Test(dataProvider = "GenotypesContextProvider") + @Test(enabled = true, dataProvider = "GenotypesContextProvider") public void testImmutable(GenotypesContextProvider cfg) { GenotypesContext gc = cfg.makeContext(); Assert.assertEquals(gc.isMutable(), true); @@ -171,14 +184,14 @@ public class GenotypesContextUnitTest extends BaseTest { Assert.assertEquals(gc.isMutable(), false); } - @Test(dataProvider = "GenotypesContextProvider", expectedExceptions = Throwable.class ) + @Test(enabled = true, dataProvider = "GenotypesContextProvider", expectedExceptions = Throwable.class ) public void testImmutableCall1(GenotypesContextProvider cfg) { GenotypesContext gc = cfg.makeContext(); gc.immutable(); gc.add(MISSING); } - @Test(dataProvider = "GenotypesContextProvider") + @Test(enabled = true, dataProvider = "GenotypesContextProvider") public void testClear(GenotypesContextProvider cfg) { GenotypesContext gc = cfg.makeContext(); gc.clear(); @@ -197,7 +210,7 @@ public class GenotypesContextUnitTest extends BaseTest { return l; } - @Test(dataProvider = "GenotypesContextProvider") + @Test(enabled = true, dataProvider = "GenotypesContextProvider") public void testAdds(GenotypesContextProvider cfg) { Genotype add1 = new Genotype("add1", Arrays.asList(Aref, Aref)); Genotype add2 = new Genotype("add2", Arrays.asList(Aref, Aref)); @@ -220,7 +233,7 @@ public class GenotypesContextUnitTest extends BaseTest { testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); } - @Test(dataProvider = "GenotypesContextProvider") + @Test(enabled = true, dataProvider = "GenotypesContextProvider") public void testRemoves(GenotypesContextProvider cfg) { Genotype rm1 = AA; Genotype rm2 = AC; @@ -257,7 +270,7 @@ public class GenotypesContextUnitTest extends BaseTest { testGenotypesContextContainsExpectedSamples(gc, gc.getGenotypes()); } - @Test(dataProvider = "GenotypesContextProvider") + @Test(enabled = true, dataProvider = "GenotypesContextProvider") public void testSet(GenotypesContextProvider cfg) { Genotype set = new Genotype("replace", Arrays.asList(Aref, Aref)); int n = cfg.makeContext().size(); @@ -271,7 +284,7 @@ public class GenotypesContextUnitTest extends BaseTest { } } - @Test(dataProvider = "GenotypesContextProvider") + @Test(enabled = true, dataProvider = "GenotypesContextProvider") public void testReplace(GenotypesContextProvider cfg) { int n = cfg.makeContext().size(); for ( int i = 0; i < n; i++ ) { From e467b8e1aeae88f9967001b8a8212c43db0ebf4c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 09:34:57 -0500 Subject: [PATCH 081/113] More contracts on LazyGenotypesContext --- .../sting/utils/variantcontext/GenotypesContext.java | 2 +- .../sting/utils/variantcontext/LazyGenotypesContext.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index ab5ab9465..25b277298 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -604,7 +604,7 @@ public class GenotypesContext implements List { } } - private final static boolean sameSamples(List genotypes, Collection sampleNamesInOrder) { + protected final static boolean sameSamples(List genotypes, Collection sampleNamesInOrder) { Set names = new HashSet(sampleNamesInOrder); if ( names.size() != sampleNamesInOrder.size() ) return false; diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java index 7facfacf6..b3a24aef5 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -65,6 +65,8 @@ public class LazyGenotypesContext extends GenotypesContext { * to the LazyGenotypesContext holding encoded genotypes data */ public interface LazyParser { + @Requires("data != null") + @Ensures("result != null") public LazyData parse(Object data); } @@ -78,6 +80,9 @@ public class LazyGenotypesContext extends GenotypesContext { final Map sampleNameToOffset; final List sampleNamesInOrder; + @Requires({"genotypes != null", "sampleNamesInOrder != null", "sampleNameToOffset != null", + "sameSamples(genotypes, sampleNamesInOrder)", + "sameSamples(genotypes, sampleNameToOffset.keySet())"}) public LazyData(final ArrayList genotypes, final List sampleNamesInOrder, final Map sampleNameToOffset) { From 1296dd41bea46ddd64b20104ab02cc62eac46255 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Mon, 21 Nov 2011 11:52:39 -0500 Subject: [PATCH 082/113] Removing the legacy -L "interval1;interval2" syntax This syntax predates the ability to have multiple -L arguments, is inconsistent with the syntax of all other GATK arguments, requires quoting to avoid interpretation by the shell, and was causing problems in Queue. A UserException is now thrown if someone tries to use this syntax. --- .../sting/commandline/IntervalBinding.java | 2 +- .../sting/utils/interval/IntervalUtils.java | 42 ++++++++++--------- .../CallableLociWalkerIntegrationTest.java | 2 +- ...astaAlternateReferenceIntegrationTest.java | 8 ++-- .../utils/interval/IntervalUtilsUnitTest.java | 13 ++++++ 5 files changed, 41 insertions(+), 26 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java b/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java index f920d90ef..82c7b20b6 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java +++ b/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java @@ -45,7 +45,7 @@ import java.util.*; * * The IntervalBinding is a formal GATK argument that bridges between a walker and * the engine to construct intervals for traversal at runtime. The IntervalBinding can - * either be a RodBinding, a string of one or more intervals, or a file with interval strings. + * either be a RodBinding, a string of one interval, or a file with interval strings. * The GATK Engine takes care of initializing the binding when appropriate and determining intervals from it. * * Note that this class is immutable. diff --git a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java index 159b145a0..f8655f74a 100644 --- a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java @@ -56,28 +56,30 @@ public class IntervalUtils { public static List parseIntervalArguments(GenomeLocParser parser, String arg) { List rawIntervals = new ArrayList(); // running list of raw GenomeLocs - // separate argument on semicolon first - for (String fileOrInterval : arg.split(";")) { - // if any argument is 'unmapped', "parse" it to a null entry. A null in this case means 'all the intervals with no alignment data'. - if (isUnmapped(fileOrInterval)) - rawIntervals.add(GenomeLoc.UNMAPPED); - // if it's a file, add items to raw interval list - else if (isIntervalFile(fileOrInterval)) { - try { - rawIntervals.addAll(intervalFileToList(parser, fileOrInterval)); - } - catch ( UserException.MalformedGenomeLoc e ) { - throw e; - } - catch ( Exception e ) { - throw new UserException.MalformedFile(fileOrInterval, "Interval file could not be parsed in any supported format.", e); - } - } + if ( arg.indexOf(';') != -1 ) { + throw new UserException.BadArgumentValue("-L " + arg, "The legacy -L \"interval1;interval2\" syntax " + + "is no longer supported. Please use one -L argument for each " + + "interval or an interval file instead."); + } - // otherwise treat as an interval -> parse and add to raw interval list - else { - rawIntervals.add(parser.parseGenomeLoc(fileOrInterval)); + // if any argument is 'unmapped', "parse" it to a null entry. A null in this case means 'all the intervals with no alignment data'. + if (isUnmapped(arg)) + rawIntervals.add(GenomeLoc.UNMAPPED); + // if it's a file, add items to raw interval list + else if (isIntervalFile(arg)) { + try { + rawIntervals.addAll(intervalFileToList(parser, arg)); } + catch ( UserException.MalformedGenomeLoc e ) { + throw e; + } + catch ( Exception e ) { + throw new UserException.MalformedFile(arg, "Interval file could not be parsed in any supported format.", e); + } + } + // otherwise treat as an interval -> parse and add to raw interval list + else { + rawIntervals.add(parser.parseGenomeLoc(arg)); } return rawIntervals; diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalkerIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalkerIntegrationTest.java index 1f3f8ebe6..3783525d1 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalkerIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalkerIntegrationTest.java @@ -52,7 +52,7 @@ public class CallableLociWalkerIntegrationTest extends WalkerTest { @Test public void testCallableLociWalker2() { - String gatk_args = commonArgs + " -format BED -L 1:10,000,000-10,000,100;1:10,000,110-10,000,120 -summary %s"; + String gatk_args = commonArgs + " -format BED -L 1:10,000,000-10,000,100 -L 1:10,000,110-10,000,120 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, Arrays.asList("c671f65712d9575b8b3e1f1dbedc146e", "d287510eac04acf5a56f5cde2cba0e4a")); executeTest("formatBed by interval", spec); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/fasta/FastaAlternateReferenceIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/fasta/FastaAlternateReferenceIntegrationTest.java index 9af39e92c..1c5db4262 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/fasta/FastaAlternateReferenceIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/fasta/FastaAlternateReferenceIntegrationTest.java @@ -12,25 +12,25 @@ public class FastaAlternateReferenceIntegrationTest extends WalkerTest { String md5_1 = "328d2d52cedfdc52da7d1abff487633d"; WalkerTestSpec spec1a = new WalkerTestSpec( - "-T FastaAlternateReferenceMaker -R " + b36KGReference + " -L 1:10,000,100-10,000,500;1:10,100,000-10,101,000;1:10,900,000-10,900,001 -o %s", + "-T FastaAlternateReferenceMaker -R " + b36KGReference + " -L 1:10,000,100-10,000,500 -L 1:10,100,000-10,101,000 -L 1:10,900,000-10,900,001 -o %s", 1, Arrays.asList(md5_1)); executeTest("testFastaReference", spec1a); WalkerTestSpec spec1b = new WalkerTestSpec( - "-T FastaReferenceMaker -R " + b36KGReference + " -L 1:10,000,100-10,000,500;1:10,100,000-10,101,000;1:10,900,000-10,900,001 -o %s", + "-T FastaReferenceMaker -R " + b36KGReference + " -L 1:10,000,100-10,000,500 -L 1:10,100,000-10,101,000 -L 1:10,900,000-10,900,001 -o %s", 1, Arrays.asList(md5_1)); executeTest("testFastaReference", spec1b); WalkerTestSpec spec2 = new WalkerTestSpec( - "-T FastaAlternateReferenceMaker -R " + b36KGReference + " -V " + validationDataLocation + "NA12878.chr1_10mb_11mb.slx.indels.vcf4 --snpmask:vcf " + b36dbSNP129 + " -L 1:10,075,000-10,075,380;1:10,093,447-10,093,847;1:10,271,252-10,271,452 -o %s", + "-T FastaAlternateReferenceMaker -R " + b36KGReference + " -V " + validationDataLocation + "NA12878.chr1_10mb_11mb.slx.indels.vcf4 --snpmask:vcf " + b36dbSNP129 + " -L 1:10,075,000-10,075,380 -L 1:10,093,447-10,093,847 -L 1:10,271,252-10,271,452 -o %s", 1, Arrays.asList("0567b32ebdc26604ddf2a390de4579ac")); executeTest("testFastaAlternateReferenceIndels", spec2); WalkerTestSpec spec3 = new WalkerTestSpec( - "-T FastaAlternateReferenceMaker -R " + b36KGReference + " -V " + GATKDataLocation + "dbsnp_129_b36.vcf -L 1:10,023,400-10,023,500;1:10,029,200-10,029,500 -o %s", + "-T FastaAlternateReferenceMaker -R " + b36KGReference + " -V " + GATKDataLocation + "dbsnp_129_b36.vcf -L 1:10,023,400-10,023,500 -L 1:10,029,200-10,029,500 -o %s", 1, Arrays.asList("8b6cd2e20c381f9819aab2d270f5e641")); executeTest("testFastaAlternateReferenceSnps", spec3); diff --git a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java index 03d33d2c5..a9035ffd9 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java @@ -3,7 +3,10 @@ package org.broadinstitute.sting.utils.interval; import net.sf.picard.reference.ReferenceSequenceFile; import net.sf.samtools.SAMFileHeader; import org.apache.commons.io.FileUtils; +import org.broad.tribble.Feature; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.commandline.IntervalBinding; +import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; import org.broadinstitute.sting.utils.GenomeLocSortedSet; import org.testng.Assert; @@ -983,4 +986,14 @@ public class IntervalUtilsUnitTest extends BaseTest { data.toString(), data.original, actual, data.expected); Assert.assertEquals(actual, data.expected, description); } + + @Test(expectedExceptions=UserException.BadArgumentValue.class) + public void testExceptionUponLegacyIntervalSyntax() throws Exception { + GenomeAnalysisEngine toolkit = new GenomeAnalysisEngine(); + toolkit.setGenomeLocParser(new GenomeLocParser(new CachingIndexedFastaSequenceFile(new File(BaseTest.hg19Reference)))); + + // Attempting to use the legacy -L "interval1;interval2" syntax should produce an exception: + IntervalBinding binding = new IntervalBinding("1;2"); + List intervals = binding.getIntervals(toolkit); + } } From 2c501364b80697c5859c20fd8dc30b3421272bf8 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 14:34:31 -0500 Subject: [PATCH 083/113] GenotypesContext no longer have immutability in constructor -- additional bug fixes throughout VariantContext and GenotypesContext objects --- .../variantcontext/GenotypesContext.java | 57 +++++++++++++------ .../variantcontext/LazyGenotypesContext.java | 4 +- .../VariantAnnotatorIntegrationTest.java | 2 +- .../GenotypesContextUnitTest.java | 12 +++- .../VariantContextUnitTest.java | 3 +- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 25b277298..8d28ba18c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -38,7 +38,7 @@ public class GenotypesContext implements List { * static constant value for an empty GenotypesContext. Useful since so many VariantContexts have no genotypes */ public final static GenotypesContext NO_GENOTYPES = - new GenotypesContext(new ArrayList(0), new HashMap(0), Collections.emptyList(), true); + new GenotypesContext(new ArrayList(0), new HashMap(0), Collections.emptyList()).immutable(); /** *sampleNamesInOrder a list of sample names, one for each genotype in genotypes, sorted in alphabetical order @@ -77,24 +77,23 @@ public class GenotypesContext implements List { * Create an empty GenotypeContext */ protected GenotypesContext() { - this(10, false); + this(10); } /** * Create an empty GenotypeContext, with initial capacity for n elements */ @Requires("n >= 0") - protected GenotypesContext(final int n, final boolean immutable) { - this(new ArrayList(n), immutable); + protected GenotypesContext(final int n) { + this(new ArrayList(n)); } /** * Create an GenotypeContext containing genotypes */ - @Requires("genotypes != null") - protected GenotypesContext(final ArrayList genotypes, final boolean immutable) { + @Requires({"genotypes != null", "noDups(genotypes)"}) + protected GenotypesContext(final ArrayList genotypes) { this.notToBeDirectlyAccessedGenotypes = genotypes; - this.immutable = immutable; this.sampleNameToOffset = null; this.cacheIsInvalid = true; } @@ -109,19 +108,16 @@ public class GenotypesContext implements List { * genotype in the vector of genotypes * @param sampleNamesInOrder a list of sample names, one for each genotype in genotypes, sorted in alphabetical * order. - * @param immutable */ - @Requires({"genotypes != null", + @Requires({"genotypes != null", "noDups(genotypes)", "sampleNameToOffset != null", "sampleNamesInOrder != null", "genotypes.size() == sampleNameToOffset.size()", "genotypes.size() == sampleNamesInOrder.size()"}) protected GenotypesContext(final ArrayList genotypes, final Map sampleNameToOffset, - final List sampleNamesInOrder, - final boolean immutable) { + final List sampleNamesInOrder) { this.notToBeDirectlyAccessedGenotypes = genotypes; - this.immutable = immutable; this.sampleNameToOffset = sampleNameToOffset; this.sampleNamesInOrder = sampleNamesInOrder; this.cacheIsInvalid = false; @@ -149,7 +145,7 @@ public class GenotypesContext implements List { @Requires("nGenotypes >= 0") @Ensures({"result != null"}) public static final GenotypesContext create(final int nGenotypes) { - return new GenotypesContext(nGenotypes, false); + return new GenotypesContext(nGenotypes); } /** @@ -173,7 +169,7 @@ public class GenotypesContext implements List { public static final GenotypesContext create(final ArrayList genotypes, final Map sampleNameToOffset, final List sampleNamesInOrder) { - return new GenotypesContext(genotypes, sampleNameToOffset, sampleNamesInOrder, false); + return new GenotypesContext(genotypes, sampleNameToOffset, sampleNamesInOrder); } /** @@ -185,7 +181,7 @@ public class GenotypesContext implements List { @Requires({"genotypes != null"}) @Ensures({"result != null"}) public static final GenotypesContext create(final ArrayList genotypes) { - return genotypes == null ? NO_GENOTYPES : new GenotypesContext(genotypes, false); + return genotypes == null ? NO_GENOTYPES : new GenotypesContext(genotypes); } /** @@ -197,7 +193,7 @@ public class GenotypesContext implements List { @Requires({"genotypes != null"}) @Ensures({"result != null"}) public static final GenotypesContext create(final Genotype... genotypes) { - return new GenotypesContext(new ArrayList(Arrays.asList(genotypes)), false); + return create(new ArrayList(Arrays.asList(genotypes))); } /** @@ -306,14 +302,16 @@ public class GenotypesContext implements List { } @Override - @Requires("genotype != null") + @Requires({"genotype != null", "get(genotype.getSampleName()) == null"}) + @Ensures("noDups(getGenotypes())") public boolean add(final Genotype genotype) { checkImmutability(); invalidateCaches(); return getGenotypes().add(genotype); } - @Requires("genotype != null") + @Requires({"genotype != null", "! containsAny(Arrays.asList(genotype))"}) + @Ensures("noDups(getGenotypes())") public boolean add(final Genotype ... genotype) { checkImmutability(); invalidateCaches(); @@ -321,11 +319,15 @@ public class GenotypesContext implements List { } @Override + @Requires("! contains(genotype)") + @Ensures("noDups(getGenotypes())") public void add(final int i, final Genotype genotype) { throw new UnsupportedOperationException(); } @Override + @Requires("! containsAny(genotypes)") + @Ensures("noDups(getGenotypes())") public boolean addAll(final Collection genotypes) { checkImmutability(); invalidateCaches(); @@ -347,6 +349,13 @@ public class GenotypesContext implements List { return getGenotypes().containsAll(objects); } + private boolean containsAny(final Collection genotypes) { + for ( final Genotype g : genotypes ) { + if ( contains(g) ) return true; + } + return false; + } + @Override public Genotype get(final int i) { return getGenotypes().get(i); @@ -421,6 +430,7 @@ public class GenotypesContext implements List { } @Override + @Ensures("noDups(getGenotypes())") public Genotype set(final int i, final Genotype genotype) { checkImmutability(); invalidateCaches(); @@ -604,6 +614,17 @@ public class GenotypesContext implements List { } } + protected final static boolean noDups(Collection genotypes) { + Set names = new HashSet(genotypes.size()); + for ( final Genotype g : genotypes ) { + if ( names.contains(g.getSampleName()) ) + return false; + names.add(g.getSampleName()); + } + + return true; + } + protected final static boolean sameSamples(List genotypes, Collection sampleNamesInOrder) { Set names = new HashSet(sampleNamesInOrder); if ( names.size() != sampleNamesInOrder.size() ) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java index b3a24aef5..5fbaadfab 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -73,7 +73,7 @@ public class LazyGenotypesContext extends GenotypesContext { /** * Returns the data used in the full GenotypesContext constructor * - * {@link GenotypesContext#GenotypesContext(java.util.ArrayList, java.util.Map, java.util.List, boolean)} + * {@link GenotypesContext#GenotypesContext(java.util.ArrayList, java.util.Map, java.util.List)} */ public static class LazyData { final ArrayList genotypes; @@ -102,7 +102,7 @@ public class LazyGenotypesContext extends GenotypesContext { */ @Requires({"parser != null", "unparsedGenotypeData != null", "nUnparsedGenotypes >= 0"}) public LazyGenotypesContext(final LazyParser parser, final Object unparsedGenotypeData, final int nUnparsedGenotypes) { - super(EMPTY, false); + super(EMPTY); this.parser = parser; this.unparsedGenotypeData = unparsedGenotypeData; this.nUnparsedGenotypes = nUnparsedGenotypes; diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index 75c27e429..1824789a9 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -58,7 +58,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { // they don't get reordered. It's a good test of the genotype ordering system. WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000", 1, - Arrays.asList("0cc0ec59f0328792e6413b6ff3f71780")); + Arrays.asList("f2ddfa8105c290b1f34b7a261a02a1ac")); executeTest("test file doesn't have annotations, not asking for annotations, #2", spec); } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java index c12fbac9c..afb9336a0 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java @@ -77,6 +77,11 @@ public class GenotypesContextUnitTest extends BaseTest { public GenotypesContext make(final List initialSamples) { return GenotypesContext.copy(initialSamples); } + + @Override + public String toString() { + return "GenotypesContext"; + } }; private final class lazyMaker implements LazyGenotypesContext.LazyParser, ContextMaker { @@ -91,6 +96,11 @@ public class GenotypesContextUnitTest extends BaseTest { public GenotypesContext make(final List initialSamples) { return new LazyGenotypesContext(this, initialSamples, initialSamples.size()); } + + @Override + public String toString() { + return "LazyGenotypesContext"; + } } private Collection allMakers = Arrays.asList(baseMaker, new lazyMaker()); @@ -100,7 +110,7 @@ public class GenotypesContextUnitTest extends BaseTest { final List initialSamples; private GenotypesContextProvider(ContextMaker maker, List initialSamples) { - super(GenotypesContextProvider.class); + super(GenotypesContextProvider.class, String.format("%s with %d samples", maker.toString(), initialSamples.size())); this.maker = maker; this.initialSamples = initialSamples; } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java index a7eac7ab9..fca7440e4 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -834,7 +834,8 @@ public class VariantContextUnitTest extends BaseTest { for ( int j = 0; j < i; j++ ) { nSamples++; Genotype g = allGenotypes.get(j % allGenotypes.size()); - gc.add(g); + final String name = String.format("%s_%d%d", g.getSampleName(), i, j); + gc.add(new Genotype(name, g.getAlleles())); switch ( g.getType() ) { case NO_CALL: nNoCall++; nNoCallAlleles++; break; case HOM_REF: nA += 2; nHomRef++; break; From 1561af22af6a54cab3625242f0b5289735b6a925 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 14:35:15 -0500 Subject: [PATCH 084/113] Exact model code cleanup -- Fixed up code when fixing a bug detected by aggressive contracts in GenotypesContext. --- .../genotyper/ExactAFCalculationModel.java | 104 ++++++++---------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index 354702dad..6aa2be419 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -42,6 +42,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { private final static double MAX_LOG10_ERROR_TO_STOP_EARLY = 6; // we want the calculation to be accurate to 1 / 10^6 private final boolean SIMPLE_GREEDY_GENOTYPER = false; private final static double SUM_GL_THRESH_NOCALL = -0.001; // if sum(gl) is bigger than this threshold, we treat GL's as non-informative and will force a no-call. + private final List NO_CALL_ALLELES = Arrays.asList(Allele.NO_CALL, Allele.NO_CALL); protected ExactAFCalculationModel(UnifiedArgumentCollection UAC, int N, Logger logger, PrintStream verboseWriter) { super(UAC, N, logger, verboseWriter); @@ -265,8 +266,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { * @return calls */ public GenotypesContext assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, - int AFofMaxLikelihood) { + double[] log10AlleleFrequencyPosteriors, + int AFofMaxLikelihood) { if ( !vc.isVariant() ) throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); @@ -338,8 +339,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - GenotypesContext calls = GenotypesContext.create(); - + final GenotypesContext calls = GenotypesContext.create(); int startIdx = AFofMaxLikelihood; for (int k = sampleIdx; k > 0; k--) { int bestGTguess; @@ -353,65 +353,51 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { double[] likelihoods = g.getLikelihoods().getAsVector(); - if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { - bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); - } - else { - int newIdx = tracebackArray[k][startIdx];; - bestGTguess = startIdx - newIdx; - startIdx = newIdx; - } - - // likelihoods are stored row-wise in lower triangular matrix. IE - // for 2 alleles they have ordering AA,AB,BB - // for 3 alleles they are ordered AA,AB,BB,AC,BC,CC - // Get now alleles corresponding to best index - int kk=0; - boolean done = false; - for (int j=0; j < vc.getNAlleles(); j++) { - for (int i=0; i <= j; i++){ - if (kk++ == bestGTguess) { - if (i==0) - myAlleles.add(vc.getReference()); - else - myAlleles.add(vc.getAlternateAllele(i-1)); - - if (j==0) - myAlleles.add(vc.getReference()); - else - myAlleles.add(vc.getAlternateAllele(j-1)); - done = true; - break; - } - + if (MathUtils.sum(likelihoods) <= SUM_GL_THRESH_NOCALL) { + if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { + bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); } - if (done) - break; + else { + int newIdx = tracebackArray[k][startIdx];; + bestGTguess = startIdx - newIdx; + startIdx = newIdx; + } + + // likelihoods are stored row-wise in lower triangular matrix. IE + // for 2 alleles they have ordering AA,AB,BB + // for 3 alleles they are ordered AA,AB,BB,AC,BC,CC + // Get now alleles corresponding to best index + int kk=0; + boolean done = false; + for (int j=0; j < vc.getNAlleles(); j++) { + for (int i=0; i <= j; i++){ + if (kk++ == bestGTguess) { + if (i==0) + myAlleles.add(vc.getReference()); + else + myAlleles.add(vc.getAlternateAllele(i-1)); + + if (j==0) + myAlleles.add(vc.getReference()); + else + myAlleles.add(vc.getAlternateAllele(j-1)); + done = true; + break; + } + + } + if (done) + break; + } + + final double qual = GenotypeLikelihoods.getQualFromLikelihoods(bestGTguess, likelihoods); + calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); + } else { + final double qual = Genotype.NO_LOG10_PERROR; + calls.add(new Genotype(sample, NO_CALL_ALLELES, qual, null, g.getAttributes(), false)); } - - final double qual = GenotypeLikelihoods.getQualFromLikelihoods(bestGTguess, likelihoods); - //System.out.println(myAlleles.toString()); - calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); } - for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { - if ( !genotype.hasLikelihoods() ) - continue; - Genotype g = GLs.get(genotype.getSampleName()); - - double[] likelihoods = genotype.getLikelihoods().getAsVector(); - - if (MathUtils.sum(likelihoods) <= SUM_GL_THRESH_NOCALL) - continue; // regular likelihoods - - ArrayList myAlleles = new ArrayList(); - - double qual = Genotype.NO_LOG10_PERROR; - myAlleles.add(Allele.NO_CALL); - myAlleles.add(Allele.NO_CALL); - //System.out.println(myAlleles.toString()); - calls.add(new Genotype(genotype.getSampleName(), myAlleles, qual, null, g.getAttributes(), false)); - } return calls; } From 022832bd74f32b8edb0663fc64034fa45265f0f1 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 21 Nov 2011 14:49:47 -0500 Subject: [PATCH 085/113] Very bad use of the == operator with Strings was ensuring that validating GenomeLocs was very inefficient. This fix resulted in a significant speedup for a simple RodWalker. --- .../src/org/broadinstitute/sting/utils/GenomeLocParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java index 8cba183da..4f1df9e7b 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java @@ -87,12 +87,12 @@ public class GenomeLocParser { @Requires("contig != null") public synchronized boolean hasContig(final String contig) { - return lastContig == contig || dict.getSequence(contig) != null; + return contig.equals(lastContig) || dict.getSequence(contig) != null; } @Requires("index >= 0") public synchronized boolean hasContig(final int index) { - return lastIndex == index|| dict.getSequence(index) != null; + return lastIndex == index || dict.getSequence(index) != null; } @Requires("contig != null") From ab2efe3bd38b6d057db1b2588f2f9e3118ac517b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 16:14:40 -0500 Subject: [PATCH 086/113] Reverting bad exact model changes --- .../genotyper/ExactAFCalculationModel.java | 97 +++++++++++-------- 1 file changed, 54 insertions(+), 43 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index 6aa2be419..91f6acd3d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -339,7 +339,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - final GenotypesContext calls = GenotypesContext.create(); + GenotypesContext calls = GenotypesContext.create(); + int startIdx = AFofMaxLikelihood; for (int k = sampleIdx; k > 0; k--) { int bestGTguess; @@ -353,49 +354,59 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { double[] likelihoods = g.getLikelihoods().getAsVector(); - if (MathUtils.sum(likelihoods) <= SUM_GL_THRESH_NOCALL) { - if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { - bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); - } - else { - int newIdx = tracebackArray[k][startIdx];; - bestGTguess = startIdx - newIdx; - startIdx = newIdx; - } - - // likelihoods are stored row-wise in lower triangular matrix. IE - // for 2 alleles they have ordering AA,AB,BB - // for 3 alleles they are ordered AA,AB,BB,AC,BC,CC - // Get now alleles corresponding to best index - int kk=0; - boolean done = false; - for (int j=0; j < vc.getNAlleles(); j++) { - for (int i=0; i <= j; i++){ - if (kk++ == bestGTguess) { - if (i==0) - myAlleles.add(vc.getReference()); - else - myAlleles.add(vc.getAlternateAllele(i-1)); - - if (j==0) - myAlleles.add(vc.getReference()); - else - myAlleles.add(vc.getAlternateAllele(j-1)); - done = true; - break; - } - - } - if (done) - break; - } - - final double qual = GenotypeLikelihoods.getQualFromLikelihoods(bestGTguess, likelihoods); - calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); - } else { - final double qual = Genotype.NO_LOG10_PERROR; - calls.add(new Genotype(sample, NO_CALL_ALLELES, qual, null, g.getAttributes(), false)); + if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { + bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); } + else { + int newIdx = tracebackArray[k][startIdx];; + bestGTguess = startIdx - newIdx; + startIdx = newIdx; + } + + // likelihoods are stored row-wise in lower triangular matrix. IE + // for 2 alleles they have ordering AA,AB,BB + // for 3 alleles they are ordered AA,AB,BB,AC,BC,CC + // Get now alleles corresponding to best index + int kk=0; + boolean done = false; + for (int j=0; j < vc.getNAlleles(); j++) { + for (int i=0; i <= j; i++){ + if (kk++ == bestGTguess) { + if (i==0) + myAlleles.add(vc.getReference()); + else + myAlleles.add(vc.getAlternateAllele(i-1)); + + if (j==0) + myAlleles.add(vc.getReference()); + else + myAlleles.add(vc.getAlternateAllele(j-1)); + done = true; + break; + } + + } + if (done) + break; + } + + final double qual = GenotypeLikelihoods.getQualFromLikelihoods(bestGTguess, likelihoods); + //System.out.println(myAlleles.toString()); + calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); + } + + for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { + if ( !genotype.hasLikelihoods() ) + continue; + Genotype g = GLs.get(genotype.getSampleName()); + + double[] likelihoods = genotype.getLikelihoods().getAsVector(); + + if (MathUtils.sum(likelihoods) <= SUM_GL_THRESH_NOCALL) + continue; // regular likelihoods + + final double qual = Genotype.NO_LOG10_PERROR; + calls.add(new Genotype(g.getSampleName(), NO_CALL_ALLELES, qual, null, g.getAttributes(), false)); } return calls; From 9ea7b70a02640c9f6f19d85b2c50123fd707835a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 16:21:23 -0500 Subject: [PATCH 087/113] Added decode method to LazyGenotypesContext -- AbstractVCFCodec calls this if the samples are not sorted. Previously called getGenotypes() which didn't actually trigger the decode --- .../gatk/walkers/beagle/BeagleOutputToVCFWalker.java | 10 +++++----- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 8 +++++--- .../utils/variantcontext/LazyGenotypesContext.java | 7 +++++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java index 8c6038e7e..f827856be 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/beagle/BeagleOutputToVCFWalker.java @@ -178,8 +178,8 @@ public class BeagleOutputToVCFWalker extends RodWalker { // ignore places where we don't have a variant if ( beagleR2Feature == null || beagleProbsFeature == null || beaglePhasedFeature == null) { - vcfWriter.add(vc_input); - return 1; + vcfWriter.add(vc_input); + return 1; } @@ -249,9 +249,9 @@ public class BeagleOutputToVCFWalker extends RodWalker { Allele bglAlleleA, bglAlleleB; if (alleleA.matches(refString)) - bglAlleleA = Allele.create(alleleA,true); + bglAlleleA = Allele.create(alleleA,true); else - bglAlleleA = Allele.create(alleleA,false); + bglAlleleA = Allele.create(alleleA,false); if (alleleB.matches(refString)) bglAlleleB = Allele.create(alleleB,true); @@ -280,7 +280,7 @@ public class BeagleOutputToVCFWalker extends RodWalker { // deal with numerical errors coming from limited formatting value on Beagle output files if (probWrongGenotype > 1 - MIN_PROB_ERROR) probWrongGenotype = 1 - MIN_PROB_ERROR; - + if (1-probWrongGenotype < noCallThreshold) { // quality is bad: don't call genotype alleles.clear(); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java index 216ee3fb6..7cceaa008 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/AbstractVCFCodec.java @@ -321,6 +321,11 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec { final LazyGenotypesContext.LazyParser lazyParser = new LazyVCFGenotypesParser(alleles, chr, pos); final int nGenotypes = header.getGenotypeSamples().size(); LazyGenotypesContext lazy = new LazyGenotypesContext(lazyParser, parts[8], nGenotypes); + + // did we resort the sample names? If so, we need to load the genotype data + if ( !header.samplesWereAlreadySorted() ) + lazy.decode(); + builder.genotypesNoValidation(lazy); } @@ -332,9 +337,6 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec { generateException(e.getMessage()); } - // did we resort the sample names? If so, we need to load the genotype data - if ( !header.samplesWereAlreadySorted() ) - vc.getGenotypes(); return vc; } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java index 5fbaadfab..574bdc3d0 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -177,4 +177,11 @@ public class LazyGenotypesContext extends GenotypesContext { public Object getUnparsedGenotypeData() { return unparsedGenotypeData; } + + /** + * Force us to decode the genotypes + */ + public void decode() { + buildCache(); + } } From 5ad3dfcd6207cba89c6824099042e0f4665ab811 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 21 Nov 2011 14:50:51 -0500 Subject: [PATCH 088/113] BugFix: byte overflow in SyntheticRead compressed base counts * fixed and added unit test --- public/java/src/org/broadinstitute/sting/utils/MathUtils.java | 4 ++++ .../org/broadinstitute/sting/utils/ReadUtilsUnitTest.java | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/MathUtils.java b/public/java/src/org/broadinstitute/sting/utils/MathUtils.java index 17f458f31..f92d4be78 100644 --- a/public/java/src/org/broadinstitute/sting/utils/MathUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/MathUtils.java @@ -188,6 +188,10 @@ public class MathUtils { return ! Double.isInfinite(val) && ! Double.isNaN(val); } + public static double bound(double value, double minBoundary, double maxBoundary) { + return Math.max(Math.min(value, maxBoundary), minBoundary); + } + public static boolean isBounded(double val, double lower, double upper) { return val >= lower && val <= upper; } diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java index 53368c339..630beaece 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java @@ -16,8 +16,8 @@ public class ReadUtilsUnitTest extends BaseTest { GATKSAMRecord read, reducedRead; final static String BASES = "ACTG"; final static String QUALS = "!+5?"; - final private static byte[] REDUCED_READ_COUNTS = new byte[]{10, 20, 30, 40}; - final private static byte[] REDUCED_READ_COUNTS_TAG = new byte[]{10, 10, 20, 30}; // just the offsets + final private static byte[] REDUCED_READ_COUNTS = new byte[]{10, 20, 30, 40, 1}; + final private static byte[] REDUCED_READ_COUNTS_TAG = new byte[]{10, 10, 20, 30, -9}; // just the offsets @BeforeTest public void init() { From 5443d3634a11f7cc36e8e4d7dfa31e0b271c1fb7 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 21 Nov 2011 19:15:56 -0500 Subject: [PATCH 091/113] Again, fixing the add call when we really mean replace -- Updating MD5s for UG to reflect that what was previously called ./.:.:10:0,0,0 is now just ./. Eric will fix long-standing bug in QD observed from this change -- VFW MD5s restored to their old correct values. There was a bug in my implementation to caused the genotypes to not be parsed from the lazy output even through the header was incorrect. --- .../genotyper/ExactAFCalculationModel.java | 6 +-- .../VariantFiltrationIntegrationTest.java | 37 ++++--------------- .../UnifiedGenotyperIntegrationTest.java | 2 +- 3 files changed, 12 insertions(+), 33 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java index 91f6acd3d..5d0b6f0a7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModel.java @@ -398,15 +398,15 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { if ( !genotype.hasLikelihoods() ) continue; - Genotype g = GLs.get(genotype.getSampleName()); - double[] likelihoods = genotype.getLikelihoods().getAsVector(); + final Genotype g = GLs.get(genotype.getSampleName()); + final double[] likelihoods = genotype.getLikelihoods().getAsVector(); if (MathUtils.sum(likelihoods) <= SUM_GL_THRESH_NOCALL) continue; // regular likelihoods final double qual = Genotype.NO_LOG10_PERROR; - calls.add(new Genotype(g.getSampleName(), NO_CALL_ALLELES, qual, null, g.getAttributes(), false)); + calls.replace(new Genotype(g.getSampleName(), NO_CALL_ALLELES, qual, null, g.getAttributes(), false)); } return calls; diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java index c2348b4a3..2c04cebd4 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/filters/VariantFiltrationIntegrationTest.java @@ -14,12 +14,9 @@ public class VariantFiltrationIntegrationTest extends WalkerTest { @Test public void testNoAction() { - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("b7b7c218e219cd923ce5b6eefc5b7171")); + Arrays.asList("8a105fa5eebdfffe7326bc5b3d8ffd1c")); executeTest("test no action", spec); } @@ -27,73 +24,55 @@ public class VariantFiltrationIntegrationTest extends WalkerTest { public void testClusteredSnps() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -window 10 --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("6d45a19e4066e7de6ff6a61f43ffad2b")); + Arrays.asList("27b13f179bb4920615dff3a32730d845")); executeTest("test clustered SNPs", spec); } @Test public void testMask1() { - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output WalkerTestSpec spec1 = new WalkerTestSpec( baseTestString() + " -maskName foo --mask:VCF3 " + validationDataLocation + "vcfexample2.vcf --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("65b5006bf3ee9d9d08a36d6b854773f2")); + Arrays.asList("578f9e774784c25871678e6464fd212b")); executeTest("test mask all", spec1); } @Test public void testMask2() { - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output WalkerTestSpec spec2 = new WalkerTestSpec( baseTestString() + " -maskName foo --mask:VCF " + validationDataLocation + "vcfMask.vcf --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("a275d36baca81a1ce03dbb528e95a069")); + Arrays.asList("bfa86a674aefca1b13d341cb14ab3c4f")); executeTest("test mask some", spec2); } @Test public void testMask3() { - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output WalkerTestSpec spec3 = new WalkerTestSpec( baseTestString() + " -maskName foo -maskExtend 10 --mask:VCF " + validationDataLocation + "vcfMask.vcf --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("c9489e1c1342817c36ab4f0770609bdb")); + Arrays.asList("5939f80d14b32d88587373532d7b90e5")); executeTest("test mask extend", spec3); } @Test public void testFilter1() { WalkerTestSpec spec = new WalkerTestSpec( - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output baseTestString() + " -filter 'DoC < 20 || FisherStrand > 20.0' -filterName foo --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("327a611bf82c6c4ae77fbb6d06359f9d")); + Arrays.asList("45219dbcfb6f81bba2ea0c35f5bfd368")); executeTest("test filter #1", spec); } @Test public void testFilter2() { - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -filter 'AlleleBalance < 70.0 && FisherStrand == 1.4' -filterName bar --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("7612b3460575402ad78fa4173178bdcc")); + Arrays.asList("c95845e817da7352b9b72bc9794f18fb")); executeTest("test filter #2", spec); } @Test public void testFilterWithSeparateNames() { - // note that this input if slightly malformed, but with the new properly - // only when really needed genotype loading of VCF files we don't actually - // fix the file in the output WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --filterName ABF -filter 'AlleleBalance < 0.7' --filterName FSF -filter 'FisherStrand == 1.4' --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -L 1:10,020,000-10,021,000", 1, - Arrays.asList("dce33441f58b284ac9ab94f8e64b84e3")); + Arrays.asList("b8cdd7f44ff1a395e0a9b06a87e1e530")); executeTest("test filter with separate names #2", spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index fc234ec24..95b5855c1 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -295,7 +295,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec4 = new WalkerTest.WalkerTestSpec( baseCommandIndelsb37 + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2_chr20_100_110K.20101123.indels.sites.vcf -I " + validationDataLocation + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("1e02f57fafaa41db71c531eb25e148e1")); + Arrays.asList("9be28cb208d8b0314d2bc2696e2fd8d4")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } From 29ca24694a9f819e5c709c82149a5b431a22a787 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 22 Nov 2011 08:22:32 -0500 Subject: [PATCH 093/113] UG now encoding NO_CALLs as ./. not ./.:.:4:0,0,0 A few updated UGs integration tests --- .../walkers/genotyper/UnifiedGenotyperIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index 95b5855c1..34e1ad30e 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -29,7 +29,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testMultiSamplePilot1() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -o %s -L 1:10,022,000-10,025,000", 1, - Arrays.asList("f5c8bd653aed02059b9f377833eae5bb")); + Arrays.asList("286f0de92e4ce57986ba861390c6019d")); executeTest("test MultiSample Pilot1", spec); } @@ -45,7 +45,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithAllelesPassedIn2() { WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("030ce4feb4bbcf700caba82a45cc45f2")); + Arrays.asList("d0593483e85a7d815f4c5ee6db284d2a")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } From e484625594321a4607449f9e51e88047e4b46076 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 22 Nov 2011 08:40:48 -0500 Subject: [PATCH 094/113] GenotypesContext now updates cached data for add, set, replace operations when possible -- Involved separately managing the sample -> offset and sample sorted list operations. This should improve performance throughout the system --- .../variantcontext/GenotypesContext.java | 178 +++++++++++++----- .../variantcontext/LazyGenotypesContext.java | 58 +++--- .../variantcontext/VariantContextUtils.java | 8 +- .../GenotypesContextUnitTest.java | 7 +- 4 files changed, 173 insertions(+), 78 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 8d28ba18c..248fdad9d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -52,9 +52,6 @@ public class GenotypesContext implements List { */ Map sampleNameToOffset = null; - /** if true, then we need to reinitialize sampleNamesInOrder and sampleNameToOffset before we use them /*/ - boolean cacheIsInvalid = true; - /** * An ArrayList of genotypes contained in this context * @@ -95,7 +92,6 @@ public class GenotypesContext implements List { protected GenotypesContext(final ArrayList genotypes) { this.notToBeDirectlyAccessedGenotypes = genotypes; this.sampleNameToOffset = null; - this.cacheIsInvalid = true; } /** @@ -120,7 +116,6 @@ public class GenotypesContext implements List { this.notToBeDirectlyAccessedGenotypes = genotypes; this.sampleNameToOffset = sampleNameToOffset; this.sampleNamesInOrder = sampleNamesInOrder; - this.cacheIsInvalid = false; } // --------------------------------------------------------------------------- @@ -246,33 +241,46 @@ public class GenotypesContext implements List { // // --------------------------------------------------------------------------- - @Ensures({"cacheIsInvalid == true"}) - protected void invalidateCaches() { - cacheIsInvalid = true; - sampleNamesInOrder = null; + @Ensures({"sampleNameToOffset == null"}) + protected void invalidateSampleNameMap() { sampleNameToOffset = null; } - @Ensures({"cacheIsInvalid == false", - "sampleNamesInOrder != null", - "sampleNameToOffset != null", - "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNamesInOrder)", - "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNameToOffset.keySet())"}) - protected void buildCache() { - if ( cacheIsInvalid ) { - cacheIsInvalid = false; + @Ensures({"sampleNamesInOrder == null"}) + protected void invalidateSampleOrdering() { + sampleNamesInOrder = null; + } + + @Ensures({"sampleNamesInOrder != null", + "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNamesInOrder)"}) + protected void ensureSampleOrdering() { + if ( sampleNamesInOrder == null ) { sampleNamesInOrder = new ArrayList(size()); - sampleNameToOffset = new HashMap(size()); for ( int i = 0; i < size(); i++ ) { - final Genotype g = getGenotypes().get(i); - sampleNamesInOrder.add(g.getSampleName()); - sampleNameToOffset.put(g.getSampleName(), i); + sampleNamesInOrder.add(getGenotypes().get(i).getSampleName()); } Collections.sort(sampleNamesInOrder); } } + @Ensures({"sampleNameToOffset != null", + "sameSamples(notToBeDirectlyAccessedGenotypes, sampleNameToOffset.keySet())"}) + protected void ensureSampleNameMap() { + if ( sampleNameToOffset == null ) { + sampleNameToOffset = new HashMap(size()); + + for ( int i = 0; i < size(); i++ ) { + sampleNameToOffset.put(getGenotypes().get(i).getSampleName(), i); + } + } + } + + // for testing purposes + protected void ensureAll() { + ensureSampleNameMap(); + ensureSampleOrdering(); + } // --------------------------------------------------------------------------- // @@ -287,7 +295,8 @@ public class GenotypesContext implements List { @Override public void clear() { checkImmutability(); - invalidateCaches(); + invalidateSampleNameMap(); + invalidateSampleOrdering(); getGenotypes().clear(); } @@ -301,21 +310,43 @@ public class GenotypesContext implements List { return getGenotypes().isEmpty(); } + /** + * Adds a single genotype to this context. + * + * There are many constraints on this input, and important + * impacts on the performance of other functions provided by this + * context. + * + * First, the sample name of genotype must be unique within this + * context. However, this is not enforced in the code itself, through + * you will invalid the contract on this context if you add duplicate + * samples and are running with CoFoJa enabled. + * + * Second, adding genotype also updates the sample name -> index map, + * so add() followed by containsSample and related function is an efficient + * series of operations. + * + * Third, adding the genotype invalidates the sorted list of sample names, to + * add() followed by any of the SampleNamesInOrder operations is inefficient, as + * each SampleNamesInOrder must rebuild the sorted list of sample names at + * an O(n log n) cost. + * + * @param genotype + * @return + */ @Override @Requires({"genotype != null", "get(genotype.getSampleName()) == null"}) @Ensures("noDups(getGenotypes())") public boolean add(final Genotype genotype) { checkImmutability(); - invalidateCaches(); - return getGenotypes().add(genotype); - } + invalidateSampleOrdering(); - @Requires({"genotype != null", "! containsAny(Arrays.asList(genotype))"}) - @Ensures("noDups(getGenotypes())") - public boolean add(final Genotype ... genotype) { - checkImmutability(); - invalidateCaches(); - return getGenotypes().addAll(Arrays.asList(genotype)); + if ( sampleNameToOffset != null ) { + // update the name map by adding entries + sampleNameToOffset.put(genotype.getSampleName(), size()); + } + + return getGenotypes().add(genotype); } @Override @@ -325,12 +356,30 @@ public class GenotypesContext implements List { throw new UnsupportedOperationException(); } + /** + * Adds all of the genotypes to this context + * + * See {@link #add(Genotype)} for important information about this functions + * constraints and performance costs + * + * @param genotypes + * @return + */ @Override @Requires("! containsAny(genotypes)") @Ensures("noDups(getGenotypes())") public boolean addAll(final Collection genotypes) { checkImmutability(); - invalidateCaches(); + invalidateSampleOrdering(); + + if ( sampleNameToOffset != null ) { + // update the name map by adding entries + int pos = size(); + for ( final Genotype g : genotypes ) { + sampleNameToOffset.put(g.getSampleName(), pos++); + } + } + return getGenotypes().addAll(genotypes); } @@ -362,13 +411,12 @@ public class GenotypesContext implements List { } public Genotype get(final String sampleName) { - buildCache(); Integer offset = getSampleI(sampleName); return offset == null ? null : getGenotypes().get(offset); } private Integer getSampleI(final String sampleName) { - buildCache(); + ensureSampleNameMap(); return sampleNameToOffset.get(sampleName); } @@ -401,31 +449,58 @@ public class GenotypesContext implements List { // return genotypes.listIterator(i); } + /** + * Note that remove requires us to invalidate our sample -> index + * cache. The loop: + * + * GenotypesContext gc = ... + * for ( sample in samples ) + * if ( gc.containsSample(sample) ) + * gc.remove(sample) + * + * is extremely inefficient, as each call to remove invalidates the cache + * and containsSample requires us to rebuild it, an O(n) operation. + * + * If you must remove many samples from the GC, use either removeAll or retainAll + * to avoid this O(n * m) operation. + * + * @param i + * @return + */ @Override public Genotype remove(final int i) { checkImmutability(); - invalidateCaches(); + invalidateSampleNameMap(); + invalidateSampleOrdering(); return getGenotypes().remove(i); } + /** + * See for important warning {@link this.remove(Integer)} + * @param o + * @return + */ @Override public boolean remove(final Object o) { checkImmutability(); - invalidateCaches(); + invalidateSampleNameMap(); + invalidateSampleOrdering(); return getGenotypes().remove(o); } @Override public boolean removeAll(final Collection objects) { checkImmutability(); - invalidateCaches(); + invalidateSampleNameMap(); + invalidateSampleOrdering(); return getGenotypes().removeAll(objects); } @Override public boolean retainAll(final Collection objects) { checkImmutability(); - invalidateCaches(); + invalidateSampleNameMap(); + invalidateSampleOrdering(); return getGenotypes().retainAll(objects); } @@ -433,14 +508,28 @@ public class GenotypesContext implements List { @Ensures("noDups(getGenotypes())") public Genotype set(final int i, final Genotype genotype) { checkImmutability(); - invalidateCaches(); - return getGenotypes().set(i, genotype); + final Genotype prev = getGenotypes().set(i, genotype); + + invalidateSampleOrdering(); + if ( sampleNameToOffset != null ) { + // update the name map by removing the old entry and replacing it with the new one + sampleNameToOffset.remove(prev.getSampleName()); + sampleNameToOffset.put(genotype.getSampleName(), i); + } + + return prev; } /** * Replaces the genotype in this context -- note for efficiency * reasons we do not add the genotype if it's not present. The * return value will be null indicating this happened. + * + * Note this operation is preserves the map cache Sample -> Offset but + * invalidates the sorted list of samples. Using replace within a loop + * containing any of the SampleNameInOrder operation requires an O(n log n) + * resorting after each replace operation. + * * @param genotype a non null genotype to bind in this context * @return null if genotype was not added, otherwise returns the previous genotype */ @@ -451,7 +540,7 @@ public class GenotypesContext implements List { if ( offset == null ) return null; else - return getGenotypes().set(offset, genotype); + return set(offset, genotype); } @Override @@ -523,7 +612,7 @@ public class GenotypesContext implements List { */ @Ensures("result != null") public Set getSampleNames() { - buildCache(); + ensureSampleNameMap(); return sampleNameToOffset.keySet(); } @@ -532,19 +621,18 @@ public class GenotypesContext implements List { */ @Ensures("result != null") public List getSampleNamesOrderedByName() { - buildCache(); + ensureSampleOrdering(); return sampleNamesInOrder; } @Requires("sample != null") public boolean containsSample(final String sample) { - buildCache(); + ensureSampleNameMap(); return sampleNameToOffset.containsKey(sample); } @Requires("samples != null") public boolean containsSamples(final Collection samples) { - buildCache(); return getSampleNames().containsAll(samples); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java index 574bdc3d0..ce0422352 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/LazyGenotypesContext.java @@ -81,8 +81,8 @@ public class LazyGenotypesContext extends GenotypesContext { final List sampleNamesInOrder; @Requires({"genotypes != null", "sampleNamesInOrder != null", "sampleNameToOffset != null", - "sameSamples(genotypes, sampleNamesInOrder)", - "sameSamples(genotypes, sampleNameToOffset.keySet())"}) + "sameSamples(genotypes, sampleNamesInOrder)", + "sameSamples(genotypes, sampleNameToOffset.keySet())"}) public LazyData(final ArrayList genotypes, final List sampleNamesInOrder, final Map sampleNameToOffset) { @@ -119,13 +119,20 @@ public class LazyGenotypesContext extends GenotypesContext { @Override @Ensures("result != null") protected ArrayList getGenotypes() { + decode(); + return notToBeDirectlyAccessedGenotypes; + } + + /** + * Force us to decode the genotypes, if not already done + */ + public void decode() { if ( ! loaded ) { //System.out.printf("Loading genotypes... %s:%d%n", contig, start); LazyData parsed = parser.parse(unparsedGenotypeData); notToBeDirectlyAccessedGenotypes = parsed.genotypes; sampleNamesInOrder = parsed.sampleNamesInOrder; sampleNameToOffset = parsed.sampleNameToOffset; - cacheIsInvalid = false; // these values build the cache loaded = true; unparsedGenotypeData = null; // don't hold the unparsed data any longer @@ -133,31 +140,43 @@ public class LazyGenotypesContext extends GenotypesContext { // That said, it's not such an important routine -- it's just checking that the genotypes // are well formed w.r.t. the alleles list, but this will be enforced within the VCFCodec } - - return notToBeDirectlyAccessedGenotypes; } /** - * Overrides the buildCache functionality. If the data hasn't been loaded + * Overrides the ensure* functionality. If the data hasn't been loaded * yet and we want to build the cache, just decode it and we're done. If we've * already decoded the data, though, go through the super class */ @Override - protected synchronized void buildCache() { - if ( cacheIsInvalid ) { - if ( ! loaded ) { - getGenotypes(); // will load up all of the necessary data - } else { - super.buildCache(); - } + protected synchronized void ensureSampleNameMap() { + if ( ! loaded ) { + decode(); // will load up all of the necessary data + } else { + super.ensureSampleNameMap(); } } @Override - protected void invalidateCaches() { + protected synchronized void ensureSampleOrdering() { + if ( ! loaded ) { + decode(); // will load up all of the necessary data + } else { + super.ensureSampleOrdering(); + } + } + + @Override + protected void invalidateSampleNameMap() { // if the cache is invalidated, and we haven't loaded our data yet, do so - if ( ! loaded ) getGenotypes(); - super.invalidateCaches(); + if ( ! loaded ) decode(); + super.invalidateSampleNameMap(); + } + + @Override + protected void invalidateSampleOrdering() { + // if the cache is invalidated, and we haven't loaded our data yet, do so + if ( ! loaded ) decode(); + super.invalidateSampleOrdering(); } @Override @@ -177,11 +196,4 @@ public class LazyGenotypesContext extends GenotypesContext { public Object getUnparsedGenotypeData() { return unparsedGenotypeData; } - - /** - * Force us to decode the genotypes - */ - public void decode() { - buildCache(); - } } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 21a371e2f..05e768ea7 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -532,7 +532,6 @@ public class VariantContextUtils { final Map attributesWithMaxAC = new TreeMap(); double log10PError = 1; VariantContext vcWithMaxAC = null; - Set addedSamples = new HashSet(first.getNSamples()); GenotypesContext genotypes = GenotypesContext.create(); // counting the number of filtered and variant VCs @@ -557,7 +556,7 @@ public class VariantContextUtils { alleles.addAll(alleleMapping.values()); - mergeGenotypes(genotypes, addedSamples, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); + mergeGenotypes(genotypes, vc, alleleMapping, genotypeMergeOptions == GenotypeMergeType.UNIQUIFY); log10PError = Math.min(log10PError, vc.isVariant() ? vc.getLog10PError() : 1); @@ -963,10 +962,10 @@ public class VariantContextUtils { } } - private static void mergeGenotypes(GenotypesContext mergedGenotypes, Set addedSamples, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { + private static void mergeGenotypes(GenotypesContext mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { for ( Genotype g : oneVC.getGenotypes() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); - if ( ! addedSamples.contains(name) ) { + if ( mergedGenotypes.containsSample(name) ) { // only add if the name is new Genotype newG = g; @@ -976,7 +975,6 @@ public class VariantContextUtils { } mergedGenotypes.add(newG); - addedSamples.add(name); } } } diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java index afb9336a0..ee0a5dfe0 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypesContextUnitTest.java @@ -88,7 +88,8 @@ public class GenotypesContextUnitTest extends BaseTest { @Override public LazyGenotypesContext.LazyData parse(final Object data) { GenotypesContext gc = GenotypesContext.copy((List)data); - gc.buildCache(); + gc.ensureSampleNameMap(); + gc.ensureSampleOrdering(); return new LazyGenotypesContext.LazyData(gc.notToBeDirectlyAccessedGenotypes, gc.sampleNamesInOrder, gc.sampleNameToOffset); } @@ -234,10 +235,6 @@ public class GenotypesContextUnitTest extends BaseTest { gc.add(add2); testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); - gc = cfg.makeContext(); - gc.add(add1, add2); - testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); - gc = cfg.makeContext(); gc.addAll(Arrays.asList(add1, add2)); testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2)); From 708731037390f175e53951ddb97cf2897e89fa25 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 22 Nov 2011 10:16:36 -0500 Subject: [PATCH 095/113] Embarassing bug fixed --- .../sting/utils/variantcontext/VariantContextUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 05e768ea7..0d3f7fae7 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -965,7 +965,7 @@ public class VariantContextUtils { private static void mergeGenotypes(GenotypesContext mergedGenotypes, VariantContext oneVC, AlleleMapper alleleMapping, boolean uniqifySamples) { for ( Genotype g : oneVC.getGenotypes() ) { String name = mergedSampleName(oneVC.getSource(), g.getSampleName(), uniqifySamples); - if ( mergedGenotypes.containsSample(name) ) { + if ( ! mergedGenotypes.containsSample(name) ) { // only add if the name is new Genotype newG = g; From 5821c11fad7b198a929c80fe4d60cb437a0eceb4 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 22 Nov 2011 10:50:22 -0500 Subject: [PATCH 097/113] For BAM and Reviewed errors we now check the error message to see if it's actually a 'too many open files' problem and, if so, we generate a User Error instead. --- .../sting/commandline/CommandLineProgram.java | 2 +- .../org/broadinstitute/sting/gatk/CommandLineGATK.java | 10 ++++++++-- .../sting/utils/exceptions/UserException.java | 6 ++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java index b0b57f7fc..9e1be5bca 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java +++ b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java @@ -392,7 +392,7 @@ public abstract class CommandLineProgram { /** * used to indicate an error occured * - * @param e the exception occured + * @param t the exception that occurred */ public static void exitSystemWithError(Throwable t) { exitSystemWithError(t.getMessage(), t); diff --git a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java index d3db35c07..b8b961119 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java +++ b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java @@ -30,7 +30,6 @@ import org.broadinstitute.sting.commandline.Argument; import org.broadinstitute.sting.commandline.ArgumentCollection; import org.broadinstitute.sting.commandline.CommandLineProgram; import org.broadinstitute.sting.gatk.arguments.GATKArgumentCollection; -import org.broadinstitute.sting.gatk.filters.ReadFilter; import org.broadinstitute.sting.gatk.refdata.tracks.FeatureManager; import org.broadinstitute.sting.gatk.walkers.Attribution; import org.broadinstitute.sting.gatk.walkers.Walker; @@ -97,13 +96,20 @@ public class CommandLineGATK extends CommandLineExecutable { // lazy loaded, so they aren't caught elsewhere and made into User Exceptions exitSystemWithUserError(e); } catch (net.sf.samtools.SAMException e) { - // Let's try this out and see how it is received by our users + checkForTooManyOpenFilesProblem(e.getMessage()); exitSystemWithSamError(e); } catch (Throwable t) { + checkForTooManyOpenFilesProblem(t.getMessage()); exitSystemWithError(t); } } + private static void checkForTooManyOpenFilesProblem(String message) { + // Special case the "Too many open files" error because it's a common User Error for which we know what to do + if ( message.indexOf("Too many open files") != -1 ) + exitSystemWithUserError(new UserException.TooManyOpenFiles()); + } + /** * Creates the a short blurb about the GATK, copyright info, and where to get documentation. * diff --git a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java index a208d2dc0..c599d4759 100755 --- a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java +++ b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java @@ -100,6 +100,12 @@ public class UserException extends ReviewedStingException { } } + public static class TooManyOpenFiles extends UserException { + public TooManyOpenFiles() { + super(String.format("There was a failure because there are too many files open concurrently; your system's open file handle limit is too small. See the unix ulimit command to adjust this limit")); + } + } + public static class ErrorWritingBamFile extends UserException { public ErrorWritingBamFile(String message) { super(String.format("An error occurred when trying to write the BAM file. Usually this happens when there is not enough space in the directory to which the data is being written (generally the temp directory) or when your system's open file handle limit is too small. To tell Java to use a bigger/better file system use -Djava.io.tmpdir=X on the command line. The exact error was %s", message)); From 32a77a8a5604eeda7a23a9350040a615b6755d72 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Tue, 22 Nov 2011 13:57:24 -0500 Subject: [PATCH 098/113] Prevent out of bound error in case read span > reference context + indel length. Can happen in RNAseq reads with long N CIGAR operators in the middle. --- .../indels/PairHMMIndelErrorModel.java | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java index 319f41d53..abd933ada 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java @@ -555,41 +555,48 @@ public class PairHMMIndelErrorModel { // cut haplotype bases long indStart = start - haplotype.getStartPosition(); long indStop = stop - haplotype.getStartPosition(); - - final byte[] haplotypeBases = Arrays.copyOfRange(haplotype.getBasesAsBytes(), - (int)indStart, (int)indStop); - double readLikelihood; - if (matchMetricArray == null) { - final int X_METRIC_LENGTH = readBases.length+1; - final int Y_METRIC_LENGTH = haplotypeBases.length+1; - matchMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - XMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - YMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - } - final double[] currentContextGOP = Arrays.copyOfRange(gapOpenProbabilityMap.get(a), (int)indStart, (int)indStop); - final double[] currentContextGCP = Arrays.copyOfRange(gapContProbabilityMap.get(a), (int)indStart, (int)indStop); - if (previousHaplotypeSeen == null) - startIdx = 0; - else { - final int s1 = computeFirstDifferingPosition(haplotypeBases, previousHaplotypeSeen); - final int s2 = computeFirstDifferingPosition(currentContextGOP, previousGOP); - final int s3 = computeFirstDifferingPosition(currentContextGCP, previousGCP); - startIdx = Math.min(Math.min(s1, s2), s3); - } - previousHaplotypeSeen = haplotypeBases.clone(); - previousGOP = currentContextGOP.clone(); - previousGCP = currentContextGCP.clone(); + if (indStart < 0 || indStop >= haplotype.getBasesAsBytes().length) { + // read spanned more than allowed reference context: we currently can't deal with this + readLikelihood =0; + } else + { + final byte[] haplotypeBases = Arrays.copyOfRange(haplotype.getBasesAsBytes(), + (int)indStart, (int)indStop); + + if (matchMetricArray == null) { + final int X_METRIC_LENGTH = readBases.length+1; + final int Y_METRIC_LENGTH = haplotypeBases.length+1; + + matchMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + XMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + YMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + } + final double[] currentContextGOP = Arrays.copyOfRange(gapOpenProbabilityMap.get(a), (int)indStart, (int)indStop); + final double[] currentContextGCP = Arrays.copyOfRange(gapContProbabilityMap.get(a), (int)indStart, (int)indStop); + if (previousHaplotypeSeen == null) + startIdx = 0; + else { + final int s1 = computeFirstDifferingPosition(haplotypeBases, previousHaplotypeSeen); + final int s2 = computeFirstDifferingPosition(currentContextGOP, previousGOP); + final int s3 = computeFirstDifferingPosition(currentContextGCP, previousGCP); + startIdx = Math.min(Math.min(s1, s2), s3); + } + previousHaplotypeSeen = haplotypeBases.clone(); + previousGOP = currentContextGOP.clone(); + previousGCP = currentContextGCP.clone(); - readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, - currentContextGOP, currentContextGCP, startIdx, matchMetricArray, XMetricArray, YMetricArray); - if (DEBUG) { - System.out.println("H:"+new String(haplotypeBases)); - System.out.println("R:"+new String(readBases)); - System.out.format("L:%4.2f\n",readLikelihood); - System.out.format("StPos:%d\n", startIdx); + readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, + currentContextGOP, currentContextGCP, startIdx, matchMetricArray, XMetricArray, YMetricArray); + + if (DEBUG) { + System.out.println("H:"+new String(haplotypeBases)); + System.out.println("R:"+new String(readBases)); + System.out.format("L:%4.2f\n",readLikelihood); + System.out.format("StPos:%d\n", startIdx); + } } readEl.put(a,readLikelihood); readLikelihoods[readIdx][j++] = readLikelihood; From a3aef8fa53c0c66b20023e4f448a6c3c18911eaa Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 22 Nov 2011 17:19:30 -0500 Subject: [PATCH 102/113] Final performance optimization for GenotypesContext --- .../varianteval/util/VariantEvalUtils.java | 2 +- .../variantcontext/GenotypesContext.java | 28 ++++++------------- .../utils/variantcontext/VariantContext.java | 8 ++++-- .../variantcontext/VariantContextUtils.java | 1 + 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index aa246b58d..b319407d1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -267,7 +267,7 @@ public class VariantEvalUtils { * @return a new VariantContext with just the requested sample */ public VariantContext getSubsetOfVariantContext(VariantContext vc, String sampleName) { - return getSubsetOfVariantContext(vc, new HashSet(Arrays.asList(sampleName))); + return getSubsetOfVariantContext(vc, Collections.singleton(sampleName)); } /** diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java index 248fdad9d..845c65c9c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -646,28 +646,18 @@ public class GenotypesContext implements List { */ @Requires("samples != null") @Ensures("result != null") - public GenotypesContext subsetToSamples( final Collection samples ) { - return subsetToSamples(new HashSet(samples)); - } - - /** - * {@link #subsetToSamples(java.util.Collection)} - * @param samples - * @return - */ - @Requires("samples != null") - @Ensures("result != null") public GenotypesContext subsetToSamples( final Set samples ) { - if ( samples.size() == size() ) + final int nSamples = samples.size(); + final int nGenotypes = size(); + + if ( nSamples == nGenotypes ) return this; - else if ( samples.isEmpty() ) + else if ( nSamples == 0 ) return NO_GENOTYPES; - else { - GenotypesContext subset = create(samples.size()); - for ( final Genotype g : getGenotypes() ) { - if ( samples.contains(g.getSampleName()) ) { - subset.add(g); - } + else { // nGenotypes < nSamples + final GenotypesContext subset = create(samples.size()); + for ( final String sample : samples ) { + subset.add(get(sample)); } return subset; } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 331ca97d3..247e412dd 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -726,19 +726,21 @@ public class VariantContext implements Feature { // to enable tribble intergrati * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ public GenotypesContext getGenotypes(String sampleName) { - return getGenotypes(Arrays.asList(sampleName)); + return getGenotypes(Collections.singleton(sampleName)); } /** * Returns a map from sampleName -> Genotype for each sampleName in sampleNames. Returns a map * for consistency with the multi-get function. * + * For testing convenience only + * * @param sampleNames a unique list of sample names * @return * @throws IllegalArgumentException if sampleName isn't bound to a genotype */ - public GenotypesContext getGenotypes(Collection sampleNames) { - return getGenotypes().subsetToSamples(sampleNames); + protected GenotypesContext getGenotypes(Collection sampleNames) { + return getGenotypes().subsetToSamples(new HashSet(sampleNames)); } public GenotypesContext getGenotypes(Set sampleNames) { diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java index 0d3f7fae7..91a018c4e 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -86,6 +86,7 @@ public class VariantContextUtils { for ( Allele allele : vc.getAlternateAlleles() ) { int altChromosomes = vc.getCalledChrCount(allele); alleleCounts.add(altChromosomes); + // todo -- this is a performance problem String freq = String.format(makePrecisionFormatStringFromDenominatorValue(totalChromosomes), ((double)altChromosomes / totalChromosomes)); alleleFreqs.add(freq); } From 75d93e6335baedf2b33de0ba37978830791f3d10 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Tue, 22 Nov 2011 22:46:12 -0500 Subject: [PATCH 103/113] Another corner condition fix: skip likelihood computation in case we cut so many bases there's no haplotype or read left --- .../sting/gatk/walkers/indels/PairHMMIndelErrorModel.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java index abd933ada..6b6a7a82e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java @@ -556,8 +556,11 @@ public class PairHMMIndelErrorModel { long indStart = start - haplotype.getStartPosition(); long indStop = stop - haplotype.getStartPosition(); double readLikelihood; + if (DEBUG) + System.out.format("indStart: %d indStop: %d WinStart:%d WinStop:%d start: %d stop: %d readLength: %d C:%s\n", + indStart, indStop, ref.getWindow().getStart(), ref.getWindow().getStop(), start, stop, read.getReadLength(), read.getCigar().toString()); - if (indStart < 0 || indStop >= haplotype.getBasesAsBytes().length) { + if (indStart < 0 || indStop >= haplotype.getBasesAsBytes().length || indStart > indStop) { // read spanned more than allowed reference context: we currently can't deal with this readLikelihood =0; } else From 32adbd614f9dc39b09fb3aa3fd9a2e910e3b4e24 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Tue, 22 Nov 2011 22:48:46 -0500 Subject: [PATCH 104/113] Solve merge conflict --- .../sting/gatk/walkers/indels/PairHMMIndelErrorModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java index 95633c222..09968f47e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/PairHMMIndelErrorModel.java @@ -561,7 +561,7 @@ public class PairHMMIndelErrorModel { System.out.format("indStart: %d indStop: %d WinStart:%d WinStop:%d start: %d stop: %d readLength: %d C:%s\n", indStart, indStop, ref.getWindow().getStart(), ref.getWindow().getStop(), start, stop, read.getReadLength(), read.getCigar().toString()); - if (indStart < 0 || indStop >= haplotype.getBasesAsBytes().length || indStart > indStop) { + if (indStart < 0 || indStop >= haplotype.getBases().length || indStart > indStop) { // read spanned more than allowed reference context: we currently can't deal with this readLikelihood =0; } else From 6c2555885c271bf8a1c4ff97da5db3c18fecb242 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 23 Nov 2011 08:34:05 -0500 Subject: [PATCH 105/113] Caching getSimpleName() in VariantEval is a big performance improvement -- Removed the SimpleMetricsByAC table, as one should just use the AlleleCount Stratefication and the upcoming VariantSummary table --- .../varianteval/VariantEvalWalker.java | 17 +- .../evaluators/SimpleMetricsByAC.java | 194 ------------------ .../stratifications/VariantStratifier.java | 15 +- .../util/NewEvaluationContext.java | 2 +- .../walkers/varianteval/util/StateKey.java | 23 +-- .../varianteval/util/VariantEvalUtils.java | 6 +- 6 files changed, 34 insertions(+), 223 deletions(-) delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java index 0cc271b59..558d9c6bf 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java @@ -265,9 +265,9 @@ public class VariantEvalWalker extends RodWalker implements Tr stratificationObjects = variantEvalUtils.initializeStratificationObjects(this, NO_STANDARD_STRATIFICATIONS, STRATIFICATIONS_TO_USE); Set> evaluationObjects = variantEvalUtils.initializeEvaluationObjects(NO_STANDARD_MODULES, MODULES_TO_USE); for ( VariantStratifier vs : getStratificationObjects() ) { - if ( vs.getClass().getSimpleName().equals("Filter") ) + if ( vs.getName().equals("Filter") ) byFilterIsEnabled = true; - else if ( vs.getClass().getSimpleName().equals("Sample") ) + else if ( vs.getName().equals("Sample") ) perSampleIsEnabled = true; } @@ -458,9 +458,7 @@ public class VariantEvalWalker extends RodWalker implements Tr table.addColumn(subTableName, subTableName); for ( VariantStratifier vs : stratificationObjects ) { - String columnName = vs.getClass().getSimpleName(); - - table.addColumn(columnName, "unknown"); + table.addColumn(vs.getName(), "unknown"); } table.addColumn("row", "unknown"); @@ -484,9 +482,8 @@ public class VariantEvalWalker extends RodWalker implements Tr String r = (String) t.getRowKeys()[row]; for ( VariantStratifier vs : stratificationObjects ) { - String columnName = vs.getClass().getSimpleName(); - - table.set(stateKey.toString() + r, columnName, stateKey.get(vs.getClass().getSimpleName())); + final String columnName = vs.getName(); + table.set(stateKey.toString() + r, columnName, stateKey.get(columnName)); } for (int col = 0; col < t.getColumnKeys().length; col++) { @@ -507,9 +504,9 @@ public class VariantEvalWalker extends RodWalker implements Tr GATKReportTable table = report.getTable(ve.getClass().getSimpleName()); for ( VariantStratifier vs : stratificationObjects ) { - String columnName = vs.getClass().getSimpleName(); + String columnName = vs.getName(); - table.set(stateKey.toString(), columnName, stateKey.get(vs.getClass().getSimpleName())); + table.set(stateKey.toString(), columnName, stateKey.get(vs.getName())); } table.set(stateKey.toString(), field.getName(), field.get(ve)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java deleted file mode 100755 index 27e8e7c86..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/SimpleMetricsByAC.java +++ /dev/null @@ -1,194 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.varianteval.evaluators; - -import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; -import org.broadinstitute.sting.gatk.walkers.varianteval.VariantEvalWalker; -import org.broadinstitute.sting.gatk.walkers.varianteval.stratifications.Degeneracy; -import org.broadinstitute.sting.gatk.walkers.varianteval.stratifications.Sample; -import org.broadinstitute.sting.gatk.walkers.varianteval.util.Analysis; -import org.broadinstitute.sting.gatk.walkers.varianteval.util.DataPoint; -import org.broadinstitute.sting.gatk.walkers.varianteval.util.StateKey; -import org.broadinstitute.sting.gatk.walkers.varianteval.util.TableType; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; - -import java.util.ArrayList; - -/* - * Copyright (c) 2010 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @author depristo - * @since Apr 11, 2010 - */ - -@Analysis(name = "Quality Metrics by allele count", description = "Shows various stats binned by allele count") -public class SimpleMetricsByAC extends VariantEvaluator implements StandardEval { - // a mapping from quality score histogram bin to Ti/Tv ratio - @DataPoint(description = "TiTv by allele count") - MetricsByAc metrics = null; - - private final static Object[] METRIC_COLUMNS = {"AC", "nTi", "nTv", "n", "TiTv"}; - private int numSamples; - - class MetricsAtAC { - public int ac = -1, nTi = 0, nTv = 0; - - public MetricsAtAC(int ac) { this.ac = ac; } - - public void update(VariantContext eval) { - if ( VariantContextUtils.isTransition(eval) ) - nTi++; - else - nTv++; - } - - // corresponding to METRIC_COLUMNS - public String getColumn(int i) { - switch (i) { - case 0: return String.valueOf(ac); - case 1: return String.valueOf(nTi); - case 2: return String.valueOf(nTv); - case 3: return String.valueOf(nTi + nTv); - case 4: return String.valueOf(ratio(nTi, nTv)); - default: - throw new ReviewedStingException("Unexpected column " + i); - } - } - } - - class MetricsByAc implements TableType { - ArrayList metrics = new ArrayList(); - Object[] rows = null; - - public MetricsByAc( int nchromosomes ) { - rows = new Object[nchromosomes+1]; - metrics = new ArrayList(nchromosomes+1); - for ( int i = 0; i < nchromosomes + 1; i++ ) { - metrics.add(new MetricsAtAC(i)); - rows[i] = "ac" + i; - } - } - - public Object[] getRowKeys() { - return rows; - } - - public Object[] getColumnKeys() { - return METRIC_COLUMNS; - } - - public String getName() { - return "MetricsByAc"; - } - - public String getCell(int ac, int y) { - return metrics.get(ac).getColumn(y); - } - - public String toString() { - return ""; - } - - public void incrValue( VariantContext eval ) { - int ac = -1; - - if ( eval.hasGenotypes() ) - ac = eval.getCalledChrCount(eval.getAlternateAllele(0)); - else if ( eval.hasAttribute("AC") ) { - ac = eval.getAttributeAsInt("AC", -1); - } - - if ( ac != -1 ) { - metrics.get(ac).update(eval); - } - } - } - - public void initialize(VariantEvalWalker walker) { - numSamples = walker.getNumSamples(); - metrics = new MetricsByAc(2*numSamples); - } - - public String getName() { - return "SimpleMetricsByAC"; - } - - public int getComparisonOrder() { - return 1; // we only need to see each eval track - } - - public boolean enabled() { - return true; - } - - public String toString() { - return getName(); - } - - public String update1(VariantContext eval, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if (numSamples == 0) { - return null; - } - - final String interesting = null; - - if (eval != null) { - if ( metrics == null ) { - int nSamples = numSamples; - - if ( nSamples != -1 ) { - metrics = new MetricsByAc(2 * nSamples); - } - } - - if ( eval.isSNP() && eval.isBiallelic() && eval.isPolymorphicInSamples() && metrics != null ) { - metrics.incrValue(eval); - } - } - - return interesting; // This module doesn't capture any interesting sites, so return null - } - - @Override - public boolean stateIsApplicable(StateKey stateKey) { - String sampleClassName = Sample.class.getSimpleName(); - String degeneracyClassName = Degeneracy.class.getSimpleName(); - - //return !(stateKey.containsKey(sampleClassName) && !stateKey.get(sampleClassName).equalsIgnoreCase("all")); - - if (stateKey.containsKey(sampleClassName) && !stateKey.get(sampleClassName).equalsIgnoreCase("all")) { - return false; - } - - if (stateKey.containsKey(degeneracyClassName) && !stateKey.get(degeneracyClassName).equalsIgnoreCase("all")) { - return false; - } - - return true; - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/VariantStratifier.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/VariantStratifier.java index 5cae2fb15..119a1b83f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/VariantStratifier.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/VariantStratifier.java @@ -9,10 +9,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public abstract class VariantStratifier implements Comparable { +public abstract class VariantStratifier implements Comparable { private VariantEvalWalker variantEvalWalker; + final private String name; protected ArrayList states = new ArrayList(); + protected VariantStratifier() { + name = this.getClass().getSimpleName(); + } + /** * @return a reference to the parent VariantEvalWalker running this stratification */ @@ -34,8 +39,12 @@ public abstract class VariantStratifier implements Comparable { return null; } - public int compareTo(Object o1) { - return this.getClass().getSimpleName().compareTo(o1.getClass().getSimpleName()); + public int compareTo(VariantStratifier o1) { + return this.getName().compareTo(o1.getName()); + } + + public final String getName() { + return name; } public ArrayList getAllStates() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/NewEvaluationContext.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/NewEvaluationContext.java index 8112ae97f..c34e44516 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/NewEvaluationContext.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/NewEvaluationContext.java @@ -21,7 +21,7 @@ public class NewEvaluationContext extends HashMap { String value = ""; for ( VariantStratifier key : this.keySet() ) { - value += "\t" + key.getClass().getSimpleName() + ":" + this.get(key) + "\n"; + value += "\t" + key.getName() + ":" + this.get(key) + "\n"; } return value; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/StateKey.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/StateKey.java index 2cccb0d35..96bd9a9b7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/StateKey.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/StateKey.java @@ -1,24 +1,23 @@ package org.broadinstitute.sting.gatk.walkers.varianteval.util; +import java.util.Map; import java.util.TreeMap; public class StateKey extends TreeMap { - public int hashCode() { - int hashCode = 1; - - for (String key : this.keySet()) { - String value = this.get(key); - - hashCode *= key.hashCode() + value.hashCode(); - } - - return hashCode; - } +// public int hashCode() { +// int hashCode = 1; +// +// for (final Map.Entry pair : this.entrySet()) { +// hashCode *= pair.getKey().hashCode() + pair.getValue().hashCode(); +// } +// +// return hashCode; +// } public String toString() { String value = ""; - for ( String key : this.keySet() ) { + for ( final String key : this.keySet() ) { //value += "\tstate " + key + ":" + this.get(key) + "\n"; value += String.format("%s:%s;", key, this.get(key)); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index b319407d1..2c57d475c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -196,7 +196,7 @@ public class VariantEvalUtils { for (VariantStratifier vs : ec.keySet()) { String state = ec.get(vs); - stateKey.put(vs.getClass().getSimpleName(), state); + stateKey.put(vs.getName(), state); } ec.addEvaluationClassList(variantEvalWalker, stateKey, evaluationObjects); @@ -230,7 +230,7 @@ public class VariantEvalUtils { table.addColumn(tableName, tableName); for (VariantStratifier vs : stratificationObjects) { - String columnName = vs.getClass().getSimpleName(); + String columnName = vs.getName(); table.addColumn(columnName, "unknown"); } @@ -410,7 +410,7 @@ public class VariantEvalUtils { newStateKey.putAll(stateKey); } - newStateKey.put(vs.getClass().getSimpleName(), state); + newStateKey.put(vs.getName(), state); initializeStateKeys(stateMap, newStateStack, newStateKey, stateKeys); } From c8bf7d209924934eb8056e35860836e493747092 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 23 Nov 2011 10:47:21 -0500 Subject: [PATCH 107/113] Check for null comment --- .../java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java index b8b961119..b4d337d8d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java +++ b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java @@ -106,7 +106,7 @@ public class CommandLineGATK extends CommandLineExecutable { private static void checkForTooManyOpenFilesProblem(String message) { // Special case the "Too many open files" error because it's a common User Error for which we know what to do - if ( message.indexOf("Too many open files") != -1 ) + if ( message != null && message.indexOf("Too many open files") != -1 ) exitSystemWithUserError(new UserException.TooManyOpenFiles()); } From 5a4856b82e3a348361c611135d408262d5df5b98 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 23 Nov 2011 11:31:04 -0500 Subject: [PATCH 108/113] GATKReports now support a format field per column -- You can tell the table to format your object with "%.2f" for example. --- .../sting/gatk/report/GATKReportColumn.java | 21 +++++++++++++------ .../sting/gatk/report/GATKReportTable.java | 18 ++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumn.java b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumn.java index 347e870c8..6452c7b2b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumn.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumn.java @@ -6,9 +6,10 @@ import java.util.TreeMap; * Holds values for a column in a GATK report table */ public class GATKReportColumn extends TreeMap { - private String columnName; - private Object defaultValue; - private boolean display; + final private String columnName; + final private Object defaultValue; + final private String format; + final private boolean display; /** * Construct the column object, specifying the column name, default value, and whether or not the column should be displayed @@ -18,11 +19,17 @@ public class GATKReportColumn extends TreeMap { * @param display if true, the column will be displayed in the final output */ public GATKReportColumn(String columnName, Object defaultValue, boolean display) { + this(columnName, defaultValue, display, null); + } + + public GATKReportColumn(String columnName, Object defaultValue, boolean display, String format) { this.columnName = columnName; this.defaultValue = defaultValue; this.display = display; + this.format = format == null ? null : (format.equals("") ? null : format); } + /** * Initialize an element in the column with a default value * @@ -55,7 +62,7 @@ public class GATKReportColumn extends TreeMap { * @return the string value at the specified position in the column, or the default value if the element is not set */ public String getStringValue(Object primaryKey) { - return toString(getWithoutSideEffects(primaryKey)); + return formatValue(getWithoutSideEffects(primaryKey)); } /** @@ -77,7 +84,7 @@ public class GATKReportColumn extends TreeMap { for (Object obj : this.values()) { if (obj != null) { - int width = toString(obj).length(); + int width = formatValue(obj).length(); if (width > maxWidth) { maxWidth = width; @@ -93,10 +100,12 @@ public class GATKReportColumn extends TreeMap { * @param obj The object to convert to a string * @return The string representation of the column */ - private static String toString(Object obj) { + private String formatValue(Object obj) { String value; if (obj == null) { value = "null"; + } else if ( format != null ) { + value = String.format(format, obj); } else if (obj instanceof Float) { value = String.format("%.8f", (Float) obj); } else if (obj instanceof Double) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java index 2fd5ad7e3..95c2a14fc 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java @@ -250,13 +250,12 @@ public class GATKReportTable { * @param defaultValue the default value for the column */ public void addColumn(String columnName, Object defaultValue) { - if (!isValidName(columnName)) { - throw new ReviewedStingException("Attempted to set a GATKReportTable column name of '" + columnName + "'. GATKReportTable column names must be purely alphanumeric - no spaces or special characters are allowed."); - } - - addColumn(columnName, defaultValue, true); + addColumn(columnName, defaultValue, null); } + public void addColumn(String columnName, Object defaultValue, String format) { + addColumn(columnName, defaultValue, true, format); + } /** * Add a column to the report, specify the default column value, and specify whether the column should be displayed in the final output (useful when intermediate columns are necessary for later calculations, but are not required to be in the output file. * @@ -265,7 +264,14 @@ public class GATKReportTable { * @param display if true - the column will be displayed; if false - the column will be hidden */ public void addColumn(String columnName, Object defaultValue, boolean display) { - columns.put(columnName, new GATKReportColumn(columnName, defaultValue, display)); + addColumn(columnName, defaultValue, display, null); + } + + public void addColumn(String columnName, Object defaultValue, boolean display, String format) { + if (!isValidName(columnName)) { + throw new ReviewedStingException("Attempted to set a GATKReportTable column name of '" + columnName + "'. GATKReportTable column names must be purely alphanumeric - no spaces or special characters are allowed."); + } + columns.put(columnName, new GATKReportColumn(columnName, defaultValue, display, format)); } /** From e5b85f0a786ee7c09413ec4ca81523387d27ce81 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Wed, 23 Nov 2011 11:45:57 -0500 Subject: [PATCH 109/113] A toString() method for IntervalBindings Necessary since we're currently writing things like this to our VCF headers: intervals=[org.broadinstitute.sting.commandline.IntervalBinding@4ce66f56] --- .../org/broadinstitute/sting/commandline/IntervalBinding.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java b/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java index 82c7b20b6..9e2c9a818 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java +++ b/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java @@ -108,4 +108,8 @@ public final class IntervalBinding { return intervals; } + + public String toString() { + return getSource(); + } } From 41076361449a9e155b916847fb28c26fc82add5c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 23 Nov 2011 13:02:07 -0500 Subject: [PATCH 110/113] VariantEval updates -- Performance optimizations -- Tables now are cleanly formatted (floats are %.2f printed) -- VariantSummary is a standard report now -- Removed CompEvalGenotypes (it didn't do anything) -- Deleted unused classes in GenotypeConcordance -- Updates integration tests as appropriate --- .../varianteval/VariantEvalWalker.java | 19 +- .../evaluators/CompEvalGenotypes.java | 35 --- .../varianteval/evaluators/CompOverlap.java | 4 +- .../varianteval/evaluators/CountVariants.java | 12 +- .../evaluators/G1KPhaseITable.java | 159 ------------- .../evaluators/GenotypeConcordance.java | 109 --------- .../evaluators/TiTvVariantEvaluator.java | 6 +- .../evaluators/ValidationReport.java | 8 +- .../evaluators/VariantSummary.java | 223 ++++++++++++++++++ .../walkers/varianteval/util/DataPoint.java | 1 + .../varianteval/util/VariantEvalUtils.java | 19 +- .../VariantEvalIntegrationTest.java | 44 ++-- 12 files changed, 281 insertions(+), 358 deletions(-) delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompEvalGenotypes.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantSummary.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java index 558d9c6bf..10d4651b7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java @@ -312,16 +312,17 @@ public class VariantEvalWalker extends RodWalker implements Tr String aastr = (ancestralAlignments == null) ? null : new String(ancestralAlignments.getSubsequenceAt(ref.getLocus().getContig(), ref.getLocus().getStart(), ref.getLocus().getStop()).getBases()); // --------- track --------- sample - VariantContexts - - HashMap, HashMap>> evalVCs = variantEvalUtils.bindVariantContexts(tracker, ref, evals, byFilterIsEnabled, true, perSampleIsEnabled, mergeEvals); - HashMap, HashMap>> compVCs = variantEvalUtils.bindVariantContexts(tracker, ref, comps, byFilterIsEnabled, false, false, false); + HashMap, HashMap>> evalVCs = variantEvalUtils.bindVariantContexts(tracker, ref, evals, byFilterIsEnabled, true, perSampleIsEnabled, mergeEvals); + HashMap, HashMap>> compVCs = variantEvalUtils.bindVariantContexts(tracker, ref, comps, byFilterIsEnabled, false, false, false); // for each eval track for ( final RodBinding evalRod : evals ) { - final HashMap> evalSet = evalVCs.containsKey(evalRod) ? evalVCs.get(evalRod) : new HashMap>(0); + final Map> emptyEvalMap = Collections.emptyMap(); + final Map> evalSet = evalVCs.containsKey(evalRod) ? evalVCs.get(evalRod) : emptyEvalMap; // for each sample stratifier for ( final String sampleName : sampleNamesForStratification ) { - Set evalSetBySample = evalSet.get(sampleName); + Collection evalSetBySample = evalSet.get(sampleName); if ( evalSetBySample == null ) { evalSetBySample = new HashSet(1); evalSetBySample.add(null); @@ -337,8 +338,8 @@ public class VariantEvalWalker extends RodWalker implements Tr // for each comp track for ( final RodBinding compRod : comps ) { // no sample stratification for comps - final HashMap> compSetHash = compVCs.get(compRod); - final Set compSet = (compSetHash == null || compSetHash.size() == 0) ? new HashSet(0) : compVCs.get(compRod).values().iterator().next(); + final HashMap> compSetHash = compVCs.get(compRod); + final Collection compSet = (compSetHash == null || compSetHash.size() == 0) ? Collections.emptyList() : compVCs.get(compRod).values().iterator().next(); // find the comp final VariantContext comp = findMatchingComp(eval, compSet); @@ -382,7 +383,7 @@ public class VariantEvalWalker extends RodWalker implements Tr return null; } - private VariantContext findMatchingComp(final VariantContext eval, final Set comps) { + private VariantContext findMatchingComp(final VariantContext eval, final Collection comps) { // if no comps, return null if ( comps == null || comps.isEmpty() ) return null; @@ -447,11 +448,11 @@ public class VariantEvalWalker extends RodWalker implements Tr TableType t = (TableType) field.get(ve); String subTableName = ve.getClass().getSimpleName() + "." + field.getName(); - String subTableDesc = datamap.get(field).description(); + final DataPoint dataPointAnn = datamap.get(field); GATKReportTable table; if (!report.hasTable(subTableName)) { - report.addTable(subTableName, subTableDesc); + report.addTable(subTableName, dataPointAnn.description()); table = report.getTable(subTableName); table.addPrimaryKey("entry", false); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompEvalGenotypes.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompEvalGenotypes.java deleted file mode 100755 index 925bff9c0..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompEvalGenotypes.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.varianteval.evaluators; - -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.variantcontext.Genotype; - -class NewCompEvalGenotypes { - private GenomeLoc loc; - private Genotype compGt; - private Genotype evalGt; - - public NewCompEvalGenotypes(GenomeLoc loc, Genotype compGt, Genotype evalGt) { - this.loc = loc; - this.compGt = compGt; - this.evalGt = evalGt; - } - - public GenomeLoc getLocus() { - return loc; - } - - public Genotype getCompGenotpye() { - return compGt; - } - public Genotype getEvalGenotype() { - return evalGt; - } - - public void setCompGenotype(Genotype compGt) { - this.compGt = compGt; - } - - public void setEvalGenotype(Genotype evalGt) { - this.evalGt = evalGt; - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java index b3695921a..89d137ea9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CompOverlap.java @@ -28,13 +28,13 @@ public class CompOverlap extends VariantEvaluator implements StandardEval { @DataPoint(description = "number of eval sites at comp sites") long nVariantsAtComp = 0; - @DataPoint(description = "percentage of eval sites at comp sites") + @DataPoint(description = "percentage of eval sites at comp sites", format = "%.2f" ) double compRate = 0.0; @DataPoint(description = "number of concordant sites") long nConcordant = 0; - @DataPoint(description = "the concordance rate") + @DataPoint(description = "the concordance rate", format = "%.2f") double concordantRate = 0.0; public int getComparisonOrder() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java index d8413573a..c740eb78c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/CountVariants.java @@ -62,17 +62,17 @@ public class CountVariants extends VariantEvaluator implements StandardEval { public long nHomDerived = 0; // calculations that get set in the finalizeEvaluation method - @DataPoint(description = "heterozygosity per locus rate") + @DataPoint(description = "heterozygosity per locus rate", format = "%.2e") public double heterozygosity = 0; - @DataPoint(description = "heterozygosity per base pair") + @DataPoint(description = "heterozygosity per base pair", format = "%.2f") public double heterozygosityPerBp = 0; - @DataPoint(description = "heterozygosity to homozygosity ratio") + @DataPoint(description = "heterozygosity to homozygosity ratio", format = "%.2f") public double hetHomRatio = 0; - @DataPoint(description = "indel rate (insertion count + deletion count)") + @DataPoint(description = "indel rate (insertion count + deletion count)", format = "%.2e") public double indelRate = 0; - @DataPoint(description = "indel rate per base pair") + @DataPoint(description = "indel rate per base pair", format = "%.2f") public double indelRatePerBp = 0; - @DataPoint(description = "deletion to insertion ratio") + @DataPoint(description = "deletion to insertion ratio", format = "%.2f") public double deletionInsertionRatio = 0; private double perLocusRate(long n) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java deleted file mode 100644 index ff8f6307c..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2011, The Broad Institute - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.broadinstitute.sting.gatk.walkers.varianteval.evaluators; - -import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; -import org.broadinstitute.sting.gatk.walkers.varianteval.VariantEvalWalker; -import org.broadinstitute.sting.gatk.walkers.varianteval.util.Analysis; -import org.broadinstitute.sting.gatk.walkers.varianteval.util.DataPoint; -import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; - -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; - -@Analysis(description = "Build 1000 Genome Phase I paper summary of variants table") -public class G1KPhaseITable extends VariantEvaluator { - // basic counts on various rates found - @DataPoint(description = "Number of samples") - public long nSamples = 0; - - @DataPoint(description = "Number of processed loci") - public long nProcessedLoci = 0; - - @DataPoint(description = "Number of SNPs") - public long nSNPs = 0; - @DataPoint(description = "SNP Novelty Rate") - public String SNPNoveltyRate = "NA"; - @DataPoint(description = "Mean number of SNPs per individual") - public long nSNPsPerSample = 0; - - @DataPoint(description = "Number of Indels") - public long nIndels = 0; - @DataPoint(description = "Indel Novelty Rate") - public String IndelNoveltyRate = "NA"; - @DataPoint(description = "Mean number of Indels per individual") - public long nIndelsPerSample = 0; - - @DataPoint(description = "Number of SVs") - public long nSVs = 0; - @DataPoint(description = "SV Novelty Rate") - public String SVNoveltyRate = "NA"; - @DataPoint(description = "Mean number of SVs per individual") - public long nSVsPerSample = 0; - - Map allVariantCounts, knownVariantCounts; - Map> countsPerSample; - - private final Map makeCounts() { - Map counts = new EnumMap(VariantContext.Type.class); - counts.put(VariantContext.Type.SNP, 0); - counts.put(VariantContext.Type.INDEL, 0); - counts.put(VariantContext.Type.SYMBOLIC, 0); - return counts; - } - - public void initialize(VariantEvalWalker walker) { - countsPerSample = new HashMap>(); - nSamples = walker.getSampleNamesForEvaluation().size(); - - for ( String sample : walker.getSampleNamesForEvaluation() ) { - countsPerSample.put(sample, makeCounts()); - } - - allVariantCounts = makeCounts(); - knownVariantCounts = makeCounts(); - } - - @Override public boolean enabled() { return true; } - - public int getComparisonOrder() { - return 2; // we only need to see each eval track - } - - public void update0(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - nProcessedLoci += context.getSkippedBases() + (ref == null ? 0 : 1); - } - - public String update2(VariantContext eval, VariantContext comp, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if ( eval == null || eval.isMonomorphicInSamples() ) return null; - - switch (eval.getType()) { - case SNP: - case INDEL: - case SYMBOLIC: - allVariantCounts.put(eval.getType(), allVariantCounts.get(eval.getType()) + 1); - if ( comp != null ) - knownVariantCounts.put(eval.getType(), knownVariantCounts.get(eval.getType()) + 1); - break; - default: - throw new UserException.BadInput("Unexpected variant context type: " + eval); - } - - // count variants per sample - for (final Genotype g : eval.getGenotypes()) { - if ( ! g.isNoCall() && ! g.isHomRef() ) { - int count = countsPerSample.get(g.getSampleName()).get(eval.getType()); - countsPerSample.get(g.getSampleName()).put(eval.getType(), count + 1); - } - } - - return null; // we don't capture any interesting sites - } - - private final int perSampleMean(VariantContext.Type type) { - long sum = 0; - for ( Map count : countsPerSample.values() ) { - sum += count.get(type); - } - return (int)(Math.round(sum / (1.0 * countsPerSample.size()))); - } - - private final String noveltyRate(VariantContext.Type type) { - int all = allVariantCounts.get(type); - int known = knownVariantCounts.get(type); - int novel = all - known; - double rate = (novel / (1.0 * all)); - return all == 0 ? "NA" : String.format("%.2f", rate); - } - - public void finalizeEvaluation() { - nSNPs = allVariantCounts.get(VariantContext.Type.SNP); - nIndels = allVariantCounts.get(VariantContext.Type.INDEL); - nSVs = allVariantCounts.get(VariantContext.Type.SYMBOLIC); - - nSNPsPerSample = perSampleMean(VariantContext.Type.SNP); - nIndelsPerSample = perSampleMean(VariantContext.Type.INDEL); - nSVsPerSample = perSampleMean(VariantContext.Type.SYMBOLIC); - - SNPNoveltyRate = noveltyRate(VariantContext.Type.SNP); - IndelNoveltyRate = noveltyRate(VariantContext.Type.INDEL); - SVNoveltyRate = noveltyRate(VariantContext.Type.SYMBOLIC); - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java index 70b37f500..4f5aeed61 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/GenotypeConcordance.java @@ -445,39 +445,6 @@ class SampleStats implements TableType { } } -/** - * Sample stats, but for AC - */ -class ACStats extends SampleStats { - private String[] rowKeys; - - public ACStats(VariantContext evalvc, VariantContext compvc, int nGenotypeTypes) { - super(nGenotypeTypes); - rowKeys = new String[1+2*evalvc.getGenotypes().size()+1+2*compvc.getGenotypes().size()]; - for ( int i = 0; i <= 2*evalvc.getGenotypes().size(); i++ ) { // todo -- assuming ploidy 2 here... - concordanceStats.put(String.format("evalAC%d",i),new long[nGenotypeTypes][nGenotypeTypes]); - rowKeys[i] = String.format("evalAC%d",i); - - } - - for ( int i = 0; i <= 2*compvc.getGenotypes().size(); i++ ) { - concordanceStats.put(String.format("compAC%d",i), new long[nGenotypeTypes][nGenotypeTypes]); - rowKeys[1+2*evalvc.getGenotypes().size()+i] = String.format("compAC%d",i); - } - } - - public String getName() { - return "Allele Count Statistics"; - } - - public Object[] getRowKeys() { - if ( rowKeys == null ) { - throw new StingException("RowKeys is null!"); - } - return rowKeys; - } -} - /** * a table of sample names to genotype concordance summary statistics */ @@ -637,79 +604,3 @@ class SampleSummaryStats implements TableType { } } -/** - * SampleSummaryStats .. but for allele counts - */ -class ACSummaryStats extends SampleSummaryStats { - private String[] rowKeys; - - public ACSummaryStats (final VariantContext evalvc, final VariantContext compvc) { - concordanceSummary.put(ALL_SAMPLES_KEY, new double[COLUMN_KEYS.length]); - rowKeys = new String[3+2*evalvc.getGenotypes().size() + 2*compvc.getGenotypes().size()]; - rowKeys[0] = ALL_SAMPLES_KEY; - for( int i = 0; i <= 2*evalvc.getGenotypes().size() ; i ++ ) { - concordanceSummary.put(String.format("evalAC%d",i), new double[COLUMN_KEYS.length]); - rowKeys[i+1] = String.format("evalAC%d",i); - } - for( int i = 0; i <= 2*compvc.getGenotypes().size() ; i ++ ) { - concordanceSummary.put(String.format("compAC%d",i), new double[COLUMN_KEYS.length]); - rowKeys[2+2*evalvc.getGenotypes().size()+i] = String.format("compAC%d",i); - } - - } - - public String getName() { - return "Allele Count Summary Statistics"; - } - - public Object[] getRowKeys() { - if ( rowKeys == null) { - throw new StingException("rowKeys is null!!"); - } - return rowKeys; - } -} - -class CompACNames implements Comparator{ - - final Logger myLogger; - private boolean info = true; - - public CompACNames(Logger l) { - myLogger = l; - } - - public boolean equals(Object o) { - return ( o.getClass() == CompACNames.class ); - } - - public int compare(Object o1, Object o2) { - if ( info ) { - myLogger.info("Sorting AC names"); - info = false; - } - //System.out.printf("Objects %s %s get ranks %d %d%n",o1.toString(),o2.toString(),getRank(o1),getRank(o2)); - return getRank(o1) - getRank(o2); - } - - public int getRank(Object o) { - if ( o.getClass() != String.class ) { - return Integer.MIN_VALUE/4; - } else { - String s = (String) o; - if ( s.startsWith("eval") ) { - return Integer.MIN_VALUE/4 + 1 + parseAC(s); - } else if ( s.startsWith("comp") ) { - return 1+ parseAC(s); - } else { - return Integer.MIN_VALUE/4; - } - } - } - - public int parseAC(String s) { - String[] g = s.split("AC"); - return Integer.parseInt(g[1]); - } -} - diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java index 17d7171b8..9de850d82 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/TiTvVariantEvaluator.java @@ -16,19 +16,19 @@ public class TiTvVariantEvaluator extends VariantEvaluator implements StandardEv long nTi = 0; @DataPoint(description = "number of transversion loci") long nTv = 0; - @DataPoint(description = "the transition to transversion ratio") + @DataPoint(description = "the transition to transversion ratio", format = "%.2f") double tiTvRatio = 0.0; @DataPoint(description = "number of comp transition sites") long nTiInComp = 0; @DataPoint(description = "number of comp transversion sites") long nTvInComp = 0; - @DataPoint(description = "the transition to transversion ratio for comp sites") + @DataPoint(description = "the transition to transversion ratio for comp sites", format = "%.2f") double TiTvRatioStandard = 0.0; @DataPoint(description = "number of derived transition loci") long nTiDerived = 0; @DataPoint(description = "number of derived transversion loci") long nTvDerived = 0; - @DataPoint(description = "the derived transition to transversion ratio") + @DataPoint(description = "the derived transition to transversion ratio", format = "%.2f") double tiTvDerivedRatio = 0.0; public boolean enabled() { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java index 1a0591e9d..86d3467fb 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/ValidationReport.java @@ -30,10 +30,10 @@ public class ValidationReport extends VariantEvaluator implements StandardEval { @DataPoint(description = "FN") int FN = 0; @DataPoint(description = "TN") int TN = 0; - @DataPoint(description = "Sensitivity") double sensitivity = 0; - @DataPoint(description = "Specificity") double specificity = 0; - @DataPoint(description = "PPV") double PPV = 0; - @DataPoint(description = "FDR") double FDR = 0; + @DataPoint(description = "Sensitivity", format = "%.2f") double sensitivity = 0; + @DataPoint(description = "Specificity", format = "%.2f") double specificity = 0; + @DataPoint(description = "PPV", format = "%.2f") double PPV = 0; + @DataPoint(description = "FDR", format = "%.2f") double FDR = 0; @DataPoint(description = "CompMonoEvalNoCall") int CompMonoEvalNoCall = 0; @DataPoint(description = "CompMonoEvalFiltered") int CompMonoEvalFiltered = 0; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantSummary.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantSummary.java new file mode 100644 index 000000000..ba7164400 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/VariantSummary.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2011, The Broad Institute + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.broadinstitute.sting.gatk.walkers.varianteval.evaluators; + +import org.broadinstitute.sting.gatk.contexts.AlignmentContext; +import org.broadinstitute.sting.gatk.contexts.ReferenceContext; +import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; +import org.broadinstitute.sting.gatk.walkers.varianteval.VariantEvalWalker; +import org.broadinstitute.sting.gatk.walkers.varianteval.util.Analysis; +import org.broadinstitute.sting.gatk.walkers.varianteval.util.DataPoint; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; + +import java.util.Collection; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +@Analysis(description = "1000 Genomes Phase I summary of variants table") +public class VariantSummary extends VariantEvaluator implements StandardEval { + // basic counts on various rates found + @DataPoint(description = "Number of samples") + public long nSamples = 0; + + @DataPoint(description = "Number of processed loci") + public long nProcessedLoci = 0; + + @DataPoint(description = "Number of SNPs") + public long nSNPs = 0; + @DataPoint(description = "Overall TiTv ratio", format = "%.2f") + public double TiTvRatio = 0; + @DataPoint(description = "SNP Novelty Rate") + public String SNPNoveltyRate = "NA"; + @DataPoint(description = "Mean number of SNPs per individual") + public long nSNPsPerSample = 0; + @DataPoint(description = "Mean TiTv ratio per individual", format = "%.2f") + public double TiTvRatioPerSample = 0; + @DataPoint(description = "Mean depth of coverage per sample at SNPs", format = "%.1f") + public double SNPDPPerSample = 0; + + @DataPoint(description = "Number of Indels") + public long nIndels = 0; + @DataPoint(description = "Indel Novelty Rate") + public String IndelNoveltyRate = "NA"; + @DataPoint(description = "Mean number of Indels per individual") + public long nIndelsPerSample = 0; + @DataPoint(description = "Mean depth of coverage per sample at Indels", format = "%.1f") + public double IndelDPPerSample = 0; + + @DataPoint(description = "Number of SVs") + public long nSVs = 0; + @DataPoint(description = "SV Novelty Rate") + public String SVNoveltyRate = "NA"; + @DataPoint(description = "Mean number of SVs per individual") + public long nSVsPerSample = 0; + + TypeSampleMap allVariantCounts, knownVariantCounts; + TypeSampleMap countsPerSample; + TypeSampleMap transitionsPerSample, transversionsPerSample; + TypeSampleMap depthPerSample; + + private final static String ALL = "ALL"; + + private class TypeSampleMap extends EnumMap> { + public TypeSampleMap(final Collection samples) { + super(VariantContext.Type.class); + for ( VariantContext.Type type : VariantContext.Type.values() ) { + Map bySample = new HashMap(samples.size()); + for ( final String sample : samples ) { + bySample.put(sample, 0); + } + bySample.put(ALL, 0); + this.put(type, bySample); + } + } + + public final void inc(final VariantContext.Type type, final String sample) { + final int count = this.get(type).get(sample); + get(type).put(sample, count + 1); + } + + public final int all(VariantContext.Type type) { + return get(type).get(ALL); + } + + public final int meanValue(VariantContext.Type type) { + long sum = 0; + int n = 0; + for ( final Map.Entry pair : get(type).entrySet() ) { + if ( pair.getKey() != ALL) { + n++; + sum += pair.getValue(); + } + } + return (int)(Math.round(sum / (1.0 * n))); + } + + public final double ratioValue(VariantContext.Type type, TypeSampleMap denoms, boolean allP) { + double sum = 0; + int n = 0; + for ( final String sample : get(type).keySet() ) { + if ( (allP && sample == ALL) || (!allP && sample != ALL) ) { + final long num = get(type).get(sample); + final long denom = denoms.get(type).get(sample); + sum += ratio(num, denom); + n++; + } + } + return Math.round(sum / (1.0 * n)); + } + } + + + public void initialize(VariantEvalWalker walker) { + nSamples = walker.getSampleNamesForEvaluation().size(); + countsPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + transitionsPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + transversionsPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + allVariantCounts = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + knownVariantCounts = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + depthPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + } + + @Override public boolean enabled() { return true; } + + public int getComparisonOrder() { + return 2; // we only need to see each eval track + } + + public void update0(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + nProcessedLoci += context.getSkippedBases() + (ref == null ? 0 : 1); + } + + public String update2(VariantContext eval, VariantContext comp, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + if ( eval == null || eval.isMonomorphicInSamples() ) return null; + + TypeSampleMap titvTable = null; + + switch (eval.getType()) { + case SNP: + titvTable = VariantContextUtils.isTransition(eval) ? transitionsPerSample : transversionsPerSample; + titvTable.inc(eval.getType(), ALL); + case INDEL: + case SYMBOLIC: + allVariantCounts.inc(eval.getType(), ALL); + if ( comp != null ) + knownVariantCounts.inc(eval.getType(), ALL); + if ( eval.hasAttribute(VCFConstants.DEPTH_KEY) ) + depthPerSample.inc(eval.getType(), ALL); + break; + default: + throw new UserException.BadInput("Unexpected variant context type: " + eval); + } + + // per sample metrics + for (final Genotype g : eval.getGenotypes()) { + if ( ! g.isNoCall() && ! g.isHomRef() ) { + countsPerSample.inc(eval.getType(), g.getSampleName()); + + // update transition / transversion ratio + if ( titvTable != null ) titvTable.inc(eval.getType(), g.getSampleName()); + + if ( g.hasAttribute(VCFConstants.DEPTH_KEY) ) + depthPerSample.inc(eval.getType(), g.getSampleName()); + } + } + + return null; // we don't capture any interesting sites + } + + private final String noveltyRate(VariantContext.Type type) { + final int all = allVariantCounts.all(type); + final int known = knownVariantCounts.all(type); + final int novel = all - known; + final double rate = (novel / (1.0 * all)); + return all == 0 ? "NA" : String.format("%.2f", rate); + } + + public void finalizeEvaluation() { + nSNPs = allVariantCounts.all(VariantContext.Type.SNP); + nIndels = allVariantCounts.all(VariantContext.Type.INDEL); + nSVs = allVariantCounts.all(VariantContext.Type.SYMBOLIC); + + TiTvRatio = transitionsPerSample.ratioValue(VariantContext.Type.SNP, transversionsPerSample, true); + TiTvRatioPerSample = transitionsPerSample.ratioValue(VariantContext.Type.SNP, transversionsPerSample, false); + + nSNPsPerSample = countsPerSample.meanValue(VariantContext.Type.SNP); + nIndelsPerSample = countsPerSample.meanValue(VariantContext.Type.INDEL); + nSVsPerSample = countsPerSample.meanValue(VariantContext.Type.SYMBOLIC); + + SNPNoveltyRate = noveltyRate(VariantContext.Type.SNP); + IndelNoveltyRate = noveltyRate(VariantContext.Type.INDEL); + SVNoveltyRate = noveltyRate(VariantContext.Type.SYMBOLIC); + + SNPDPPerSample = depthPerSample.meanValue(VariantContext.Type.SNP); + IndelDPPerSample = depthPerSample.meanValue(VariantContext.Type.INDEL); + } +} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/DataPoint.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/DataPoint.java index 396843252..90a6b97e0 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/DataPoint.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/DataPoint.java @@ -6,4 +6,5 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface DataPoint { String description() default ""; // the description, optional + String format() default ""; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java index 2c57d475c..cb44ca522 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/util/VariantEvalUtils.java @@ -246,7 +246,7 @@ public class VariantEvalUtils { field.setAccessible(true); if (!(field.get(vei) instanceof TableType)) { - table.addColumn(field.getName(), 0.0); + table.addColumn(field.getName(), 0.0, datamap.get(field).format()); } } } catch (InstantiationException e) { @@ -297,6 +297,7 @@ public class VariantEvalUtils { * Additional variant contexts per sample are automatically generated and added to the map unless the sample name * matches the ALL_SAMPLE_NAME constant. * + * * @param tracker the metadata tracker * @param ref the reference context * @param tracks the list of tracks to process @@ -308,7 +309,7 @@ public class VariantEvalUtils { * * @return the mapping of track to VC list that should be populated */ - public HashMap, HashMap>> + public HashMap, HashMap>> bindVariantContexts(RefMetaDataTracker tracker, ReferenceContext ref, List> tracks, @@ -319,11 +320,11 @@ public class VariantEvalUtils { if ( tracker == null ) return null; - HashMap, HashMap>> bindings = new HashMap, HashMap>>(); + HashMap, HashMap>> bindings = new HashMap, HashMap>>(); RodBinding firstTrack = tracks.isEmpty() ? null : tracks.get(0); for ( RodBinding track : tracks ) { - HashMap> mapping = new HashMap>(); + HashMap> mapping = new HashMap>(); for ( VariantContext vc : tracker.getValues(track, ref.getLocus()) ) { @@ -352,9 +353,9 @@ public class VariantEvalUtils { if ( mergeTracks && bindings.containsKey(firstTrack) ) { // go through each binding of sample -> value and add all of the bindings from this entry - HashMap> firstMapping = bindings.get(firstTrack); - for ( Map.Entry> elt : mapping.entrySet() ) { - Set firstMappingSet = firstMapping.get(elt.getKey()); + HashMap> firstMapping = bindings.get(firstTrack); + for ( Map.Entry> elt : mapping.entrySet() ) { + Collection firstMappingSet = firstMapping.get(elt.getKey()); if ( firstMappingSet != null ) { firstMappingSet.addAll(elt.getValue()); } else { @@ -369,9 +370,9 @@ public class VariantEvalUtils { return bindings; } - private void addMapping(HashMap> mappings, String sample, VariantContext vc) { + private void addMapping(HashMap> mappings, String sample, VariantContext vc) { if ( !mappings.containsKey(sample) ) - mappings.put(sample, new LinkedHashSet()); + mappings.put(sample, new ArrayList(1)); mappings.get(sample).add(vc); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java index 102d4715e..403ecce78 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java @@ -30,7 +30,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("a36414421621b377d6146d58d2fcecd0") + Arrays.asList("abe943d1aac120d7e75b9b9e5dac2399") ); executeTest("testFunctionClassWithSnpeff", spec); } @@ -50,7 +50,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("6a71b17c19f5914c277a99f45f5d9c39") + Arrays.asList("5fd9624c7a35ffb79d0feb1e233fc757") ); executeTest("testStratifySamplesAndExcludeMonomorphicSites", spec); } @@ -70,7 +70,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("fb926edfd3d811e18b33798a43ef4379") + Arrays.asList("4a8765cd02d36e63f6d0f0c10a6c674b") ); executeTest("testFundamentalsCountVariantsSNPsandIndels", spec); } @@ -91,7 +91,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("26b7d57e3a204ac80a28cb29485b59b7") + Arrays.asList("4106ab8f742ad1c3138c29220151503c") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithNovelty", spec); } @@ -113,7 +113,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("1df8184062f330bea9da8bacacc5a09d") + Arrays.asList("6cee3a8d68307a118944f2df5401ac89") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithNoveltyAndFilter", spec); } @@ -134,7 +134,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("927f26414509db9e7c0a2c067d57c949") + Arrays.asList("af5dd27354d5dfd0d2fe03149af09b55") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithCpG", spec); } @@ -155,7 +155,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("e6fddefd95122cabc5a0f0b95bce6d34") + Arrays.asList("062a231e203671e19aa9c6507710d762") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithFunctionalClass", spec); } @@ -176,7 +176,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("df10486dae73a9cf8c647964f51ba3e0") + Arrays.asList("75abdd2b17c0a5e04814b6969a3d4d7e") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithDegeneracy", spec); } @@ -197,7 +197,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("524adb0b7ff70e227b8803a88f36713e") + Arrays.asList("bdbb5f8230a4a193058750c5e506c733") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithSample", spec); } @@ -220,7 +220,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("ef6449789dfc032602458b7c5538a1bc") + Arrays.asList("f076120da22930294840fcc396f5f141") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithJexlExpression", spec); } @@ -245,7 +245,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("13b90e94fa82d72bb04a0a5addb27c3f") + Arrays.asList("69201f4a2a7a44b38805a4aeeb8830b6") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithMultipleJexlExpressions", spec); } @@ -264,7 +264,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("8458b9d7803d75aae551fac7dbd152d6") + Arrays.asList("c3bd3cb6cfb21a8c2b4d5f69104bf6c2") ); executeTest("testFundamentalsCountVariantsNoCompRod", spec); } @@ -277,7 +277,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { " --eval " + validationDataLocation + "yri.trio.gatk_glftrio.intersection.annotated.filtered.chr1.vcf" + " --comp:comp_genotypes,VCF3 " + validationDataLocation + "yri.trio.gatk.ug.head.vcf"; WalkerTestSpec spec = new WalkerTestSpec(withSelect(tests, "DP < 50", "DP50") + " " + extraArgs + " -ST CpG -o %s", - 1, Arrays.asList("b954dee127ec4205ed7d33c91aa3e045")); + 1, Arrays.asList("861f94e3237d62bd5bc00757319241f7")); executeTestParallel("testSelect1", spec); } @@ -294,7 +294,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { @Test public void testCompVsEvalAC() { String extraArgs = "-T VariantEval -R "+b36KGReference+" -o %s -ST CpG -EV GenotypeConcordance --eval:evalYRI,VCF3 " + validationDataLocation + "yri.trio.gatk.ug.very.few.lines.vcf --comp:compYRI,VCF3 " + validationDataLocation + "yri.trio.gatk.fake.genotypes.ac.test.vcf"; - WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("ae0027197547731a9a5c1eec5fbe0221")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("955c33365e017679047fabec0f14d5e0")); executeTestParallel("testCompVsEvalAC",spec); } @@ -312,7 +312,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { @Test public void testCompOverlap() { String extraArgs = "-T VariantEval -R " + b37KGReference + " -L " + validationDataLocation + "VariantEval/pacbio.hg19.intervals --comp:comphapmap " + comparisonDataLocation + "Validated/HapMap/3.3/genotypes_r27_nr.b37_fwd.vcf --eval " + validationDataLocation + "VariantEval/pacbio.ts.recalibrated.vcf -noEV -EV CompOverlap -sn NA12878 -noST -ST Novelty -o %s"; - WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("009ecc8376a20dce81ff5299ef6bfecb")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("fb7d989e44bd74c5376cb5732f9f3f64")); executeTestParallel("testCompOverlap",spec); } @@ -324,7 +324,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { " --dbsnp " + b37dbSNP132 + " --eval:evalBI " + validationDataLocation + "VariantEval/ALL.20100201.chr20.bi.sites.vcf" + " -noST -ST Novelty -o %s"; - WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("835b44fc3004cc975c968c9f92ed25d6")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("da5bcb305c5ef207ce175821efdbdefd")); executeTestParallel("testEvalTrackWithoutGenotypes",spec); } @@ -336,7 +336,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { " --eval:evalBI " + validationDataLocation + "VariantEval/ALL.20100201.chr20.bi.sites.vcf" + " --eval:evalBC " + validationDataLocation + "VariantEval/ALL.20100201.chr20.bc.sites.vcf" + " -noST -ST Novelty -o %s"; - WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("f0e003f1293343c3210ae95e8936b19a")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("fde839ece1442388f21a2f0b936756a8")); executeTestParallel("testMultipleEvalTracksWithoutGenotypes",spec); } @@ -353,13 +353,13 @@ public class VariantEvalIntegrationTest extends WalkerTest { " -noST -noEV -ST Novelty -EV CompOverlap" + " -o %s"; - WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("0b81d97f843ec4a1a4222d1f9949bfca")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("1efae6b3b88c752b771e0c8fae24464e")); executeTestParallel("testMultipleCompTracks",spec); } @Test - public void testPerSampleAndSubsettedSampleHaveSameResults() { - String md5 = "7425ca5c439afd7bb33ed5cfea02c2b3"; + public void testPerSampleAndSubsettedSampleHaveSameResults1() { + String md5 = "bc9bcabc3105e2515d9a2d41506d2de1"; WalkerTestSpec spec = new WalkerTestSpec( buildCommandLine( @@ -414,7 +414,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("924b6123edb9da540d0abc66f6f33e16") + Arrays.asList("e53546243250634fc03e83b4e61ec55f") ); executeTest("testAlleleCountStrat", spec); } @@ -435,7 +435,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("9794e2dba205c6929dc89899fdf0bf6b") + Arrays.asList("c8086f0525bc13e666afeb670c2e13ae") ); executeTest("testIntervalStrat", spec); } From fdd90825a193c2f928fad28a9eed16d28847ac5a Mon Sep 17 00:00:00 2001 From: David Roazen Date: Wed, 23 Nov 2011 13:31:39 -0500 Subject: [PATCH 111/113] Queue now outputs a GATK-like header with version number, build timestamp, etc. --- .../sting/queue/QCommandLine.scala | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala index 768eab7e4..913bd243c 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala @@ -26,7 +26,6 @@ package org.broadinstitute.sting.queue import function.QFunction import java.io.File -import java.util.Arrays import org.broadinstitute.sting.commandline._ import org.broadinstitute.sting.queue.util._ import org.broadinstitute.sting.queue.engine.{QGraphSettings, QGraph} @@ -34,6 +33,9 @@ import collection.JavaConversions._ import org.broadinstitute.sting.utils.classloader.PluginManager import org.broadinstitute.sting.utils.exceptions.UserException import org.broadinstitute.sting.utils.io.IOUtils +import org.broadinstitute.sting.utils.help.ApplicationDetails +import java.util.{ResourceBundle, Arrays} +import org.broadinstitute.sting.utils.text.TextFormattingUtils /** * Entry point of Queue. Compiles and runs QScripts passed in to the command line. @@ -175,6 +177,42 @@ class QCommandLine extends CommandLineProgram with Logging { override def getArgumentTypeDescriptors = Arrays.asList(new ScalaCompoundArgumentTypeDescriptor) + override def getApplicationDetails : ApplicationDetails = { + new ApplicationDetails(createQueueHeader(), + List.empty[String], + ApplicationDetails.createDefaultRunningInstructions(getClass.asInstanceOf[Class[CommandLineProgram]]), + "") + } + + private def createQueueHeader() : List[String] = { + List(String.format("Queue v%s, Compiled %s", getQueueVersion, getBuildTimestamp), + "Copyright (c) 2011 The Broad Institute", + "Please view our documentation at http://www.broadinstitute.org/gsa/wiki", + "For support, please view our support site at http://getsatisfaction.com/gsa") + } + + private def getQueueVersion : String = { + var stingResources : ResourceBundle = TextFormattingUtils.loadResourceBundle("StingText") + + if ( stingResources.containsKey("org.broadinstitute.sting.queue.QueueVersion.version") ) { + stingResources.getString("org.broadinstitute.sting.queue.QueueVersion.version") + } + else { + "" + } + } + + private def getBuildTimestamp : String = { + var stingResources : ResourceBundle = TextFormattingUtils.loadResourceBundle("StingText") + + if ( stingResources.containsKey("build.timestamp") ) { + stingResources.getString("build.timestamp") + } + else { + "" + } + } + def shutdown() = { shuttingDown = true qGraph.shutdown() From 12f09d88f91e6aded9e49f47b62f483925540483 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 23 Nov 2011 16:08:18 -0500 Subject: [PATCH 112/113] Removing references to SimpleMetricsByAC --- .../sting/gatk/report/GATKReportUnitTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/report/GATKReportUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/report/GATKReportUnitTest.java index 02e1ba99a..f7be1d845 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/report/GATKReportUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/report/GATKReportUnitTest.java @@ -44,12 +44,5 @@ public class GATKReportUnitTest extends BaseTest { Assert.assertEquals(validationReport.getVersion(), GATKReportVersion.V0_1); Object validationReportPK = countVariants.getPrimaryKey("none.eval.none.known"); Assert.assertEquals(validationReport.get(validationReportPK, "sensitivity"), "NaN"); - - GATKReportTable simpleMetricsByAC = report.getTable("SimpleMetricsByAC.metrics"); - Assert.assertEquals(simpleMetricsByAC.getVersion(), GATKReportVersion.V0_1); - Object simpleMetricsByACPK = simpleMetricsByAC.getPrimaryKey("none.eval.none.novel.ac2"); - Assert.assertEquals(simpleMetricsByAC.get(simpleMetricsByACPK, "AC"), "2"); - - Assert.assertFalse(simpleMetricsByAC.containsPrimaryKey("none.eval.none.novel.ac2.bad")); } } From e60272975ad88ef7138599ec88f61257c0b865ca Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 23 Nov 2011 19:01:33 -0500 Subject: [PATCH 113/113] Fix for changed MD5 in streaming VCF test --- .../gatk/walkers/variantutils/VCFStreamingIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/VCFStreamingIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/VCFStreamingIntegrationTest.java index 00044f859..3a25bc5c1 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/VCFStreamingIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/VCFStreamingIntegrationTest.java @@ -98,7 +98,7 @@ public class VCFStreamingIntegrationTest extends WalkerTest { " -EV CompOverlap -noEV -noST" + " -o %s", 1, - Arrays.asList("d46a735ffa898f4aa6b3758c5b03f06d") + Arrays.asList("1f7ed8c0f671dd227ab764624ef0d64c") ); executeTest("testVCFStreamingChain", selectTestSpec);