From 76dd816e709ef230231173b43076c4a423577a4a Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 12:47:27 +0200 Subject: [PATCH 001/380] Added getParents() -> returns an arrayList containing the sample's parent(s) if available --- .../broadinstitute/sting/gatk/samples/Sample.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java index b39fdd79d..a14d999ea 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -3,6 +3,7 @@ package org.broadinstitute.sting.gatk.samples; import org.broadinstitute.sting.utils.exceptions.UserException; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -110,6 +111,17 @@ public class Sample implements Comparable { // implements java.io.Serial return infoDB.getSample(paternalID); } + public ArrayList getParents(){ + ArrayList parents = new ArrayList(2); + Sample parent = getMother(); + if(parent != null) + parents.add(parent); + parent = getFather(); + if(parent != null) + parents.add(parent); + return parents; + } + /** * Get gender of the sample * @return property of key "gender" - must be of type Gender From ef6a6fdfe499809fc3fd6282a460c2fd542963c5 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 12:49:18 +0200 Subject: [PATCH 002/380] Added getAsMap -> returns the likelihoods as an EnumMap with Genotypes as keys and likelihoods as values. --- .../utils/variantcontext/GenotypeLikelihoods.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 dba16cf86..292bc17cd 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -25,8 +25,12 @@ package org.broadinstitute.sting.utils.variantcontext; import org.broad.tribble.TribbleException; +import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +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; @@ -94,6 +98,16 @@ public class GenotypeLikelihoods { return likelihoodsAsString_PLs; } + public EnumMap getAsMap(boolean normalizeFromLog10){ + //Make sure that the log10likelihoods are set + double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); + 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; + } + private final static double[] parsePLsIntoLikelihoods(String likelihoodsAsString_PLs) { if ( !likelihoodsAsString_PLs.equals(VCFConstants.MISSING_VALUE_v4) ) { String[] strings = likelihoodsAsString_PLs.split(","); From 1c61a573296f6df9a3b2af20255c0deff1ece565 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 13:06:44 +0200 Subject: [PATCH 003/380] Original rewrite of PhaseByTransmission: - Adapted to get the trio information from the SampleDB (i.e. from Pedigree file (ped)) => Multiple trios can be passed as argument - Mendelian violations and trio phasing possibilities are pre-calculated and stored in Maps. => Runtime is ~3x faster - Genotype combinations possible only given two MVs are now given a squared MV prior (e.g. 0/0+0/0=>1/1 is given 10^-16 prior if the MV prior is 10^-8) - Corrected bug: In case the best genotype combination is Het/Het/Het, the genotypes are now set appropriately (before original genotypes were left even if they weren't Het/Het/Het) - Basic reporting added: -- mvf argument let the user specify a file to report remaining MVs -- When the walker ends, some basic stats about the genotype reconfiguration and phasing are output Known problems: - GQ is not recalculated even if the genotype changes Possible improvements: - Phase partially typed trios - Use standard Allele/Genotype Classes for the storage of the pre-calculated phase --- .../walkers/phasing/PhaseByTransmission.java | 781 +++++++++++++----- 1 file changed, 569 insertions(+), 212 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 3eedc2a28..12b541526 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 @@ -7,18 +7,18 @@ import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgume 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.samples.Sample; import org.broadinstitute.sting.gatk.walkers.RodWalker; 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.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 java.io.File; -import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.*; /** @@ -32,13 +32,13 @@ import java.util.*; * the genotypes exhibit a Mendelian violation. This walker assumes there are only three samples in the VCF file to * begin. */ -public class PhaseByTransmission extends RodWalker { +public class PhaseByTransmission extends RodWalker, HashMap> { @ArgumentCollection protected StandardVariantContextInputArgumentCollection variantCollection = new StandardVariantContextInputArgumentCollection(); - @Argument(shortName="f", fullName="familySpec", required=true, doc="Patterns for the family structure (usage: mom+dad=child). Specify several trios by supplying this argument many times and/or a file containing many patterns.") - public ArrayList familySpecs = null; + @Argument(shortName = "mvf",required = false,fullName = "MendelianViolationsFile", doc="File to output the mendelian violation details.") + private PrintStream mvFile = null; @Output protected VCFWriter vcfWriter = null; @@ -48,239 +48,523 @@ public class PhaseByTransmission extends RodWalker { private final Double MENDELIAN_VIOLATION_PRIOR = 1e-8; - private class Trio { - private String mother; - private String father; - private String child; + private ArrayList trios = new ArrayList(); - public Trio(String mother, String father, String child) { - this.mother = mother; - this.father = father; - this.child = child; - } + //Matrix of priors for all genotype combinations + private EnumMap>> mvCountMatrix; - public Trio(String familySpec) { - String[] pieces = familySpec.split("[\\+\\=]"); + //Matrix of allele transmission + private EnumMap>> transmissionMatrix; - this.mother = pieces[0]; - this.father = pieces[1]; - this.child = pieces[2]; - } + //Metrics counters hashkeys + private final Byte NUM_TRIO_GENOTYPES_CALLED = 0; + private final Byte NUM_TRIO_GENOTYPES_NOCALL = 1; + private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; + private final Byte NUM_HET = 3; + private final Byte NUM_HET_HET_HET = 4; + private final Byte NUM_VIOLATIONS = 5; - public String getMother() { return mother; } - public String getFather() { return father; } - public String getChild() { return child; } + private enum AlleleType { + NO_CALL, + REF, + VAR, + UNPHASED_REF, + UNPHASED_VAR } - private ArrayList trios = new ArrayList(); + //Stores a trio-genotype + private class TrioPhase { - public ArrayList getFamilySpecsFromCommandLineInput(ArrayList familySpecs) { - if (familySpecs != null) { - // Let's first go through the list and see if we were given any files. We'll add every entry in the file to our - // spec list set, and treat the entries as if they had been specified on the command line. - ArrayList specs = new ArrayList(); - for (String familySpec : familySpecs) { - File specFile = new File(familySpec); + private ArrayList trioAlleles = new ArrayList(6); - try { - XReadLines reader = new XReadLines(specFile); + private ArrayList getAlleles(Genotype.Type genotype){ + ArrayList alleles = new ArrayList(2); + if(genotype == Genotype.Type.HOM_REF){ + alleles.add(AlleleType.REF); + alleles.add(AlleleType.REF); + } + else if(genotype == Genotype.Type.HET){ + alleles.add(AlleleType.REF); + alleles.add(AlleleType.VAR); + } + else if(genotype == Genotype.Type.HOM_VAR){ + alleles.add(AlleleType.VAR); + alleles.add(AlleleType.VAR); + } + else{ + alleles.add(AlleleType.NO_CALL); + alleles.add(AlleleType.NO_CALL); + } + return alleles; + } - List lines = reader.readLines(); - for (String line : lines) { - specs.add(new Trio(line)); - } - } catch (FileNotFoundException e) { - specs.add(new Trio(familySpec)); // not a file, so must be a family spec - } - } + private ArrayList phaseSingleIndividualAlleles(Genotype.Type genotype){ + if(genotype == Genotype.Type.HET){ + ArrayList phasedAlleles = new ArrayList(2); + phasedAlleles.add(AlleleType.UNPHASED_REF); + phasedAlleles.add(AlleleType.UNPHASED_VAR); + return phasedAlleles; + } + else + return getAlleles(genotype); + } - return specs; + private ArrayList phaseMonoParentFamilyAlleles(Genotype.Type parent, Genotype.Type child){ + ArrayList phasedAlleles = new ArrayList(4); + //Special case for Het/Het as it is ambiguous + if(parent == Genotype.Type.HET && child == Genotype.Type.HET){ + phasedAlleles.add(AlleleType.UNPHASED_REF); + phasedAlleles.add(AlleleType.UNPHASED_VAR); + phasedAlleles.add(AlleleType.UNPHASED_REF); + phasedAlleles.add(AlleleType.UNPHASED_VAR); } - return new ArrayList(); + ArrayList parentAlleles = getAlleles(parent); + ArrayList childAlleles = getAlleles(child); + + int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); + if(childTransmittedAlleleIndex > -1){ + phasedAlleles.add(parentAlleles.get(0)); + phasedAlleles.add(parentAlleles.get(1)); + phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + phasedAlleles.add(childAlleles.get(0)); + } + else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ + phasedAlleles.add(parentAlleles.get(1)); + phasedAlleles.add(parentAlleles.get(0)); + phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + phasedAlleles.add(childAlleles.get(0)); + } + else{ + parentAlleles.addAll(childAlleles); + for(AlleleType allele : parentAlleles){ + if(allele == AlleleType.REF){ + phasedAlleles.add(AlleleType.UNPHASED_REF); + } + else if(allele == AlleleType.VAR){ + phasedAlleles.add(AlleleType.UNPHASED_VAR); + } + else{ + phasedAlleles.add(AlleleType.NO_CALL); + } + } + } + + return phasedAlleles; + } + + private ArrayList phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + ArrayList phasedAlleles = new ArrayList(6); + + Set> possiblePhasedChildGenotypes = new HashSet>(); + ArrayList motherAlleles = getAlleles(mother); + ArrayList fatherAlleles = getAlleles(father); + ArrayList childAlleles = getAlleles(child); + + //Build all possible child genotypes for the given parent's genotypes + for (AlleleType momAllele : motherAlleles) { + for (AlleleType fatherAllele : fatherAlleles) { + ArrayList possiblePhasedChildAlleles = new ArrayList(2); + possiblePhasedChildAlleles.add(momAllele); + possiblePhasedChildAlleles.add(fatherAllele); + possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); + } + } + + for (ArrayList phasedChildGenotype : possiblePhasedChildGenotypes) { + int firstAlleleIndex = phasedChildGenotype.indexOf(childAlleles.get(0)); + int secondAlleleIndex = phasedChildGenotype.lastIndexOf(childAlleles.get(1)); + if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { + //Add mother's alleles + phasedAlleles.add(phasedChildGenotype.get(0)); + if(motherAlleles.get(0) != phasedAlleles.get(0)) + phasedAlleles.add(motherAlleles.get(0)); + else + phasedAlleles.add(motherAlleles.get(1)); + + //Add father's alleles + phasedAlleles.add(phasedChildGenotype.get(1)); + if(fatherAlleles.get(0) != phasedAlleles.get(2)) + phasedAlleles.add(fatherAlleles.get(0)); + else + phasedAlleles.add(fatherAlleles.get(1)); + + //Add child's alleles + phasedAlleles.addAll(phasedChildGenotype); + return phasedAlleles; + } + } + + //If this is reached then no phasing could be found + motherAlleles.addAll(fatherAlleles); + motherAlleles.addAll(childAlleles); + for(AlleleType allele : motherAlleles){ + if(allele == AlleleType.REF){ + phasedAlleles.add(AlleleType.UNPHASED_REF); + } + else if(allele == AlleleType.VAR){ + phasedAlleles.add(AlleleType.UNPHASED_VAR); + } + else{ + phasedAlleles.add(AlleleType.NO_CALL); + } + } + return phasedAlleles; + } + + + 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){ + trioAlleles.addAll(phaseSingleIndividualAlleles(mother)); + trioAlleles.addAll(phaseSingleIndividualAlleles(father)); + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.add(AlleleType.NO_CALL); + } + else if(mother == Genotype.Type.NO_CALL){ + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.add(AlleleType.NO_CALL); + if(father == Genotype.Type.NO_CALL){ + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.addAll(phaseSingleIndividualAlleles(child)); + } + else + trioAlleles.addAll(phaseMonoParentFamilyAlleles(father, child)); + } + else if(father == Genotype.Type.NO_CALL){ + trioAlleles.addAll(phaseMonoParentFamilyAlleles(mother, child)); + trioAlleles.add(2, AlleleType.NO_CALL); + trioAlleles.add(3, AlleleType.NO_CALL); + } + //Special case for Het/Het/Het as it is ambiguous + else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ + trioAlleles.add(AlleleType.UNPHASED_REF); + trioAlleles.add(AlleleType.UNPHASED_VAR); + trioAlleles.add(AlleleType.UNPHASED_REF); + trioAlleles.add(AlleleType.UNPHASED_VAR); + trioAlleles.add(AlleleType.UNPHASED_REF); + trioAlleles.add(AlleleType.UNPHASED_VAR); + } + //All family members have genotypes and at least one of them is not Het + else{ + trioAlleles = phaseFamilyAlleles(mother, father, child); + } + } + + public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, int transmissionProb,ArrayList phasedGenotypes){ + phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,trioAlleles.subList(0,2))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,trioAlleles.subList(2,4))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,trioAlleles.subList(4,6))); + return phasedGenotypes; + } + + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, List phasedAlleles){ + + //Add the transmission probability + Map genotypeAttributes = new HashMap(); + genotypeAttributes.putAll(genotype.getAttributes()); + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); + + boolean isPhased = true; + + List alleles = new ArrayList(2); + + //If unphased, return original genotype + for(AlleleType allele : phasedAlleles){ + if(allele == AlleleType.NO_CALL){ + genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); + return genotype; + } + //Otherwise add the appropriate allele + else if(allele == AlleleType.UNPHASED_REF){ + isPhased = false; + alleles.add(refAllele); + } + else if(allele == AlleleType.UNPHASED_VAR){ + isPhased = false; + alleles.add(altAllele); + } + else if(allele == AlleleType.REF){ + alleles.add(refAllele); + } + else if(allele == AlleleType.VAR){ + alleles.add(altAllele); + } + } + //TODO: Recalculate GQ field for the new genotype + //Remove the GQ attribute as it needs to be recalculated for the new genotype assignment + //double[] likelihoods = genotype.getLikelihoods().getAsVector(); + + //genotypeAttributes.put(VCFConstants.GENOTYPE_QUALITY_KEY,likelihoods[1]); + genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); + return new Genotype(genotype.getSampleName(), alleles, genotype.getNegLog10PError(), null, genotype.getAttributes(), isPhased); + } + + } /** * Parse the familial relationship specification, and initialize VCF writer */ public void initialize() { - trios = getFamilySpecsFromCommandLineInput(familySpecs); - ArrayList rodNames = new ArrayList(); rodNames.add(variantCollection.variants.getName()); - Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit(), rodNames); Set vcfSamples = SampleUtils.getSampleList(vcfRods, VariantContextUtils.GenotypeMergeType.REQUIRE_UNIQUE); + //Get the trios from the families passed as ped + setTrios(); + if(trios.size()<1) + throw new UserException.BadInput("No PED file passed or no trios found in PED file. Aborted."); + + Set headerLines = new HashSet(); headerLines.addAll(VCFUtils.getHeaderFields(this.getToolkit())); - headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Float, "Probability that the phase is correct given that the genotypes are correct")); + headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Integer, "Phred score of the phase given that the genotypes are correct")); headerLines.add(new VCFHeaderLine("source", SOURCE_NAME)); vcfWriter.writeHeader(new VCFHeader(headerLines, vcfSamples)); + + buildMatrices(); + + if(mvFile != null) + mvFile.println("#CHROM\tPOS\tFILTER\tAC\tFAMILY\tTP\tMOTHER_GT\tMOTHER_DP\tMOTHER_RAD\tMOTHER_AAD\tMOTHER_HRPL\tMOTHER_HETPL\tMOTHER_HAPL\tFATHER_GT\tFATHER_DP\tFATHER_RAD\tFATHER_AAD\tFATHER_HRPL\tFATHER_HETPL\tFATHER_HAPL\tCHILD_GT\tCHILD_DP\tCHILD_RAD\tCHILD_AAD\tCHILD_HRPL\tCHILD_HETPL\tCHILD_HAPL"); + } - private double computeTransmissionLikelihoodOfGenotypeConfiguration(Genotype mom, Genotype dad, Genotype child) { - double[] momLikelihoods = MathUtils.normalizeFromLog10(mom.getLikelihoods().getAsVector()); - double[] dadLikelihoods = MathUtils.normalizeFromLog10(dad.getLikelihoods().getAsVector()); - double[] childLikelihoods = MathUtils.normalizeFromLog10(child.getLikelihoods().getAsVector()); + /** + * Select Trios only + */ + private void setTrios(){ - int momIndex = mom.getType().ordinal() - 1; - int dadIndex = dad.getType().ordinal() - 1; - int childIndex = child.getType().ordinal() - 1; - - return momLikelihoods[momIndex]*dadLikelihoods[dadIndex]*childLikelihoods[childIndex]; - } - - private ArrayList createAllThreeGenotypes(Allele refAllele, Allele altAllele, Genotype g) { - List homRefAlleles = new ArrayList(); - homRefAlleles.add(refAllele); - homRefAlleles.add(refAllele); - Genotype homRef = new Genotype(g.getSampleName(), homRefAlleles, g.getNegLog10PError(), 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); - - List homVarAlleles = new ArrayList(); - homVarAlleles.add(altAllele); - homVarAlleles.add(altAllele); - Genotype homVar = new Genotype(g.getSampleName(), homVarAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); - - ArrayList genotypes = new ArrayList(); - genotypes.add(homRef); - genotypes.add(het); - genotypes.add(homVar); - - return genotypes; - } - - private int getNumberOfMatchingAlleles(Allele alleleToMatch, Genotype g) { - List alleles = g.getAlleles(); - int matchingAlleles = 0; - - for (Allele a : alleles) { - if (!alleleToMatch.equals(a)) { - matchingAlleles++; + Map> families = this.getSampleDB().getFamilies(); + Set family; + ArrayList parents; + for(String familyID : families.keySet()){ + family = families.get(familyID); + if(family.size()!=3){ + logger.info(String.format("Caution: Family %s has %d members; At the moment Phase By Transmission only supports trios. Family skipped.",familyID,family.size())); } - } - - return matchingAlleles; - } - - private boolean isMendelianViolation(Allele refAllele, Allele altAllele, Genotype mom, Genotype dad, Genotype child) { - int numMomRefAlleles = getNumberOfMatchingAlleles(refAllele, mom) > 0 ? 1 : 0; - int numMomAltAlleles = getNumberOfMatchingAlleles(altAllele, mom) > 0 ? 1 : 0; - - int numDadRefAlleles = getNumberOfMatchingAlleles(refAllele, dad) > 0 ? 1 : 0; - int numDadAltAlleles = getNumberOfMatchingAlleles(altAllele, dad) > 0 ? 1 : 0; - - int numChildRefAlleles = getNumberOfMatchingAlleles(refAllele, child); - int numChildAltAlleles = getNumberOfMatchingAlleles(altAllele, child); - - return (numMomRefAlleles + numDadRefAlleles < numChildRefAlleles || numMomAltAlleles + numDadAltAlleles < numChildAltAlleles); - } - - private ArrayList getPhasedGenotypes(Genotype mom, Genotype dad, Genotype child) { - Set possiblePhasedChildGenotypes = new HashSet(); - - for (Allele momAllele : mom.getAlleles()) { - for (Allele dadAllele : dad.getAlleles()) { - ArrayList possiblePhasedChildAlleles = new ArrayList(); - possiblePhasedChildAlleles.add(momAllele); - possiblePhasedChildAlleles.add(dadAllele); - - Genotype possiblePhasedChildGenotype = new Genotype(child.getSampleName(), possiblePhasedChildAlleles, child.getNegLog10PError(), child.getFilters(), child.getAttributes(), true); - - possiblePhasedChildGenotypes.add(possiblePhasedChildGenotype); - } - } - - ArrayList finalGenotypes = new ArrayList(); - - for (Genotype phasedChildGenotype : possiblePhasedChildGenotypes) { - if (child.sameGenotype(phasedChildGenotype, true)) { - Allele momTransmittedAllele = phasedChildGenotype.getAllele(0); - Allele momUntransmittedAllele = mom.getAllele(0) != momTransmittedAllele ? mom.getAllele(0) : mom.getAllele(1); - - ArrayList phasedMomAlleles = new ArrayList(); - phasedMomAlleles.add(momTransmittedAllele); - phasedMomAlleles.add(momUntransmittedAllele); - - Genotype phasedMomGenotype = new Genotype(mom.getSampleName(), phasedMomAlleles, mom.getNegLog10PError(), mom.getFilters(), mom.getAttributes(), true); - - Allele dadTransmittedAllele = phasedChildGenotype.getAllele(1); - Allele dadUntransmittedAllele = dad.getAllele(0) != dadTransmittedAllele ? dad.getAllele(0) : dad.getAllele(1); - - ArrayList phasedDadAlleles = new ArrayList(); - phasedDadAlleles.add(dadTransmittedAllele); - phasedDadAlleles.add(dadUntransmittedAllele); - - Genotype phasedDadGenotype = new Genotype(dad.getSampleName(), phasedDadAlleles, dad.getNegLog10PError(), dad.getFilters(), dad.getAttributes(), true); - - finalGenotypes.add(phasedMomGenotype); - finalGenotypes.add(phasedDadGenotype); - finalGenotypes.add(phasedChildGenotype); - - return finalGenotypes; - } - } - - finalGenotypes.add(mom); - finalGenotypes.add(dad); - finalGenotypes.add(child); - - return finalGenotypes; - } - - private ArrayList phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child) { - ArrayList finalGenotypes = new ArrayList(); - finalGenotypes.add(mother); - finalGenotypes.add(father); - finalGenotypes.add(child); - - if (mother.isCalled() && father.isCalled() && child.isCalled()) { - ArrayList possibleMotherGenotypes = createAllThreeGenotypes(ref, alt, mother); - ArrayList possibleFatherGenotypes = createAllThreeGenotypes(ref, alt, father); - ArrayList possibleChildGenotypes = createAllThreeGenotypes(ref, alt, child); - - double bestConfigurationLikelihood = 0.0; - double bestPrior = 0.0; - Genotype bestMotherGenotype = mother; - Genotype bestFatherGenotype = father; - Genotype bestChildGenotype = child; - - double norm = 0.0; - - for (Genotype motherGenotype : possibleMotherGenotypes) { - for (Genotype fatherGenotype : possibleFatherGenotypes) { - for (Genotype childGenotype : possibleChildGenotypes) { - double prior = isMendelianViolation(ref, alt, motherGenotype, fatherGenotype, childGenotype) ? MENDELIAN_VIOLATION_PRIOR : 1.0 - 12*MENDELIAN_VIOLATION_PRIOR; - double configurationLikelihood = computeTransmissionLikelihoodOfGenotypeConfiguration(motherGenotype, fatherGenotype, childGenotype); - norm += prior*configurationLikelihood; - - if (prior*configurationLikelihood > bestPrior*bestConfigurationLikelihood) { - bestConfigurationLikelihood = configurationLikelihood; - bestPrior = prior; - bestMotherGenotype = motherGenotype; - bestFatherGenotype = fatherGenotype; - bestChildGenotype = childGenotype; - } - } + else{ + for(Sample familyMember : family){ + parents = familyMember.getParents(); + if(parents.size()>0){ + if(family.containsAll(parents)) + this.trios.add(familyMember); + else + logger.info(String.format("Caution: Family %s is not a trio; At the moment Phase By Transmission only supports trios. Family skipped.",familyID)); + break; + } } } - if (!(bestMotherGenotype.isHet() && bestFatherGenotype.isHet() && bestChildGenotype.isHet())) { - Map attributes = new HashMap(); - attributes.putAll(bestChildGenotype.getAttributes()); - attributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, bestPrior*bestConfigurationLikelihood / norm); - bestChildGenotype = Genotype.modifyAttributes(bestChildGenotype, attributes); + } - finalGenotypes = getPhasedGenotypes(bestMotherGenotype, bestFatherGenotype, bestChildGenotype); + + + } + + private void buildMatrices(){ + mvCountMatrix = new EnumMap>>(Genotype.Type.class); + transmissionMatrix = new EnumMap>>(Genotype.Type.class); + for(Genotype.Type mother : Genotype.Type.values()){ + mvCountMatrix.put(mother,new EnumMap>(Genotype.Type.class)); + transmissionMatrix.put(mother,new EnumMap>(Genotype.Type.class)); + for(Genotype.Type father : Genotype.Type.values()){ + mvCountMatrix.get(mother).put(father,new EnumMap(Genotype.Type.class)); + transmissionMatrix.get(mother).put(father,new EnumMap(Genotype.Type.class)); + for(Genotype.Type child : Genotype.Type.values()){ + mvCountMatrix.get(mother).get(father).put(child, getCombinationMVCount(mother, father, child)); + transmissionMatrix.get(mother).get(father).put(child,new TrioPhase(mother,father,child)); + } + } + } + } + + private int getCombinationMVCount(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + + //Child is no call => No MV + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE) + return 0; + //Add parents with genotypes for the evaluation + ArrayList parents = new ArrayList(); + if (!(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE)) + parents.add(mother); + if (!(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE)) + parents.add(father); + + //Both parents no calls => No MV + if (parents.isEmpty()) + return 0; + + //If at least one parent had a genotype, then count the number of ref and alt alleles that can be passed + int parentsNumRefAlleles = 0; + int parentsNumAltAlleles = 0; + + for(Genotype.Type parent : parents){ + if(parent == Genotype.Type.HOM_REF){ + parentsNumRefAlleles++; + } + else if(parent == Genotype.Type.HET){ + parentsNumRefAlleles++; + parentsNumAltAlleles++; + } + else if(parent == Genotype.Type.HOM_VAR){ + parentsNumAltAlleles++; } } - return finalGenotypes; + //Case Child is HomRef + if(child == Genotype.Type.HOM_REF){ + if(parentsNumRefAlleles == parents.size()) + return 0; + else return (parents.size()-parentsNumRefAlleles); + } + + //Case child is HomVar + if(child == Genotype.Type.HOM_VAR){ + if(parentsNumAltAlleles == parents.size()) + return 0; + else return parents.size()-parentsNumAltAlleles; + } + + //Case child is Het + if(child == Genotype.Type.HET && ((parentsNumRefAlleles > 0 && parentsNumAltAlleles > 0) || parents.size()<2)) + return 0; + + //MV + return 1; + } + + private Double getCombinationPrior(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + + double nonMVPrior = 1.0 - 12*MENDELIAN_VIOLATION_PRIOR; + + //Child is no call => No MV + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE) + return nonMVPrior; + //Add parents with genotypes for the evaluation + ArrayList parents = new ArrayList(); + if (!(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE)) + parents.add(mother); + if (!(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE)) + parents.add(father); + + //Both parents no calls => No MV + if (parents.isEmpty()) + return nonMVPrior; + + //If at least one parent had a genotype, then count the number of ref and alt alleles that can be passed + int parentsNumRefAlleles = 0; + int parentsNumAltAlleles = 0; + + for(Genotype.Type parent : parents){ + if(parent == Genotype.Type.HOM_REF){ + parentsNumRefAlleles++; + } + else if(parent == Genotype.Type.HET){ + parentsNumRefAlleles++; + parentsNumAltAlleles++; + } + else if(parent == Genotype.Type.HOM_VAR){ + parentsNumAltAlleles++; + } + } + + //Case Child is HomRef + if(child == Genotype.Type.HOM_REF){ + if(parentsNumRefAlleles == parents.size()) + return nonMVPrior; + else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumRefAlleles); + } + + //Case child is HomVar + if(child == Genotype.Type.HOM_VAR){ + if(parentsNumAltAlleles == parents.size()) + return nonMVPrior; + else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumAltAlleles); + } + + //Case child is Het + if(child == Genotype.Type.HET && ((parentsNumRefAlleles > 0 && parentsNumAltAlleles > 0) || parents.size()<2)) + return nonMVPrior; + + //MV + return MENDELIAN_VIOLATION_PRIOR; + } + + private int countFamilyGenotypeDiff(Genotype.Type motherOriginal,Genotype.Type fatherOriginal,Genotype.Type childOriginal,Genotype.Type motherNew,Genotype.Type fatherNew,Genotype.Type childNew){ + int count = 0; + if(motherOriginal!=motherNew) + count++; + if(fatherOriginal!=fatherNew) + count++; + if(childOriginal!=childNew) + count++; + return count; + } + + + private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { + + //For now, only consider trios with complete information + //TODO: Phasing of trios with missing information + if(!(mother.isCalled() && father.isCalled() && child.isCalled())) { + finalGenotypes.add(mother); + finalGenotypes.add(father); + finalGenotypes.add(child); + return false; + } + + //Get the PL + Map motherLikelihoods = mother.getLikelihoods().getAsMap(true); + Map fatherLikelihoods = father.getLikelihoods().getAsMap(true); + Map childLikelihoods = child.getLikelihoods().getAsMap(true); + + //Prior vars + double bestConfigurationLikelihood = 0.0; + double norm = 0.0; + boolean isMV = false; + int bestConfigurationGenotypeDiffs=4; + Genotype.Type bestMotherGenotype = mother.getType(); + Genotype.Type bestFatherGenotype = father.getType(); + Genotype.Type bestChildGenotype = child.getType(); + + //Get the most likely combination + int mvCount; + double configurationLikelihood; + int configurationGenotypeDiffs; + for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ + for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ + for(Map.Entry childGenotype : childLikelihoods.entrySet()){ + mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); + configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-12*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + norm += configurationLikelihood; + configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); + //Keep this combination if + //It has a better likelihood + //Or it has the same likelihood but requires less changes from original genotypes + if ((configurationLikelihood > bestConfigurationLikelihood) || + (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { + bestConfigurationLikelihood = configurationLikelihood; + bestMotherGenotype = motherGenotype.getKey(); + bestFatherGenotype = fatherGenotype.getKey(); + bestChildGenotype = childGenotype.getKey(); + isMV = mvCount>0; + bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + } + } + } + } + + //Get the phased alleles for the genotype configuration + TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype).get(bestFatherGenotype).get(bestChildGenotype); + + //Return the phased genotypes + phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,MathUtils.probabilityToPhredScale(1-(bestConfigurationLikelihood / norm)),finalGenotypes); + return isMV; + } /** @@ -292,18 +576,34 @@ public class PhaseByTransmission extends RodWalker { * @return null */ @Override - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + public HashMap map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + + //Local cars to avoid lookups on increment + int numTrioGenotypesCalled = 0; + int numTrioGenotypesNoCall = 0; + int numTrioGenotypesPhased = 0; + int numHet = 0 ; + int numHetHetHet = 0; + int numMVs = 0; + if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); Map genotypeMap = vc.getGenotypes(); - for (Trio trio : trios) { - Genotype mother = vc.getGenotype(trio.getMother()); - Genotype father = vc.getGenotype(trio.getFather()); - Genotype child = vc.getGenotype(trio.getChild()); + boolean isMV; - ArrayList trioGenotypes = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child); + for (Sample sample : trios) { + Genotype mother = vc.getGenotype(sample.getMaternalID()); + Genotype father = vc.getGenotype(sample.getPaternalID()); + Genotype child = vc.getGenotype(sample.getID()); + + //Skip trios where any of the genotype is missing in the variant context + if(mother == null || father == null | child == null) + continue; + + ArrayList trioGenotypes = new ArrayList(3); + isMV = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); Genotype phasedMother = trioGenotypes.get(0); Genotype phasedFather = trioGenotypes.get(1); @@ -312,14 +612,47 @@ public class PhaseByTransmission extends RodWalker { genotypeMap.put(phasedMother.getSampleName(), phasedMother); genotypeMap.put(phasedFather.getSampleName(), phasedFather); genotypeMap.put(phasedChild.getSampleName(), phasedChild); + + //Increment metrics counters + if(phasedMother.isCalled() && phasedFather.isCalled() && phasedChild.isCalled()){ + numTrioGenotypesCalled++; + if(phasedMother.isPhased()) + numTrioGenotypesPhased++; + + if(phasedMother.isHet() || phasedFather.isHet() || phasedChild.isHet()){ + numHet++; + if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()){ + numHetHetHet++; + }else if(!phasedMother.isPhased()){ + int x =9; + } + } + + if(isMV){ + numMVs++; + if(mvFile != null) + mvFile.println(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())); + } + }else{ + numTrioGenotypesNoCall++; + } + } + VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypeMap); vcfWriter.add(newvc); } - return null; + HashMap metricsCounters = new HashMap(5); + metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,numTrioGenotypesCalled); + metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,numTrioGenotypesNoCall); + metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,numTrioGenotypesPhased); + metricsCounters.put(NUM_HET,numHet); + metricsCounters.put(NUM_HET_HET_HET,numHetHetHet); + metricsCounters.put(NUM_VIOLATIONS,numMVs); + return metricsCounters; } /** @@ -328,8 +661,15 @@ public class PhaseByTransmission extends RodWalker { * @return Initial value of reduce. */ @Override - public Integer reduceInit() { - return null; + public HashMap reduceInit() { + HashMap metricsCounters = new HashMap(5); + metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_HET,0); + metricsCounters.put(NUM_HET_HET_HET,0); + metricsCounters.put(NUM_VIOLATIONS,0); + return metricsCounters; } /** @@ -340,7 +680,24 @@ public class PhaseByTransmission extends RodWalker { * @return accumulator with result of the map taken into account. */ @Override - public Integer reduce(Integer value, Integer sum) { - return null; + public HashMap reduce(HashMap value, HashMap sum) { + sum.put(NUM_TRIO_GENOTYPES_CALLED,value.get(NUM_TRIO_GENOTYPES_CALLED)+sum.get(NUM_TRIO_GENOTYPES_CALLED)); + sum.put(NUM_TRIO_GENOTYPES_NOCALL,value.get(NUM_TRIO_GENOTYPES_NOCALL)+sum.get(NUM_TRIO_GENOTYPES_NOCALL)); + sum.put(NUM_TRIO_GENOTYPES_PHASED,value.get(NUM_TRIO_GENOTYPES_PHASED)+sum.get(NUM_TRIO_GENOTYPES_PHASED)); + sum.put(NUM_HET,value.get(NUM_HET)+sum.get(NUM_HET)); + sum.put(NUM_HET_HET_HET,value.get(NUM_HET_HET_HET)+sum.get(NUM_HET_HET_HET)); + sum.put(NUM_VIOLATIONS,value.get(NUM_VIOLATIONS)+sum.get(NUM_VIOLATIONS)); + return sum; + } + + + @Override + public void onTraversalDone(HashMap result) { + logger.info("Number of complete trio-genotypes: " + result.get(NUM_TRIO_GENOTYPES_CALLED)); + logger.info("Number of trio-genotypes containing no call(s): " + result.get(NUM_TRIO_GENOTYPES_NOCALL)); + logger.info("Number of trio-genotypes phased: " + result.get(NUM_TRIO_GENOTYPES_PHASED)); + logger.info("Number of resulting Hom/Hom/Hom trios: " + result.get(NUM_HET)); + logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_HET_HET_HET)); + logger.info("Number of remaining mendelian violations: " + result.get(NUM_VIOLATIONS)); } } From edea90786a4555ff665b07a3dc2f97ce7cbae24c Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 17:04:19 +0200 Subject: [PATCH 004/380] Genotype quality is now recalculated for each of the phased Genotypes. Small problem is that we unnecessarily loose a little precision on the genotypes that do not change after assignment. --- .../walkers/phasing/PhaseByTransmission.java | 9 ++---- .../variantcontext/GenotypeLikelihoods.java | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 7 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 12b541526..52629277f 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 @@ -266,6 +266,7 @@ public class PhaseByTransmission extends RodWalker, HashMa Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); + genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); boolean isPhased = true; @@ -274,7 +275,6 @@ public class PhaseByTransmission extends RodWalker, HashMa //If unphased, return original genotype for(AlleleType allele : phasedAlleles){ if(allele == AlleleType.NO_CALL){ - genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); return genotype; } //Otherwise add the appropriate allele @@ -293,13 +293,8 @@ public class PhaseByTransmission extends RodWalker, HashMa alleles.add(altAllele); } } - //TODO: Recalculate GQ field for the new genotype - //Remove the GQ attribute as it needs to be recalculated for the new genotype assignment - //double[] likelihoods = genotype.getLikelihoods().getAsVector(); - //genotypeAttributes.put(VCFConstants.GENOTYPE_QUALITY_KEY,likelihoods[1]); - genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); - return new Genotype(genotype.getSampleName(), alleles, genotype.getNegLog10PError(), null, genotype.getAttributes(), isPhased); + return new Genotype(genotype.getSampleName(), alleles, genotype.getLikelihoods().getLog10GQ(genotype.getType()), null, genotype.getAttributes(), isPhased); } 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 292bc17cd..9ed9b41cc 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -25,8 +25,10 @@ package org.broadinstitute.sting.utils.variantcontext; import org.broad.tribble.TribbleException; +import org.broadinstitute.sting.gatk.io.DirectOutputTracker; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.jgrapht.util.MathUtil; import java.util.EnumMap; import java.util.Map; @@ -98,6 +100,7 @@ public class GenotypeLikelihoods { return likelihoodsAsString_PLs; } + //Return genotype likelihoods as an EnumMap with Genotypes as keys and likelihoods as values public EnumMap getAsMap(boolean normalizeFromLog10){ //Make sure that the log10likelihoods are set double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); @@ -108,6 +111,32 @@ public class GenotypeLikelihoods { return likelihoodsMap; } + //Return the log10 Genotype Quality (GQ) for the given genotype + public double getLog10GQ(Genotype.Type genotype){ + + double qual = Double.NEGATIVE_INFINITY; + EnumMap likelihoods = getAsMap(false); + for(Map.Entry likelihood : likelihoods.entrySet()){ + if(likelihood.getKey() == genotype) + continue; + if(likelihood.getValue() > qual) + qual = likelihood.getValue(); + + } + + qual = likelihoods.get(genotype) - qual; + + 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 one + 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(","); From 01b16abc8dcc233e29258eaa4e1d1c0415180e91 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 10:24:41 +0200 Subject: [PATCH 005/380] Genotype quality calculation modified to handle all genotypes the same way. This is inconsistent with GQ output by the UG but is correct even for cases of poor quality genotypes. --- .../variantcontext/GenotypeLikelihoods.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) 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 9ed9b41cc..b83ffa2fd 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -111,8 +111,8 @@ public class GenotypeLikelihoods { return likelihoodsMap; } - //Return the log10 Genotype Quality (GQ) for the given genotype - public double getLog10GQ(Genotype.Type genotype){ + //Return the neg log10 Genotype Quality (GQ) for the given genotype + public double getNegLog10GQ(Genotype.Type genotype){ double qual = Double.NEGATIVE_INFINITY; EnumMap likelihoods = getAsMap(false); @@ -124,15 +124,9 @@ public class GenotypeLikelihoods { } - qual = likelihoods.get(genotype) - qual; - - 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 one - double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); - double chosenGenotype = normalized[genotype.ordinal()-1]; - qual = -1.0 * Math.log10(1.0 - chosenGenotype); - } + double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); + double chosenGenotype = normalized[genotype.ordinal()-1]; + qual = -1.0 * Math.log10(1.0 - chosenGenotype); return qual; } From 7312e35c71167dee3510bf428204b3a4b75ab52f Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 10:25:53 +0200 Subject: [PATCH 006/380] Now makes use of standard Allele and Genotype classes. This allowed quite some code cleaning. --- .../walkers/phasing/PhaseByTransmission.java | 341 +++++++----------- 1 file changed, 135 insertions(+), 206 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 52629277f..62b7bfffa 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 @@ -56,7 +56,7 @@ public class PhaseByTransmission extends RodWalker, HashMa //Matrix of allele transmission private EnumMap>> transmissionMatrix; - //Metrics counters hashkeys + //Metrics counters hash keys private final Byte NUM_TRIO_GENOTYPES_CALLED = 0; private final Byte NUM_TRIO_GENOTYPES_NOCALL = 1; private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; @@ -64,237 +64,223 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_HET_HET_HET = 4; private final Byte NUM_VIOLATIONS = 5; - private enum AlleleType { - NO_CALL, - REF, - VAR, - UNPHASED_REF, - UNPHASED_VAR + private enum FamilyMember { + MOTHER, + FATHER, + CHILD } //Stores a trio-genotype private class TrioPhase { - private ArrayList trioAlleles = new ArrayList(6); + //Create 2 fake alleles + //The actual bases will never be used but the Genotypes created using the alleles will be. + private final Allele REF = Allele.create("A",true); + private final Allele VAR = Allele.create("A",false); + private final Allele NO_CALL = Allele.create(".",false); + private final String DUMMY_NAME = "DummySample"; - private ArrayList getAlleles(Genotype.Type genotype){ - ArrayList alleles = new ArrayList(2); + private EnumMap trioPhasedGenotypes = new EnumMap(FamilyMember.class); + + private ArrayList getAlleles(Genotype.Type genotype){ + ArrayList alleles = new ArrayList(2); if(genotype == Genotype.Type.HOM_REF){ - alleles.add(AlleleType.REF); - alleles.add(AlleleType.REF); + alleles.add(REF); + alleles.add(REF); } else if(genotype == Genotype.Type.HET){ - alleles.add(AlleleType.REF); - alleles.add(AlleleType.VAR); + alleles.add(REF); + alleles.add(VAR); } else if(genotype == Genotype.Type.HOM_VAR){ - alleles.add(AlleleType.VAR); - alleles.add(AlleleType.VAR); + alleles.add(VAR); + alleles.add(VAR); + } + else if(genotype == Genotype.Type.NO_CALL){ + alleles.add(NO_CALL); + alleles.add(NO_CALL); } else{ - alleles.add(AlleleType.NO_CALL); - alleles.add(AlleleType.NO_CALL); + return null; } return alleles; } - private ArrayList phaseSingleIndividualAlleles(Genotype.Type genotype){ - if(genotype == Genotype.Type.HET){ - ArrayList phasedAlleles = new ArrayList(2); - phasedAlleles.add(AlleleType.UNPHASED_REF); - phasedAlleles.add(AlleleType.UNPHASED_VAR); - return phasedAlleles; + //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){ + if(genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HOM_VAR){ + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME, getAlleles(genotype), Genotype.NO_NEG_LOG_10PERROR, null, null, true)); } else - return getAlleles(genotype); + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - private ArrayList phaseMonoParentFamilyAlleles(Genotype.Type parent, Genotype.Type child){ - ArrayList phasedAlleles = new ArrayList(4); + private void phaseMonoParentFamilyAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ + //Special case for Het/Het as it is ambiguous - if(parent == Genotype.Type.HET && child == Genotype.Type.HET){ - phasedAlleles.add(AlleleType.UNPHASED_REF); - phasedAlleles.add(AlleleType.UNPHASED_VAR); - phasedAlleles.add(AlleleType.UNPHASED_REF); - phasedAlleles.add(AlleleType.UNPHASED_VAR); + if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - ArrayList parentAlleles = getAlleles(parent); - ArrayList childAlleles = getAlleles(child); + ArrayList parentAlleles = getAlleles(parentGenotype); + ArrayList childAlleles = getAlleles(childGenotype); + ArrayList parentPhasedAlleles = new ArrayList(2); + ArrayList childPhasedAlleles = new ArrayList(2); + //If there is a possible phasing between the mother and child => phase int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); if(childTransmittedAlleleIndex > -1){ - phasedAlleles.add(parentAlleles.get(0)); - phasedAlleles.add(parentAlleles.get(1)); - phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - phasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); } else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ - phasedAlleles.add(parentAlleles.get(1)); - phasedAlleles.add(parentAlleles.get(0)); - phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - phasedAlleles.add(childAlleles.get(0)); + parentPhasedAlleles.add(parentAlleles.get(1)); + parentPhasedAlleles.add(parentAlleles.get(0)); + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); } + //This is a Mendelian Violation => Do not phase else{ - parentAlleles.addAll(childAlleles); - for(AlleleType allele : parentAlleles){ - if(allele == AlleleType.REF){ - phasedAlleles.add(AlleleType.UNPHASED_REF); - } - else if(allele == AlleleType.VAR){ - phasedAlleles.add(AlleleType.UNPHASED_VAR); - } - else{ - phasedAlleles.add(AlleleType.NO_CALL); - } - } + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME,getAlleles(parentGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - - return phasedAlleles; } - private ArrayList phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - ArrayList phasedAlleles = new ArrayList(6); + private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - Set> possiblePhasedChildGenotypes = new HashSet>(); - ArrayList motherAlleles = getAlleles(mother); - ArrayList fatherAlleles = getAlleles(father); - ArrayList childAlleles = getAlleles(child); + Set> possiblePhasedChildGenotypes = new HashSet>(); + ArrayList motherAlleles = getAlleles(mother); + ArrayList fatherAlleles = getAlleles(father); + ArrayList childAlleles = getAlleles(child); //Build all possible child genotypes for the given parent's genotypes - for (AlleleType momAllele : motherAlleles) { - for (AlleleType fatherAllele : fatherAlleles) { - ArrayList possiblePhasedChildAlleles = new ArrayList(2); + for (Allele momAllele : motherAlleles) { + for (Allele fatherAllele : fatherAlleles) { + ArrayList possiblePhasedChildAlleles = new ArrayList(2); possiblePhasedChildAlleles.add(momAllele); possiblePhasedChildAlleles.add(fatherAllele); possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); } } - for (ArrayList phasedChildGenotype : possiblePhasedChildGenotypes) { - int firstAlleleIndex = phasedChildGenotype.indexOf(childAlleles.get(0)); - int secondAlleleIndex = phasedChildGenotype.lastIndexOf(childAlleles.get(1)); + for (ArrayList childPhasedAllelesAlleles : possiblePhasedChildGenotypes) { + int firstAlleleIndex = childPhasedAllelesAlleles.indexOf(childAlleles.get(0)); + int secondAlleleIndex = childPhasedAllelesAlleles.lastIndexOf(childAlleles.get(1)); + //If a possible combination has been found, create the genotypes if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { - //Add mother's alleles - phasedAlleles.add(phasedChildGenotype.get(0)); - if(motherAlleles.get(0) != phasedAlleles.get(0)) - phasedAlleles.add(motherAlleles.get(0)); + //Create mother's genotype + ArrayList motherPhasedAlleles = new ArrayList(2); + motherPhasedAlleles.add(childPhasedAllelesAlleles.get(0)); + if(motherAlleles.get(0) != motherPhasedAlleles.get(0)) + motherPhasedAlleles.add(motherAlleles.get(0)); else - phasedAlleles.add(motherAlleles.get(1)); + motherPhasedAlleles.add(motherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,motherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); - //Add father's alleles - phasedAlleles.add(phasedChildGenotype.get(1)); - if(fatherAlleles.get(0) != phasedAlleles.get(2)) - phasedAlleles.add(fatherAlleles.get(0)); + //Create father's genotype + ArrayList fatherPhasedAlleles = new ArrayList(2); + fatherPhasedAlleles.add(childPhasedAllelesAlleles.get(1)); + if(fatherAlleles.get(0) != fatherPhasedAlleles.get(0)) + fatherPhasedAlleles.add(fatherAlleles.get(0)); else - phasedAlleles.add(fatherAlleles.get(1)); + fatherPhasedAlleles.add(fatherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,fatherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); - //Add child's alleles - phasedAlleles.addAll(phasedChildGenotype); - return phasedAlleles; + //Create child's genotype + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,childPhasedAllelesAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Once a phased combination is found; exit + return; } } //If this is reached then no phasing could be found - motherAlleles.addAll(fatherAlleles); - motherAlleles.addAll(childAlleles); - for(AlleleType allele : motherAlleles){ - if(allele == AlleleType.REF){ - phasedAlleles.add(AlleleType.UNPHASED_REF); - } - else if(allele == AlleleType.VAR){ - phasedAlleles.add(AlleleType.UNPHASED_VAR); - } - else{ - phasedAlleles.add(AlleleType.NO_CALL); - } - } - return phasedAlleles; + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,getAlleles(mother),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,getAlleles(father),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } 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){ - trioAlleles.addAll(phaseSingleIndividualAlleles(mother)); - trioAlleles.addAll(phaseSingleIndividualAlleles(father)); - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.add(AlleleType.NO_CALL); + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } - else if(mother == Genotype.Type.NO_CALL){ - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.add(AlleleType.NO_CALL); - if(father == Genotype.Type.NO_CALL){ - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.addAll(phaseSingleIndividualAlleles(child)); + else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } else - trioAlleles.addAll(phaseMonoParentFamilyAlleles(father, child)); + phaseMonoParentFamilyAlleles(father, child, FamilyMember.FATHER); } - else if(father == Genotype.Type.NO_CALL){ - trioAlleles.addAll(phaseMonoParentFamilyAlleles(mother, child)); - trioAlleles.add(2, AlleleType.NO_CALL); - trioAlleles.add(3, AlleleType.NO_CALL); + else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + phaseMonoParentFamilyAlleles(mother, child, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); } //Special case for Het/Het/Het as it is ambiguous else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ - trioAlleles.add(AlleleType.UNPHASED_REF); - trioAlleles.add(AlleleType.UNPHASED_VAR); - trioAlleles.add(AlleleType.UNPHASED_REF); - trioAlleles.add(AlleleType.UNPHASED_VAR); - trioAlleles.add(AlleleType.UNPHASED_REF); - trioAlleles.add(AlleleType.UNPHASED_VAR); + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } //All family members have genotypes and at least one of them is not Het else{ - trioAlleles = phaseFamilyAlleles(mother, father, child); + phaseFamilyAlleles(mother, father, child); } } public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, int transmissionProb,ArrayList phasedGenotypes){ - phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,trioAlleles.subList(0,2))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,trioAlleles.subList(2,4))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,trioAlleles.subList(4,6))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); return phasedGenotypes; } - private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, List phasedAlleles){ + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, Genotype phasedGenotype){ //Add the transmission probability Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); - genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); - boolean isPhased = true; + //Handle missing genotype + if(!phasedGenotype.isAvailable()) + return new Genotype(genotype.getSampleName(), null); + //Handle NoCall genotype + else if(phasedGenotype.isNoCall()) + return new Genotype(genotype.getSampleName(), phasedGenotype.getAlleles()); - List alleles = new ArrayList(2); + ArrayList phasedAlleles = new ArrayList(2); + for(Allele allele : phasedGenotype.getAlleles()){ + if(allele.isReference()) + phasedAlleles.add(refAllele); + else if(allele.isNonReference()) + phasedAlleles.add(altAllele); + //At this point there should not be any other alleles left + else + throw new UserException(String.format("BUG: Unexpected allele: %s. Please report.",allele.toString())); - //If unphased, return original genotype - for(AlleleType allele : phasedAlleles){ - if(allele == AlleleType.NO_CALL){ - return genotype; - } - //Otherwise add the appropriate allele - else if(allele == AlleleType.UNPHASED_REF){ - isPhased = false; - alleles.add(refAllele); - } - else if(allele == AlleleType.UNPHASED_VAR){ - isPhased = false; - alleles.add(altAllele); - } - else if(allele == AlleleType.REF){ - alleles.add(refAllele); - } - else if(allele == AlleleType.VAR){ - alleles.add(altAllele); - } - } + } - return new Genotype(genotype.getSampleName(), alleles, genotype.getLikelihoods().getLog10GQ(genotype.getType()), null, genotype.getAttributes(), isPhased); + //Compute the new Log10Error if the genotype is different from the original genotype + double negLog10Error; + if(genotype.getType() == phasedGenotype.getType()) + negLog10Error = genotype.getNegLog10PError(); + else + negLog10Error = genotype.getLikelihoods().getNegLog10GQ(phasedGenotype.getType()); + + return new Genotype(genotype.getSampleName(), phasedAlleles, negLog10Error, null, genotypeAttributes, phasedGenotype.isPhased()); } @@ -432,63 +418,6 @@ public class PhaseByTransmission extends RodWalker, HashMa return 1; } - private Double getCombinationPrior(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - - double nonMVPrior = 1.0 - 12*MENDELIAN_VIOLATION_PRIOR; - - //Child is no call => No MV - if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE) - return nonMVPrior; - //Add parents with genotypes for the evaluation - ArrayList parents = new ArrayList(); - if (!(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE)) - parents.add(mother); - if (!(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE)) - parents.add(father); - - //Both parents no calls => No MV - if (parents.isEmpty()) - return nonMVPrior; - - //If at least one parent had a genotype, then count the number of ref and alt alleles that can be passed - int parentsNumRefAlleles = 0; - int parentsNumAltAlleles = 0; - - for(Genotype.Type parent : parents){ - if(parent == Genotype.Type.HOM_REF){ - parentsNumRefAlleles++; - } - else if(parent == Genotype.Type.HET){ - parentsNumRefAlleles++; - parentsNumAltAlleles++; - } - else if(parent == Genotype.Type.HOM_VAR){ - parentsNumAltAlleles++; - } - } - - //Case Child is HomRef - if(child == Genotype.Type.HOM_REF){ - if(parentsNumRefAlleles == parents.size()) - return nonMVPrior; - else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumRefAlleles); - } - - //Case child is HomVar - if(child == Genotype.Type.HOM_VAR){ - if(parentsNumAltAlleles == parents.size()) - return nonMVPrior; - else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumAltAlleles); - } - - //Case child is Het - if(child == Genotype.Type.HET && ((parentsNumRefAlleles > 0 && parentsNumAltAlleles > 0) || parents.size()<2)) - return nonMVPrior; - - //MV - return MENDELIAN_VIOLATION_PRIOR; - } - private int countFamilyGenotypeDiff(Genotype.Type motherOriginal,Genotype.Type fatherOriginal,Genotype.Type childOriginal,Genotype.Type motherNew,Genotype.Type fatherNew,Genotype.Type childNew){ int count = 0; if(motherOriginal!=motherNew) @@ -534,7 +463,7 @@ public class PhaseByTransmission extends RodWalker, HashMa for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ for(Map.Entry childGenotype : childLikelihoods.entrySet()){ mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); - configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-12*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); norm += configurationLikelihood; configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); //Keep this combination if From 38ebf3141a13ebafcc43b69c3364e0066ba17ea9 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 12:30:04 +0200 Subject: [PATCH 007/380] - Now supports parent/child pairs - Sites with missing genotypes in pairs/trios are handled as follows: -- Missing child -> Homozygous parents are phased, no transmission probability is emitted -- Two individuals missing -> Phase if homozygous, no transmission probability is emitted -- One parent missing -> Phased / transmission probability emitted - Mutation prior set as argument --- .../walkers/phasing/PhaseByTransmission.java | 134 ++++++++++-------- 1 file changed, 77 insertions(+), 57 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 62b7bfffa..d128ff40e 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 @@ -40,13 +40,16 @@ public class PhaseByTransmission extends RodWalker, HashMa @Argument(shortName = "mvf",required = false,fullName = "MendelianViolationsFile", doc="File to output the mendelian violation details.") private PrintStream mvFile = null; + @Argument(shortName = "prior",required = false,fullName = "DeNovoPrior", doc="Prior for de novo mutations. Default: 1e-8") + private double deNovoPrior=1e-8; + @Output protected VCFWriter vcfWriter = null; private final String TRANSMISSION_PROBABILITY_TAG_NAME = "TP"; private final String SOURCE_NAME = "PhaseByTransmission"; - private final Double MENDELIAN_VIOLATION_PRIOR = 1e-8; + public final double NO_TRANSMISSION_PROB = -1.0; private ArrayList trios = new ArrayList(); @@ -240,26 +243,26 @@ public class PhaseByTransmission extends RodWalker, HashMa } } - public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, int transmissionProb,ArrayList phasedGenotypes){ + public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); return phasedGenotypes; } - private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, Genotype phasedGenotype){ + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ + + //Handle null, missing and unavailable genotypes + //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. + if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + return genotype; //Add the transmission probability Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); - genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); - - //Handle missing genotype - if(!phasedGenotype.isAvailable()) - return new Genotype(genotype.getSampleName(), null); - //Handle NoCall genotype - else if(phasedGenotype.isNoCall()) - return new Genotype(genotype.getSampleName(), phasedGenotype.getAlleles()); + if(transmissionProb>NO_TRANSMISSION_PROB) + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); ArrayList phasedAlleles = new ArrayList(2); for(Allele allele : phasedGenotype.getAlleles()){ @@ -324,8 +327,8 @@ public class PhaseByTransmission extends RodWalker, HashMa ArrayList parents; for(String familyID : families.keySet()){ family = families.get(familyID); - if(family.size()!=3){ - logger.info(String.format("Caution: Family %s has %d members; At the moment Phase By Transmission only supports trios. Family skipped.",familyID,family.size())); + if(family.size()<2 || family.size()>3){ + logger.info(String.format("Caution: Family %s has %d members; At the moment Phase By Transmission only supports trios and parent/child pairs. Family skipped.",familyID,family.size())); } else{ for(Sample familyMember : family){ @@ -334,7 +337,7 @@ public class PhaseByTransmission extends RodWalker, HashMa if(family.containsAll(parents)) this.trios.add(familyMember); else - logger.info(String.format("Caution: Family %s is not a trio; At the moment Phase By Transmission only supports trios. Family skipped.",familyID)); + logger.info(String.format("Caution: Family %s skipped as it is not a trio nor a parent/child pair; At the moment Phase By Transmission only supports trios and parent/child pairs. Family skipped.",familyID)); break; } } @@ -429,64 +432,84 @@ public class PhaseByTransmission extends RodWalker, HashMa return count; } + private EnumMap getLikelihoodsAsMapSafeNull(Genotype genotype){ + if(genotype == null || !genotype.isAvailable()){ + EnumMap likelihoods = new EnumMap(Genotype.Type.class); + likelihoods.put(Genotype.Type.UNAVAILABLE,1.0); + return likelihoods; + } + else if(genotype.isNoCall()){ + EnumMap likelihoods = new EnumMap(Genotype.Type.class); + likelihoods.put(Genotype.Type.NO_CALL,1.0); + return likelihoods; + } + return genotype.getLikelihoods().getAsMap(true); + } + + private Genotype.Type getTypeSafeNull(Genotype genotype){ + if(genotype == null) + return Genotype.Type.UNAVAILABLE; + return genotype.getType(); + } + private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { - //For now, only consider trios with complete information - //TODO: Phasing of trios with missing information - if(!(mother.isCalled() && father.isCalled() && child.isCalled())) { - finalGenotypes.add(mother); - finalGenotypes.add(father); - finalGenotypes.add(child); - return false; - } - //Get the PL - Map motherLikelihoods = mother.getLikelihoods().getAsMap(true); - Map fatherLikelihoods = father.getLikelihoods().getAsMap(true); - Map childLikelihoods = child.getLikelihoods().getAsMap(true); + Map motherLikelihoods = getLikelihoodsAsMapSafeNull(mother); + Map fatherLikelihoods = getLikelihoodsAsMapSafeNull(father); + Map childLikelihoods = getLikelihoodsAsMapSafeNull(child); //Prior vars double bestConfigurationLikelihood = 0.0; double norm = 0.0; boolean isMV = false; int bestConfigurationGenotypeDiffs=4; - Genotype.Type bestMotherGenotype = mother.getType(); - Genotype.Type bestFatherGenotype = father.getType(); - Genotype.Type bestChildGenotype = child.getType(); + Genotype.Type bestMotherGenotype = getTypeSafeNull(mother); + Genotype.Type bestFatherGenotype = getTypeSafeNull(father); + Genotype.Type bestChildGenotype = getTypeSafeNull(child); //Get the most likely combination - int mvCount; - double configurationLikelihood; - int configurationGenotypeDiffs; - for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ - for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ - for(Map.Entry childGenotype : childLikelihoods.entrySet()){ - mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); - configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); - norm += configurationLikelihood; - configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); - //Keep this combination if - //It has a better likelihood - //Or it has the same likelihood but requires less changes from original genotypes - if ((configurationLikelihood > bestConfigurationLikelihood) || - (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { - bestConfigurationLikelihood = configurationLikelihood; - bestMotherGenotype = motherGenotype.getKey(); - bestFatherGenotype = fatherGenotype.getKey(); - bestChildGenotype = childGenotype.getKey(); - isMV = mvCount>0; - bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + //Only check for most likely combination if at least a parent and the child have genotypes + if(childLikelihoods.size()>2 && (motherLikelihoods.size() + fatherLikelihoods.size())>3){ + int mvCount; + double configurationLikelihood; + int configurationGenotypeDiffs; + for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ + for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ + for(Map.Entry childGenotype : childLikelihoods.entrySet()){ + mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); + configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + norm += configurationLikelihood; + configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); + //Keep this combination if + //It has a better likelihood + //Or it has the same likelihood but requires less changes from original genotypes + if ((configurationLikelihood > bestConfigurationLikelihood) || + (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { + bestConfigurationLikelihood = configurationLikelihood; + bestMotherGenotype = motherGenotype.getKey(); + bestFatherGenotype = fatherGenotype.getKey(); + bestChildGenotype = childGenotype.getKey(); + isMV = mvCount>0; + bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + } } } } + + //normalize the best configuration probability + bestConfigurationLikelihood = bestConfigurationLikelihood / norm; + } + else{ + bestConfigurationLikelihood = NO_TRANSMISSION_PROB; } //Get the phased alleles for the genotype configuration TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype).get(bestFatherGenotype).get(bestChildGenotype); //Return the phased genotypes - phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,MathUtils.probabilityToPhredScale(1-(bestConfigurationLikelihood / norm)),finalGenotypes); + phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); return isMV; } @@ -522,8 +545,8 @@ public class PhaseByTransmission extends RodWalker, HashMa Genotype father = vc.getGenotype(sample.getPaternalID()); Genotype child = vc.getGenotype(sample.getID()); - //Skip trios where any of the genotype is missing in the variant context - if(mother == null || father == null | child == null) + //Keep only trios and parent/child pairs + if(mother == null && father == null || child == null) continue; ArrayList trioGenotypes = new ArrayList(3); @@ -545,11 +568,8 @@ public class PhaseByTransmission extends RodWalker, HashMa if(phasedMother.isHet() || phasedFather.isHet() || phasedChild.isHet()){ numHet++; - if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()){ + if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()) numHetHetHet++; - }else if(!phasedMother.isPhased()){ - int x =9; - } } if(isMV){ From 62477a08104c3a158ccbb547ab5f347625676809 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 13:45:21 +0200 Subject: [PATCH 008/380] Added documentation and comments --- .../walkers/phasing/PhaseByTransmission.java | 127 ++++++++++++++---- 1 file changed, 103 insertions(+), 24 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 d128ff40e..c4c6b69b7 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 @@ -22,15 +22,57 @@ import java.io.PrintStream; import java.util.*; /** - * Phases a trio VCF (child phased by transmission, implied phase carried over to parents). Given genotypes for a trio, - * this walker modifies the genotypes (if necessary) to reflect the most likely configuration given the genotype - * likelihoods and inheritance constraints, phases child by transmission and carries over implied phase to the parents - * (their alleles in their genotypes are ordered as transmitted|untransmitted). Computes probability that the - * determined phase is correct given that the genotype configuration is correct (useful if you want to use this to - * compare phasing accuracy, but want to break that comparison down by phasing confidence in the truth set). Optionally - * filters out sites where the phasing is indeterminate (site has no-calls), ambiguous (everyone is heterozygous), or - * the genotypes exhibit a Mendelian violation. This walker assumes there are only three samples in the VCF file to - * begin. + * Computes the most likely genotype combination and phases trios and parent/child pairs + * + *

+ * PhaseByTransmission is a GATK tool that 1) computes the most likely genotype combination and phases trios and parent/child pairs given their genotype likelihoods and a mutation prior and 2) phases + * all sites were parent/child transmission can be inferred unambiguously. It reports the genotype combination (and hence phasing) probability. + * Ambiguous sites are: + *

    + *
  • Sites where all individuals are heterozygous
  • + *
  • Sites where there is a Mendelian violation
  • + *
+ * Missing genotypes are handled as follows: + *
    + *
  • In parent/child pairs: If an individual genotype is missing at one site, the other one is phased if it is homozygous. No phasing probability is emitted.
  • + *
  • In trios: If the child is missing, parents are treated as separate individuals and phased if homozygous. No phasing probability is emitted.
  • + *
  • In trios: If one of the parents is missing, it is handled like a parent/child pair. Phasing is done unless both the parent and child are heterozygous and a phasing probabilitt is emitted.
  • + *
  • In trios: If two individuals are missing, the remaining individual is phased if it is homozygous. No phasing probability is emitted.
  • + *
+ * + *

Input

+ *

+ *

    + *
  • A VCF variant set containing trio(s) and/or parent/child pair(s).
  • + *
  • A PED pedigree file containing the description of the individuals relationships.
  • + *
+ *

+ * + *

Options

+ *

+ *

    + *
  • MendelianViolationsFile: An optional argument for reporting. If a file is specified, all sites that remain in mendelian violation after being assigned the most likely genotype + * combination will be reported there. Information reported: chromosome, position, filter, allele count in VCF, family, transmission probability, + * and each individual genotype, depth, allelic depth and likelihoods.
  • + *
  • DeNovoPrior: Mutation prio; default is 1e-8
  • + *
+ *

+ * + *

Output

+ *

+ * An VCF with genotypes recalibrated as most likely under the familial constraint and phased by descent where non ambiguous.. + *

+ * + *

Examples

+ *
+ * java -Xmx2g -jar GenomeAnalysisTK.jar \
+ *   -R ref.fasta \
+ *   -T PhaseByTransmission \
+ *   -V input.vcf \
+ *   -ped input.ped \
+ *   -o output.vcf
+ * 
+ * */ public class PhaseByTransmission extends RodWalker, HashMap> { @@ -73,7 +115,8 @@ public class PhaseByTransmission extends RodWalker, HashMa CHILD } - //Stores a trio-genotype + //Stores a conceptual trio or parent/child pair genotype combination along with its phasing. + //This combination can then be "applied" to a given trio or pair using the getPhasedGenotypes method. private class TrioPhase { //Create 2 fake alleles @@ -119,7 +162,8 @@ public class PhaseByTransmission extends RodWalker, HashMa trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - private void phaseMonoParentFamilyAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ + //Find the phase for a parent/child pair + private void phasePairAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ //Special case for Het/Het as it is ambiguous if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ @@ -155,6 +199,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } } + //Phases a family by transmission private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ Set> possiblePhasedChildGenotypes = new HashSet>(); @@ -209,7 +254,10 @@ public class PhaseByTransmission extends RodWalker, HashMa trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - + /* Constructor: Creates a conceptual trio genotype combination from the given genotypes. + If one or more genotypes are set as NO_CALL or UNAVAILABLE, it will phase them like a pair + or single individual. + */ public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Take care of cases where one or more family members are no call @@ -225,10 +273,10 @@ public class PhaseByTransmission extends RodWalker, HashMa phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } else - phaseMonoParentFamilyAlleles(father, child, FamilyMember.FATHER); + phasePairAlleles(father, child, FamilyMember.FATHER); } else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ - phaseMonoParentFamilyAlleles(mother, child, FamilyMember.MOTHER); + phasePairAlleles(mother, child, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); } //Special case for Het/Het/Het as it is ambiguous @@ -243,11 +291,20 @@ public class PhaseByTransmission extends RodWalker, HashMa } } - public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ + /** + * Applies the trio genotype combination to the given trio. + * @param ref: Reference allele + * @param alt: Alternate allele + * @param motherGenotype: Genotype of the mother to phase using this trio genotype combination + * @param fatherGenotype: Genotype of the father to phase using this trio genotype combination + * @param childGenotype: Genotype of the child to phase using this trio genotype combination + * @param transmissionProb: Probability for this trio genotype combination to be correct (pass NO_TRANSMISSION_PROB if unavailable) + * @param phasedGenotypes: An ArrayList to which the newly phased genotypes are added in the following order: Mother, Father, Child + */ + public void getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); - return phasedGenotypes; } private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ @@ -290,7 +347,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Parse the familial relationship specification, and initialize VCF writer + * Parse the familial relationship specification, build the transmission matrices and initialize VCF writer */ public void initialize() { ArrayList rodNames = new ArrayList(); @@ -306,7 +363,7 @@ public class PhaseByTransmission extends RodWalker, HashMa Set headerLines = new HashSet(); headerLines.addAll(VCFUtils.getHeaderFields(this.getToolkit())); - headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Integer, "Phred score of the phase given that the genotypes are correct")); + headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Integer, "Phred score of the genotype combination and phase given that the genotypes are correct")); headerLines.add(new VCFHeaderLine("source", SOURCE_NAME)); vcfWriter.writeHeader(new VCFHeader(headerLines, vcfSamples)); @@ -318,7 +375,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Select Trios only + * Select trios and parent/child pairs only */ private void setTrios(){ @@ -349,6 +406,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } + //Create the transmission matrices private void buildMatrices(){ mvCountMatrix = new EnumMap>>(Genotype.Type.class); transmissionMatrix = new EnumMap>>(Genotype.Type.class); @@ -366,6 +424,9 @@ public class PhaseByTransmission extends RodWalker, HashMa } } + //Returns the number of Mendelian Violations for a given genotype combination. + //If one of the parents genotype is missing, it will consider it as a parent/child pair + //If the child genotype or both parents genotypes are missing, 0 is returned. private int getCombinationMVCount(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Child is no call => No MV @@ -421,6 +482,7 @@ public class PhaseByTransmission extends RodWalker, HashMa return 1; } + //Given two trio genotypes combinations, returns the number of different genotypes between the two combinations. private int countFamilyGenotypeDiff(Genotype.Type motherOriginal,Genotype.Type fatherOriginal,Genotype.Type childOriginal,Genotype.Type motherNew,Genotype.Type fatherNew,Genotype.Type childNew){ int count = 0; if(motherOriginal!=motherNew) @@ -432,6 +494,8 @@ public class PhaseByTransmission extends RodWalker, HashMa return count; } + //Get a Map of genotype likelihoods. If the genotype is NO_CALL or UNAVAILABLE, the Map will contain a single + //NO_CALL resp. UNAVAILABLE element with a likelihood of 1.0 private EnumMap getLikelihoodsAsMapSafeNull(Genotype genotype){ if(genotype == null || !genotype.isAvailable()){ EnumMap likelihoods = new EnumMap(Genotype.Type.class); @@ -446,6 +510,7 @@ public class PhaseByTransmission extends RodWalker, HashMa return genotype.getLikelihoods().getAsMap(true); } + //Returns the Genotype.Type; returns UNVAILABLE if given null private Genotype.Type getTypeSafeNull(Genotype genotype){ if(genotype == null) return Genotype.Type.UNAVAILABLE; @@ -453,6 +518,16 @@ public class PhaseByTransmission extends RodWalker, HashMa } + /** + * Phases the genotypes of the given trio. If one of the parents is null, it is considered a parent/child pair. + * @param ref: Reference allele + * @param alt: Alternative allele + * @param mother: Mother's genotype + * @param father: Father's genotype + * @param child: Child's genotype + * @param finalGenotypes: An ArrayList that will be added the genotypes phased by transmission in the following order: Mother, Father, Child + * @return + */ private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { //Get the PL @@ -600,9 +675,9 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Provide an initial value for reduce computations. + * Initializes the reporting counters. * - * @return Initial value of reduce. + * @return All counters initialized to 0 */ @Override public HashMap reduceInit() { @@ -617,10 +692,10 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Reduces a single map with the accumulator provided as the ReduceType. + * Adds the value of the site phased to the reporting counters. * - * @param value result of the map. - * @param sum accumulator for the reduce. + * @param value Site values + * @param sum accumulator for the reporting counters * @return accumulator with result of the map taken into account. */ @Override @@ -635,6 +710,10 @@ public class PhaseByTransmission extends RodWalker, HashMa } + /** + * Reports statistics on the phasing by transmission process. + * @param result Accumulator with all counters. + */ @Override public void onTraversalDone(HashMap result) { logger.info("Number of complete trio-genotypes: " + result.get(NUM_TRIO_GENOTYPES_CALLED)); From 62cff266d4e20aa2a48f88c47f282d9a6bc25287 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 26 Oct 2011 14:40:04 +0200 Subject: [PATCH 009/380] GQ calculation corrected for most likely genotype --- .../utils/variantcontext/GenotypeLikelihoods.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 b83ffa2fd..47c93bb1b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -124,10 +124,15 @@ public class GenotypeLikelihoods { } - double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); - double chosenGenotype = normalized[genotype.ordinal()-1]; - qual = -1.0 * Math.log10(1.0 - chosenGenotype); + //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; } From 81b163ff4d6a8203cb4cb155a92a751ee1b2349d Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 26 Oct 2011 14:49:12 +0200 Subject: [PATCH 010/380] Indentation --- .../walkers/phasing/PhaseByTransmission.java | 370 +++++++++--------- 1 file changed, 185 insertions(+), 185 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 c4c6b69b7..956cf7c89 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 @@ -129,220 +129,220 @@ public class PhaseByTransmission extends RodWalker, HashMa private EnumMap trioPhasedGenotypes = new EnumMap(FamilyMember.class); private ArrayList getAlleles(Genotype.Type genotype){ - ArrayList alleles = new ArrayList(2); - if(genotype == Genotype.Type.HOM_REF){ - alleles.add(REF); - alleles.add(REF); - } - else if(genotype == Genotype.Type.HET){ - alleles.add(REF); - alleles.add(VAR); - } - else if(genotype == Genotype.Type.HOM_VAR){ - 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; - } - - //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){ - if(genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HOM_VAR){ - trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME, getAlleles(genotype), Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - } - else - trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - } - - //Find the phase for a parent/child pair - private void phasePairAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ - - //Special case for Het/Het as it is ambiguous - if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + ArrayList alleles = new ArrayList(2); + if(genotype == Genotype.Type.HOM_REF){ + alleles.add(REF); + alleles.add(REF); + } + else if(genotype == Genotype.Type.HET){ + alleles.add(REF); + alleles.add(VAR); + } + else if(genotype == Genotype.Type.HOM_VAR){ + 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; } - ArrayList parentAlleles = getAlleles(parentGenotype); - ArrayList childAlleles = getAlleles(childGenotype); - ArrayList parentPhasedAlleles = new ArrayList(2); - ArrayList childPhasedAlleles = new ArrayList(2); - - //If there is a possible phasing between the mother and child => phase - int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); - if(childTransmittedAlleleIndex > -1){ - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - childPhasedAlleles.add(childAlleles.get(0)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + //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){ + if(genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HOM_VAR){ + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME, getAlleles(genotype), Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + } + else + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ - parentPhasedAlleles.add(parentAlleles.get(1)); - parentPhasedAlleles.add(parentAlleles.get(0)); - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - childPhasedAlleles.add(childAlleles.get(0)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - } - //This is a Mendelian Violation => Do not phase - else{ - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME,getAlleles(parentGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - } - } - //Phases a family by transmission - private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + //Find the phase for a parent/child pair + private void phasePairAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ - Set> possiblePhasedChildGenotypes = new HashSet>(); - ArrayList motherAlleles = getAlleles(mother); - ArrayList fatherAlleles = getAlleles(father); - ArrayList childAlleles = getAlleles(child); + //Special case for Het/Het as it is ambiguous + if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + } - //Build all possible child genotypes for the given parent's genotypes - for (Allele momAllele : motherAlleles) { - for (Allele fatherAllele : fatherAlleles) { - ArrayList possiblePhasedChildAlleles = new ArrayList(2); - possiblePhasedChildAlleles.add(momAllele); - possiblePhasedChildAlleles.add(fatherAllele); - possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); + ArrayList parentAlleles = getAlleles(parentGenotype); + ArrayList childAlleles = getAlleles(childGenotype); + ArrayList parentPhasedAlleles = new ArrayList(2); + ArrayList childPhasedAlleles = new ArrayList(2); + + //If there is a possible phasing between the mother and child => phase + int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); + if(childTransmittedAlleleIndex > -1){ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + } + else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ + parentPhasedAlleles.add(parentAlleles.get(1)); + parentPhasedAlleles.add(parentAlleles.get(0)); + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + } + //This is a Mendelian Violation => Do not phase + else{ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME,getAlleles(parentGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } } - for (ArrayList childPhasedAllelesAlleles : possiblePhasedChildGenotypes) { - int firstAlleleIndex = childPhasedAllelesAlleles.indexOf(childAlleles.get(0)); - int secondAlleleIndex = childPhasedAllelesAlleles.lastIndexOf(childAlleles.get(1)); - //If a possible combination has been found, create the genotypes - if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { - //Create mother's genotype - ArrayList motherPhasedAlleles = new ArrayList(2); - motherPhasedAlleles.add(childPhasedAllelesAlleles.get(0)); - if(motherAlleles.get(0) != motherPhasedAlleles.get(0)) - motherPhasedAlleles.add(motherAlleles.get(0)); - else - motherPhasedAlleles.add(motherAlleles.get(1)); - trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,motherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + //Phases a family by transmission + private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - //Create father's genotype - ArrayList fatherPhasedAlleles = new ArrayList(2); - fatherPhasedAlleles.add(childPhasedAllelesAlleles.get(1)); - if(fatherAlleles.get(0) != fatherPhasedAlleles.get(0)) - fatherPhasedAlleles.add(fatherAlleles.get(0)); - else - fatherPhasedAlleles.add(fatherAlleles.get(1)); - trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,fatherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + Set> possiblePhasedChildGenotypes = new HashSet>(); + ArrayList motherAlleles = getAlleles(mother); + ArrayList fatherAlleles = getAlleles(father); + ArrayList childAlleles = getAlleles(child); - //Create child's genotype - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,childPhasedAllelesAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); - - //Once a phased combination is found; exit - return; + //Build all possible child genotypes for the given parent's genotypes + for (Allele momAllele : motherAlleles) { + for (Allele fatherAllele : fatherAlleles) { + ArrayList possiblePhasedChildAlleles = new ArrayList(2); + possiblePhasedChildAlleles.add(momAllele); + possiblePhasedChildAlleles.add(fatherAllele); + possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); + } } + + for (ArrayList childPhasedAllelesAlleles : possiblePhasedChildGenotypes) { + int firstAlleleIndex = childPhasedAllelesAlleles.indexOf(childAlleles.get(0)); + int secondAlleleIndex = childPhasedAllelesAlleles.lastIndexOf(childAlleles.get(1)); + //If a possible combination has been found, create the genotypes + if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { + //Create mother's genotype + ArrayList motherPhasedAlleles = new ArrayList(2); + motherPhasedAlleles.add(childPhasedAllelesAlleles.get(0)); + if(motherAlleles.get(0) != motherPhasedAlleles.get(0)) + motherPhasedAlleles.add(motherAlleles.get(0)); + else + motherPhasedAlleles.add(motherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,motherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Create father's genotype + ArrayList fatherPhasedAlleles = new ArrayList(2); + fatherPhasedAlleles.add(childPhasedAllelesAlleles.get(1)); + if(fatherAlleles.get(0) != fatherPhasedAlleles.get(0)) + fatherPhasedAlleles.add(fatherAlleles.get(0)); + else + fatherPhasedAlleles.add(fatherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,fatherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Create child's genotype + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,childPhasedAllelesAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Once a phased combination is found; exit + return; + } + } + + //If this is reached then no phasing could be found + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,getAlleles(mother),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,getAlleles(father),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - //If this is reached then no phasing could be found - trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,getAlleles(mother),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,getAlleles(father),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - } + /* Constructor: Creates a conceptual trio genotype combination from the given genotypes. + If one or more genotypes are set as NO_CALL or UNAVAILABLE, it will phase them like a pair + or single individual. + */ + public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - /* Constructor: Creates a conceptual trio genotype combination from the given genotypes. - If one or more genotypes are set as NO_CALL or UNAVAILABLE, it will phase them like a pair - or single individual. - */ - 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){ - phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - phaseSingleIndividualAlleles(father, FamilyMember.FATHER); - phaseSingleIndividualAlleles(child, FamilyMember.CHILD); - } - else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ - phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + //Take care of cases where one or more family members are no call + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } - else - phasePairAlleles(father, child, FamilyMember.FATHER); + else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + 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){ + phasePairAlleles(mother, child, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + } + //Special case for Het/Het/Het as it is ambiguous + else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); + } + //All family members have genotypes and at least one of them is not Het + else{ + phaseFamilyAlleles(mother, father, child); + } } - else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ - phasePairAlleles(mother, child, FamilyMember.MOTHER); - phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + + /** + * Applies the trio genotype combination to the given trio. + * @param ref: Reference allele + * @param alt: Alternate allele + * @param motherGenotype: Genotype of the mother to phase using this trio genotype combination + * @param fatherGenotype: Genotype of the father to phase using this trio genotype combination + * @param childGenotype: Genotype of the child to phase using this trio genotype combination + * @param transmissionProb: Probability for this trio genotype combination to be correct (pass NO_TRANSMISSION_PROB if unavailable) + * @param phasedGenotypes: An ArrayList to which the newly phased genotypes are added in the following order: Mother, Father, Child + */ + public void getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ + phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); } - //Special case for Het/Het/Het as it is ambiguous - else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ - phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - phaseSingleIndividualAlleles(father, FamilyMember.FATHER); - phaseSingleIndividualAlleles(child, FamilyMember.CHILD); - } - //All family members have genotypes and at least one of them is not Het - else{ - phaseFamilyAlleles(mother, father, child); - } - } - /** - * Applies the trio genotype combination to the given trio. - * @param ref: Reference allele - * @param alt: Alternate allele - * @param motherGenotype: Genotype of the mother to phase using this trio genotype combination - * @param fatherGenotype: Genotype of the father to phase using this trio genotype combination - * @param childGenotype: Genotype of the child to phase using this trio genotype combination - * @param transmissionProb: Probability for this trio genotype combination to be correct (pass NO_TRANSMISSION_PROB if unavailable) - * @param phasedGenotypes: An ArrayList to which the newly phased genotypes are added in the following order: Mother, Father, Child - */ - public void getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ - phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); - } + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ - private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ + //Handle null, missing and unavailable genotypes + //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. + if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + return genotype; - //Handle null, missing and unavailable genotypes - //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. - if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) - return genotype; + //Add the transmission probability + Map genotypeAttributes = new HashMap(); + genotypeAttributes.putAll(genotype.getAttributes()); + if(transmissionProb>NO_TRANSMISSION_PROB) + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); - //Add the transmission probability - Map genotypeAttributes = new HashMap(); - genotypeAttributes.putAll(genotype.getAttributes()); - if(transmissionProb>NO_TRANSMISSION_PROB) - genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); + ArrayList phasedAlleles = new ArrayList(2); + for(Allele allele : phasedGenotype.getAlleles()){ + if(allele.isReference()) + phasedAlleles.add(refAllele); + else if(allele.isNonReference()) + phasedAlleles.add(altAllele); + //At this point there should not be any other alleles left + else + throw new UserException(String.format("BUG: Unexpected allele: %s. Please report.",allele.toString())); - ArrayList phasedAlleles = new ArrayList(2); - for(Allele allele : phasedGenotype.getAlleles()){ - if(allele.isReference()) - phasedAlleles.add(refAllele); - else if(allele.isNonReference()) - phasedAlleles.add(altAllele); - //At this point there should not be any other alleles left + } + + //Compute the new Log10Error if the genotype is different from the original genotype + double negLog10Error; + if(genotype.getType() == phasedGenotype.getType()) + negLog10Error = genotype.getNegLog10PError(); else - throw new UserException(String.format("BUG: Unexpected allele: %s. Please report.",allele.toString())); + negLog10Error = genotype.getLikelihoods().getNegLog10GQ(phasedGenotype.getType()); + return new Genotype(genotype.getSampleName(), phasedAlleles, negLog10Error, null, genotypeAttributes, phasedGenotype.isPhased()); } - //Compute the new Log10Error if the genotype is different from the original genotype - double negLog10Error; - if(genotype.getType() == phasedGenotype.getType()) - negLog10Error = genotype.getNegLog10PError(); - else - negLog10Error = genotype.getLikelihoods().getNegLog10GQ(phasedGenotype.getType()); - - return new Genotype(genotype.getSampleName(), phasedAlleles, negLog10Error, null, genotypeAttributes, phasedGenotype.isPhased()); - } - } From 1f044faedd01317b71c3abf91ac935beb6581223 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 26 Oct 2011 19:57:09 +0200 Subject: [PATCH 011/380] - Genotype assignment in case of equally likeli combination is now random - Genotype combinations with 0 confidence are now left unphased --- .../walkers/phasing/PhaseByTransmission.java | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 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 956cf7c89..f5e9ce6ea 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 @@ -109,6 +109,9 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_HET_HET_HET = 4; private final Byte NUM_VIOLATIONS = 5; + //Random number generator + private Random rand = new Random(); + private enum FamilyMember { MOTHER, FATHER, @@ -309,17 +312,22 @@ public class PhaseByTransmission extends RodWalker, HashMa private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ + int phredScoreTransmission = -1; + if(transmissionProb != NO_TRANSMISSION_PROB) + phredScoreTransmission = MathUtils.probabilityToPhredScale(1-(transmissionProb)); + //Handle null, missing and unavailable genotypes //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. - if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + //In addition, if the phasing confidence is 0, then return the unphased, original genotypes. + if(phredScoreTransmission ==0 || genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) return genotype; //Add the transmission probability Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); if(transmissionProb>NO_TRANSMISSION_PROB) - genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, phredScoreTransmission); ArrayList phasedAlleles = new ArrayList(2); for(Allele allele : phasedGenotype.getAlleles()){ @@ -538,36 +546,46 @@ public class PhaseByTransmission extends RodWalker, HashMa //Prior vars double bestConfigurationLikelihood = 0.0; double norm = 0.0; - boolean isMV = false; - int bestConfigurationGenotypeDiffs=4; - Genotype.Type bestMotherGenotype = getTypeSafeNull(mother); - Genotype.Type bestFatherGenotype = getTypeSafeNull(father); - Genotype.Type bestChildGenotype = getTypeSafeNull(child); + int configuration_index =0; + ArrayList isMV = new ArrayList(); + isMV.add(false); + ArrayList bestMotherGenotype = new ArrayList(); + bestMotherGenotype.add(getTypeSafeNull(mother)); + ArrayList bestFatherGenotype = new ArrayList(); + bestFatherGenotype.add(getTypeSafeNull(father)); + ArrayList bestChildGenotype = new ArrayList(); + bestChildGenotype.add(getTypeSafeNull(child)); //Get the most likely combination //Only check for most likely combination if at least a parent and the child have genotypes if(childLikelihoods.size()>2 && (motherLikelihoods.size() + fatherLikelihoods.size())>3){ int mvCount; double configurationLikelihood; - int configurationGenotypeDiffs; for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ for(Map.Entry childGenotype : childLikelihoods.entrySet()){ mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); norm += configurationLikelihood; - configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); //Keep this combination if //It has a better likelihood //Or it has the same likelihood but requires less changes from original genotypes - if ((configurationLikelihood > bestConfigurationLikelihood) || - (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { + if (configurationLikelihood > bestConfigurationLikelihood){ bestConfigurationLikelihood = configurationLikelihood; - bestMotherGenotype = motherGenotype.getKey(); - bestFatherGenotype = fatherGenotype.getKey(); - bestChildGenotype = childGenotype.getKey(); - isMV = mvCount>0; - bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + isMV.clear(); + isMV.add(mvCount>0); + bestMotherGenotype.clear(); + bestMotherGenotype.add(motherGenotype.getKey()); + bestFatherGenotype.clear(); + bestFatherGenotype.add(fatherGenotype.getKey()); + bestChildGenotype.clear(); + bestChildGenotype.add(childGenotype.getKey()); + } + else if(configurationLikelihood == bestConfigurationLikelihood) { + bestMotherGenotype.add(motherGenotype.getKey()); + bestFatherGenotype.add(fatherGenotype.getKey()); + bestChildGenotype.add(childGenotype.getKey()); + isMV.add(mvCount>0); } } } @@ -575,17 +593,22 @@ public class PhaseByTransmission extends RodWalker, HashMa //normalize the best configuration probability bestConfigurationLikelihood = bestConfigurationLikelihood / norm; + + //In case of multiple equally likely combinations, take a random one + if(bestMotherGenotype.size()>1){ + configuration_index = rand.nextInt(bestMotherGenotype.size()-1); + } + } else{ bestConfigurationLikelihood = NO_TRANSMISSION_PROB; } - //Get the phased alleles for the genotype configuration - TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype).get(bestFatherGenotype).get(bestChildGenotype); + TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype.get(configuration_index)).get(bestFatherGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); - //Return the phased genotypes - phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); - return isMV; + //Return the phased genotypes + phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); + return isMV.get(configuration_index); } From b91a9c4711d60b361a226a517a97e68217fdd5cf Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 2 Nov 2011 08:04:01 +0100 Subject: [PATCH 012/380] - Fixed parent/child pairs handling (was crashing before) - Added parent/child pair reporting --- .../walkers/phasing/PhaseByTransmission.java | 151 ++++++++++++------ 1 file changed, 101 insertions(+), 50 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 f5e9ce6ea..5b2024f96 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 @@ -105,9 +105,13 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_TRIO_GENOTYPES_CALLED = 0; private final Byte NUM_TRIO_GENOTYPES_NOCALL = 1; private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; - private final Byte NUM_HET = 3; - private final Byte NUM_HET_HET_HET = 4; - private final Byte NUM_VIOLATIONS = 5; + private final Byte NUM_TRIO_HET_HET_HET = 3; + private final Byte NUM_TRIO_VIOLATIONS = 4; + private final Byte NUM_PAIR_GENOTYPES_CALLED = 5; + private final Byte NUM_PAIR_GENOTYPES_NOCALL = 6; + private final Byte NUM_PAIR_GENOTYPES_PHASED = 7; + private final Byte NUM_PAIR_HET_HET = 8; + private final Byte NUM_PAIR_VIOLATIONS = 9; //Random number generator private Random rand = new Random(); @@ -172,6 +176,7 @@ public class PhaseByTransmission extends RodWalker, HashMa if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + return; } ArrayList parentAlleles = getAlleles(parentGenotype); @@ -612,6 +617,48 @@ public class PhaseByTransmission extends RodWalker, HashMa } + + private void updatePairMetricsCounters(Genotype parent, Genotype child, boolean isMV, HashMap counters){ + + //Increment metrics counters + if(parent.isCalled() && child.isCalled()){ + counters.put(NUM_PAIR_GENOTYPES_CALLED,counters.get(NUM_PAIR_GENOTYPES_CALLED)+1); + if(parent.isPhased()) + counters.put(NUM_PAIR_GENOTYPES_PHASED,counters.get(NUM_PAIR_GENOTYPES_PHASED)+1); + else{ + if(parent.isHet() && child.isHet()) + counters.put(NUM_PAIR_HET_HET,counters.get(NUM_PAIR_HET_HET)+1); + + else if(isMV) + counters.put(NUM_PAIR_VIOLATIONS,counters.get(NUM_PAIR_VIOLATIONS)+1); + } + }else{ + counters.put(NUM_PAIR_GENOTYPES_NOCALL,counters.get(NUM_PAIR_GENOTYPES_NOCALL)+1); + } + + } + + private void updateTrioMetricsCounters(Genotype mother, Genotype father, Genotype child, boolean isMV, HashMap counters){ + + //Increment metrics counters + if(mother.isCalled() && father.isCalled() && child.isCalled()){ + counters.put(NUM_TRIO_GENOTYPES_CALLED,counters.get(NUM_TRIO_GENOTYPES_CALLED)+1); + if(mother.isPhased()) + counters.put(NUM_TRIO_GENOTYPES_PHASED,counters.get(NUM_TRIO_GENOTYPES_PHASED)+1); + + else{ + if(mother.isHet() && father.isHet() && child.isHet()) + counters.put(NUM_TRIO_HET_HET_HET,counters.get(NUM_TRIO_HET_HET_HET)+1); + + else if(isMV) + counters.put(NUM_TRIO_VIOLATIONS,counters.get(NUM_TRIO_VIOLATIONS)+1); + + } + }else{ + counters.put(NUM_TRIO_GENOTYPES_NOCALL,counters.get(NUM_TRIO_GENOTYPES_NOCALL)+1); + } + } + /** * For each variant in the file, determine the phasing for the child and replace the child's genotype with the trio's genotype * @@ -623,13 +670,17 @@ public class PhaseByTransmission extends RodWalker, HashMa @Override public HashMap map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - //Local cars to avoid lookups on increment - int numTrioGenotypesCalled = 0; - int numTrioGenotypesNoCall = 0; - int numTrioGenotypesPhased = 0; - int numHet = 0 ; - int numHetHetHet = 0; - int numMVs = 0; + HashMap metricsCounters = new HashMap(10); + metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_TRIO_HET_HET_HET,0); + metricsCounters.put(NUM_TRIO_VIOLATIONS,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_PAIR_HET_HET,0); + metricsCounters.put(NUM_PAIR_VIOLATIONS,0); if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); @@ -654,30 +705,26 @@ public class PhaseByTransmission extends RodWalker, HashMa 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); - - //Increment metrics counters - if(phasedMother.isCalled() && phasedFather.isCalled() && phasedChild.isCalled()){ - numTrioGenotypesCalled++; - if(phasedMother.isPhased()) - numTrioGenotypesPhased++; - - if(phasedMother.isHet() || phasedFather.isHet() || phasedChild.isHet()){ - numHet++; - if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()) - numHetHetHet++; + //Fill the genotype map with the new genotypes and increment metrics counters + genotypeMap.put(phasedChild.getSampleName(),phasedChild); + if(mother != null){ + genotypeMap.put(phasedMother.getSampleName(), phasedMother); + if(father != null){ + genotypeMap.put(phasedFather.getSampleName(), phasedFather); + updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,isMV,metricsCounters); } - - if(isMV){ - numMVs++; - if(mvFile != null) - mvFile.println(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())); - } - }else{ - numTrioGenotypesNoCall++; + else + updatePairMetricsCounters(phasedMother,phasedChild,isMV,metricsCounters); } + else{ + genotypeMap.put(phasedFather.getSampleName(),phasedFather); + updatePairMetricsCounters(phasedFather,phasedChild,isMV,metricsCounters); + } + + //Report violation if set so + //TODO: ADAPT FOR PAIRS TOO!! + if(isMV && mvFile != null) + mvFile.println(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())); } @@ -686,14 +733,6 @@ public class PhaseByTransmission extends RodWalker, HashMa vcfWriter.add(newvc); } - - HashMap metricsCounters = new HashMap(5); - metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,numTrioGenotypesCalled); - metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,numTrioGenotypesNoCall); - metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,numTrioGenotypesPhased); - metricsCounters.put(NUM_HET,numHet); - metricsCounters.put(NUM_HET_HET_HET,numHetHetHet); - metricsCounters.put(NUM_VIOLATIONS,numMVs); return metricsCounters; } @@ -704,13 +743,17 @@ public class PhaseByTransmission extends RodWalker, HashMa */ @Override public HashMap reduceInit() { - HashMap metricsCounters = new HashMap(5); + HashMap metricsCounters = new HashMap(10); metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,0); metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,0); metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,0); - metricsCounters.put(NUM_HET,0); - metricsCounters.put(NUM_HET_HET_HET,0); - metricsCounters.put(NUM_VIOLATIONS,0); + metricsCounters.put(NUM_TRIO_HET_HET_HET,0); + metricsCounters.put(NUM_TRIO_VIOLATIONS,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_PAIR_HET_HET,0); + metricsCounters.put(NUM_PAIR_VIOLATIONS,0); return metricsCounters; } @@ -726,9 +769,13 @@ public class PhaseByTransmission extends RodWalker, HashMa sum.put(NUM_TRIO_GENOTYPES_CALLED,value.get(NUM_TRIO_GENOTYPES_CALLED)+sum.get(NUM_TRIO_GENOTYPES_CALLED)); sum.put(NUM_TRIO_GENOTYPES_NOCALL,value.get(NUM_TRIO_GENOTYPES_NOCALL)+sum.get(NUM_TRIO_GENOTYPES_NOCALL)); sum.put(NUM_TRIO_GENOTYPES_PHASED,value.get(NUM_TRIO_GENOTYPES_PHASED)+sum.get(NUM_TRIO_GENOTYPES_PHASED)); - sum.put(NUM_HET,value.get(NUM_HET)+sum.get(NUM_HET)); - sum.put(NUM_HET_HET_HET,value.get(NUM_HET_HET_HET)+sum.get(NUM_HET_HET_HET)); - sum.put(NUM_VIOLATIONS,value.get(NUM_VIOLATIONS)+sum.get(NUM_VIOLATIONS)); + sum.put(NUM_TRIO_HET_HET_HET,value.get(NUM_TRIO_HET_HET_HET)+sum.get(NUM_TRIO_HET_HET_HET)); + sum.put(NUM_TRIO_VIOLATIONS,value.get(NUM_TRIO_VIOLATIONS)+sum.get(NUM_TRIO_VIOLATIONS)); + sum.put(NUM_PAIR_GENOTYPES_CALLED,value.get(NUM_PAIR_GENOTYPES_CALLED)+sum.get(NUM_PAIR_GENOTYPES_CALLED)); + sum.put(NUM_PAIR_GENOTYPES_NOCALL,value.get(NUM_PAIR_GENOTYPES_NOCALL)+sum.get(NUM_PAIR_GENOTYPES_NOCALL)); + sum.put(NUM_PAIR_GENOTYPES_PHASED,value.get(NUM_PAIR_GENOTYPES_PHASED)+sum.get(NUM_PAIR_GENOTYPES_PHASED)); + sum.put(NUM_PAIR_HET_HET,value.get(NUM_PAIR_HET_HET)+sum.get(NUM_PAIR_HET_HET)); + sum.put(NUM_PAIR_VIOLATIONS,value.get(NUM_PAIR_VIOLATIONS)+sum.get(NUM_PAIR_VIOLATIONS)); return sum; } @@ -742,8 +789,12 @@ public class PhaseByTransmission extends RodWalker, HashMa logger.info("Number of complete trio-genotypes: " + result.get(NUM_TRIO_GENOTYPES_CALLED)); logger.info("Number of trio-genotypes containing no call(s): " + result.get(NUM_TRIO_GENOTYPES_NOCALL)); logger.info("Number of trio-genotypes phased: " + result.get(NUM_TRIO_GENOTYPES_PHASED)); - logger.info("Number of resulting Hom/Hom/Hom trios: " + result.get(NUM_HET)); - logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_HET_HET_HET)); - logger.info("Number of remaining mendelian violations: " + result.get(NUM_VIOLATIONS)); + logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_TRIO_HET_HET_HET)); + logger.info("Number of remaining mendelian violations in trios: " + result.get(NUM_TRIO_VIOLATIONS)); + logger.info("Number of complete pair-genotypes: " + result.get(NUM_PAIR_GENOTYPES_CALLED)); + logger.info("Number of pair-genotypes containing no call(s): " + result.get(NUM_PAIR_GENOTYPES_NOCALL)); + logger.info("Number of pair-genotypes phased: " + result.get(NUM_PAIR_GENOTYPES_PHASED)); + logger.info("Number of resulting Het/Het pairs: " + result.get(NUM_PAIR_HET_HET)); + logger.info("Number of remaining mendelian violations in pairs: " + result.get(NUM_PAIR_VIOLATIONS)); } } From 119ca7d742371423060bcc7150f908e1cb04de8a Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 2 Nov 2011 08:22:33 +0100 Subject: [PATCH 013/380] Fixed a bug in parent/child pairs reporting causing a crash in case the -mvf option was used and mother was not provided --- .../sting/gatk/walkers/phasing/PhaseByTransmission.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 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 5b2024f96..806a84e9d 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 @@ -681,6 +681,7 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); + String mvfLine = ""; if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); @@ -712,19 +713,23 @@ public class PhaseByTransmission extends RodWalker, HashMa if(father != null){ genotypeMap.put(phasedFather.getSampleName(), phasedFather); updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,isMV,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()); } - else + else{ updatePairMetricsCounters(phasedMother,phasedChild,isMV,metricsCounters); + mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t.:.:.:.\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(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); + } } else{ genotypeMap.put(phasedFather.getSampleName(),phasedFather); updatePairMetricsCounters(phasedFather,phasedChild,isMV,metricsCounters); + mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t.:.:.:.\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedFather.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),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()); } //Report violation if set so //TODO: ADAPT FOR PAIRS TOO!! if(isMV && mvFile != null) - mvFile.println(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())); + mvFile.println(mvfLine); } From 19ad5b635ad46f119ed1beccccf7bb2bc6a18aef Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 2 Nov 2011 18:35:31 +0100 Subject: [PATCH 014/380] - Calculation of parent/child pairs corrected - Separated the reporting of single and double mendelian violations in trios --- .../walkers/phasing/PhaseByTransmission.java | 177 ++++++++++++------ 1 file changed, 120 insertions(+), 57 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 806a84e9d..3c39b58d3 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 @@ -107,6 +107,7 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; private final Byte NUM_TRIO_HET_HET_HET = 3; private final Byte NUM_TRIO_VIOLATIONS = 4; + private final Byte NUM_TRIO_DOUBLE_VIOLATIONS = 10; private final Byte NUM_PAIR_GENOTYPES_CALLED = 5; private final Byte NUM_PAIR_GENOTYPES_NOCALL = 6; private final Byte NUM_PAIR_GENOTYPES_PHASED = 7; @@ -507,17 +508,14 @@ public class PhaseByTransmission extends RodWalker, HashMa return count; } - //Get a Map of genotype likelihoods. If the genotype is NO_CALL or UNAVAILABLE, the Map will contain a single - //NO_CALL resp. UNAVAILABLE element with a likelihood of 1.0 + //Get a Map of genotype likelihoods. + //In case of null, unavailable or no call, all likelihoods are 1/3. private EnumMap getLikelihoodsAsMapSafeNull(Genotype genotype){ - if(genotype == null || !genotype.isAvailable()){ + if(genotype == null || !genotype.isCalled()){ EnumMap likelihoods = new EnumMap(Genotype.Type.class); - likelihoods.put(Genotype.Type.UNAVAILABLE,1.0); - return likelihoods; - } - else if(genotype.isNoCall()){ - EnumMap likelihoods = new EnumMap(Genotype.Type.class); - likelihoods.put(Genotype.Type.NO_CALL,1.0); + likelihoods.put(Genotype.Type.HOM_REF,1.0/3.0); + likelihoods.put(Genotype.Type.HET,1.0/3.0); + likelihoods.put(Genotype.Type.HOM_VAR,1.0/3.0); return likelihoods; } return genotype.getLikelihoods().getAsMap(true); @@ -541,57 +539,113 @@ public class PhaseByTransmission extends RodWalker, HashMa * @param finalGenotypes: An ArrayList that will be added the genotypes phased by transmission in the following order: Mother, Father, Child * @return */ - private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { + private int phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { - //Get the PL - Map motherLikelihoods = getLikelihoodsAsMapSafeNull(mother); - Map fatherLikelihoods = getLikelihoodsAsMapSafeNull(father); + //Check whether it is a pair or trio + //Always assign the first parent as the parent having genotype information in pairs + //Always assign the mother as the first parent in trios + int parentsCalled = 0; + Map firstParentLikelihoods; + Map secondParentLikelihoods; + Genotype.Type pairSecondParentGenotype = null; + if(mother == null || !mother.isCalled()){ + firstParentLikelihoods = getLikelihoodsAsMapSafeNull(father); + secondParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); + pairSecondParentGenotype = mother == null ? Genotype.Type.UNAVAILABLE : mother.getType(); + if(father != null && father.isCalled()) + parentsCalled = 1; + } + else{ + firstParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); + secondParentLikelihoods = getLikelihoodsAsMapSafeNull(father); + if(father == null || !father.isCalled()){ + parentsCalled = 1; + pairSecondParentGenotype = father == null ? Genotype.Type.UNAVAILABLE : father.getType(); + }else{ + parentsCalled = 2; + } + } Map childLikelihoods = getLikelihoodsAsMapSafeNull(child); //Prior vars double bestConfigurationLikelihood = 0.0; double norm = 0.0; int configuration_index =0; - ArrayList isMV = new ArrayList(); - isMV.add(false); - ArrayList bestMotherGenotype = new ArrayList(); - bestMotherGenotype.add(getTypeSafeNull(mother)); - ArrayList bestFatherGenotype = new ArrayList(); - bestFatherGenotype.add(getTypeSafeNull(father)); + ArrayList bestMVCount = new ArrayList(); + bestMVCount.add(0); + + ArrayList bestFirstParentGenotype = new ArrayList(); + ArrayList bestSecondParentGenotype = new ArrayList(); ArrayList bestChildGenotype = new ArrayList(); + bestFirstParentGenotype.add(getTypeSafeNull(mother)); + bestSecondParentGenotype.add(getTypeSafeNull(father)); bestChildGenotype.add(getTypeSafeNull(child)); //Get the most likely combination //Only check for most likely combination if at least a parent and the child have genotypes - if(childLikelihoods.size()>2 && (motherLikelihoods.size() + fatherLikelihoods.size())>3){ + if(child.isCalled() && parentsCalled > 0){ int mvCount; - double configurationLikelihood; - for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ - for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ - for(Map.Entry childGenotype : childLikelihoods.entrySet()){ - mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); - configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + int cumulativeMVCount = 0; + double configurationLikelihood = 0; + for(Map.Entry childGenotype : childLikelihoods.entrySet()){ + for(Map.Entry firstParentGenotype : firstParentLikelihoods.entrySet()){ + for(Map.Entry secondParentGenotype : secondParentLikelihoods.entrySet()){ + mvCount = mvCountMatrix.get(firstParentGenotype.getKey()).get(secondParentGenotype.getKey()).get(childGenotype.getKey()); + //For parent/child pairs, sum over the possible genotype configurations of the missing parent + if(parentsCalled<2){ + cumulativeMVCount += mvCount; + configurationLikelihood += mvCount>0 ? Math.pow(deNovoPrior,mvCount)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue(); + } + //Evaluate configurations of trios + else{ + configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue(); + norm += configurationLikelihood; + //Keep this combination if + //It has a better likelihood + //Or it has the same likelihood but requires less changes from original genotypes + if (configurationLikelihood > bestConfigurationLikelihood){ + bestConfigurationLikelihood = configurationLikelihood; + bestMVCount.clear(); + bestMVCount.add(mvCount); + bestFirstParentGenotype.clear(); + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.clear(); + bestSecondParentGenotype.add(secondParentGenotype.getKey()); + bestChildGenotype.clear(); + bestChildGenotype.add(childGenotype.getKey()); + } + else if(configurationLikelihood == bestConfigurationLikelihood) { + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.add(secondParentGenotype.getKey()); + bestChildGenotype.add(childGenotype.getKey()); + bestMVCount.add(mvCount); + } + } + } + //Evaluate configurations of parent/child pairs + if(parentsCalled<2){ norm += configurationLikelihood; //Keep this combination if //It has a better likelihood //Or it has the same likelihood but requires less changes from original genotypes if (configurationLikelihood > bestConfigurationLikelihood){ bestConfigurationLikelihood = configurationLikelihood; - isMV.clear(); - isMV.add(mvCount>0); - bestMotherGenotype.clear(); - bestMotherGenotype.add(motherGenotype.getKey()); - bestFatherGenotype.clear(); - bestFatherGenotype.add(fatherGenotype.getKey()); + bestMVCount.clear(); + bestMVCount.add(cumulativeMVCount/3); bestChildGenotype.clear(); + bestFirstParentGenotype.clear(); + bestSecondParentGenotype.clear(); bestChildGenotype.add(childGenotype.getKey()); + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.add(pairSecondParentGenotype); } else if(configurationLikelihood == bestConfigurationLikelihood) { - bestMotherGenotype.add(motherGenotype.getKey()); - bestFatherGenotype.add(fatherGenotype.getKey()); + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.add(pairSecondParentGenotype); bestChildGenotype.add(childGenotype.getKey()); - isMV.add(mvCount>0); + bestMVCount.add(cumulativeMVCount/3); } + configurationLikelihood = 0; } } } @@ -600,8 +654,8 @@ public class PhaseByTransmission extends RodWalker, HashMa bestConfigurationLikelihood = bestConfigurationLikelihood / norm; //In case of multiple equally likely combinations, take a random one - if(bestMotherGenotype.size()>1){ - configuration_index = rand.nextInt(bestMotherGenotype.size()-1); + if(bestFirstParentGenotype.size()>1){ + configuration_index = rand.nextInt(bestFirstParentGenotype.size()-1); } } @@ -609,16 +663,20 @@ public class PhaseByTransmission extends RodWalker, HashMa bestConfigurationLikelihood = NO_TRANSMISSION_PROB; } - TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype.get(configuration_index)).get(bestFatherGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); + TrioPhase phasedTrioGenotypes; + if(parentsCalled < 2 && mother == null || !mother.isCalled()) + phasedTrioGenotypes = transmissionMatrix.get(bestSecondParentGenotype.get(configuration_index)).get(bestFirstParentGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); + else + phasedTrioGenotypes = transmissionMatrix.get(bestFirstParentGenotype.get(configuration_index)).get(bestSecondParentGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); //Return the phased genotypes phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); - return isMV.get(configuration_index); + return bestMVCount.get(configuration_index); } - private void updatePairMetricsCounters(Genotype parent, Genotype child, boolean isMV, HashMap counters){ + private void updatePairMetricsCounters(Genotype parent, Genotype child, int mvCount, HashMap counters){ //Increment metrics counters if(parent.isCalled() && child.isCalled()){ @@ -626,11 +684,9 @@ public class PhaseByTransmission extends RodWalker, HashMa if(parent.isPhased()) counters.put(NUM_PAIR_GENOTYPES_PHASED,counters.get(NUM_PAIR_GENOTYPES_PHASED)+1); else{ + counters.put(NUM_PAIR_VIOLATIONS,counters.get(NUM_PAIR_VIOLATIONS)+mvCount); if(parent.isHet() && child.isHet()) counters.put(NUM_PAIR_HET_HET,counters.get(NUM_PAIR_HET_HET)+1); - - else if(isMV) - counters.put(NUM_PAIR_VIOLATIONS,counters.get(NUM_PAIR_VIOLATIONS)+1); } }else{ counters.put(NUM_PAIR_GENOTYPES_NOCALL,counters.get(NUM_PAIR_GENOTYPES_NOCALL)+1); @@ -638,7 +694,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } - private void updateTrioMetricsCounters(Genotype mother, Genotype father, Genotype child, boolean isMV, HashMap counters){ + private void updateTrioMetricsCounters(Genotype mother, Genotype father, Genotype child, int mvCount, HashMap counters){ //Increment metrics counters if(mother.isCalled() && father.isCalled() && child.isCalled()){ @@ -647,11 +703,14 @@ public class PhaseByTransmission extends RodWalker, HashMa counters.put(NUM_TRIO_GENOTYPES_PHASED,counters.get(NUM_TRIO_GENOTYPES_PHASED)+1); else{ - if(mother.isHet() && father.isHet() && child.isHet()) - counters.put(NUM_TRIO_HET_HET_HET,counters.get(NUM_TRIO_HET_HET_HET)+1); - - else if(isMV) - counters.put(NUM_TRIO_VIOLATIONS,counters.get(NUM_TRIO_VIOLATIONS)+1); + if(mvCount > 0){ + if(mvCount >1) + counters.put(NUM_TRIO_DOUBLE_VIOLATIONS,counters.get(NUM_TRIO_DOUBLE_VIOLATIONS)+1); + else + counters.put(NUM_TRIO_VIOLATIONS,counters.get(NUM_TRIO_VIOLATIONS)+1); + } + else if(mother.isHet() && father.isHet() && child.isHet()) + counters.put(NUM_TRIO_HET_HET_HET,counters.get(NUM_TRIO_HET_HET_HET)+1); } }else{ @@ -681,14 +740,15 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); - String mvfLine = ""; + metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + String mvfLine; if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); Map genotypeMap = vc.getGenotypes(); - boolean isMV; + int mvCount; for (Sample sample : trios) { Genotype mother = vc.getGenotype(sample.getMaternalID()); @@ -700,7 +760,7 @@ public class PhaseByTransmission extends RodWalker, HashMa continue; ArrayList trioGenotypes = new ArrayList(3); - isMV = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); + mvCount = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); Genotype phasedMother = trioGenotypes.get(0); Genotype phasedFather = trioGenotypes.get(1); @@ -712,23 +772,23 @@ public class PhaseByTransmission extends RodWalker, HashMa genotypeMap.put(phasedMother.getSampleName(), phasedMother); if(father != null){ genotypeMap.put(phasedFather.getSampleName(), phasedFather); - updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,isMV,metricsCounters); + 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()); } else{ - updatePairMetricsCounters(phasedMother,phasedChild,isMV,metricsCounters); + updatePairMetricsCounters(phasedMother,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t.:.:.:.\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(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } } else{ genotypeMap.put(phasedFather.getSampleName(),phasedFather); - updatePairMetricsCounters(phasedFather,phasedChild,isMV,metricsCounters); + updatePairMetricsCounters(phasedFather,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t.:.:.:.\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedFather.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),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()); } //Report violation if set so //TODO: ADAPT FOR PAIRS TOO!! - if(isMV && mvFile != null) + if(mvCount>0 && mvFile != null) mvFile.println(mvfLine); } @@ -759,6 +819,7 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); + metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); return metricsCounters; } @@ -781,6 +842,7 @@ public class PhaseByTransmission extends RodWalker, HashMa sum.put(NUM_PAIR_GENOTYPES_PHASED,value.get(NUM_PAIR_GENOTYPES_PHASED)+sum.get(NUM_PAIR_GENOTYPES_PHASED)); sum.put(NUM_PAIR_HET_HET,value.get(NUM_PAIR_HET_HET)+sum.get(NUM_PAIR_HET_HET)); sum.put(NUM_PAIR_VIOLATIONS,value.get(NUM_PAIR_VIOLATIONS)+sum.get(NUM_PAIR_VIOLATIONS)); + sum.put(NUM_TRIO_DOUBLE_VIOLATIONS,value.get(NUM_TRIO_DOUBLE_VIOLATIONS)+sum.get(NUM_TRIO_DOUBLE_VIOLATIONS)); return sum; } @@ -795,7 +857,8 @@ public class PhaseByTransmission extends RodWalker, HashMa logger.info("Number of trio-genotypes containing no call(s): " + result.get(NUM_TRIO_GENOTYPES_NOCALL)); logger.info("Number of trio-genotypes phased: " + result.get(NUM_TRIO_GENOTYPES_PHASED)); logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_TRIO_HET_HET_HET)); - logger.info("Number of remaining mendelian violations in trios: " + result.get(NUM_TRIO_VIOLATIONS)); + logger.info("Number of remaining single mendelian violations in trios: " + result.get(NUM_TRIO_VIOLATIONS)); + logger.info("Number of remaining double mendelian violations in trios: " + result.get(NUM_TRIO_DOUBLE_VIOLATIONS)); logger.info("Number of complete pair-genotypes: " + result.get(NUM_PAIR_GENOTYPES_CALLED)); logger.info("Number of pair-genotypes containing no call(s): " + result.get(NUM_PAIR_GENOTYPES_NOCALL)); logger.info("Number of pair-genotypes phased: " + result.get(NUM_PAIR_GENOTYPES_PHASED)); From 893787de535fde77b56d02e5c006400e021f76c1 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 3 Nov 2011 13:04:11 +0100 Subject: [PATCH 015/380] Functions getAsMap and getNegLog10GQ now handle missing genotype case. --- .../sting/utils/variantcontext/GenotypeLikelihoods.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 47c93bb1b..8c8e4f257 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -101,9 +101,12 @@ public class GenotypeLikelihoods { } //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]); @@ -112,10 +115,13 @@ 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 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; From 385a6abec144fc72c726f88216cdcd9c3a4fa435 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 3 Nov 2011 13:04:53 +0100 Subject: [PATCH 016/380] Fixed a bug that wrongly swapped the mother and father genotypes in case the child genotype missing. --- .../gatk/walkers/phasing/PhaseByTransmission.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 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 3c39b58d3..244527212 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 @@ -547,10 +547,15 @@ public class PhaseByTransmission extends RodWalker, HashMa int parentsCalled = 0; Map firstParentLikelihoods; Map secondParentLikelihoods; + ArrayList bestFirstParentGenotype = new ArrayList(); + ArrayList bestSecondParentGenotype = new ArrayList(); + ArrayList bestChildGenotype = new ArrayList(); Genotype.Type pairSecondParentGenotype = null; if(mother == null || !mother.isCalled()){ firstParentLikelihoods = getLikelihoodsAsMapSafeNull(father); secondParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); + bestFirstParentGenotype.add(getTypeSafeNull(father)); + bestSecondParentGenotype.add(getTypeSafeNull(mother)); pairSecondParentGenotype = mother == null ? Genotype.Type.UNAVAILABLE : mother.getType(); if(father != null && father.isCalled()) parentsCalled = 1; @@ -558,6 +563,8 @@ public class PhaseByTransmission extends RodWalker, HashMa else{ firstParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); secondParentLikelihoods = getLikelihoodsAsMapSafeNull(father); + bestFirstParentGenotype.add(getTypeSafeNull(mother)); + bestSecondParentGenotype.add(getTypeSafeNull(father)); if(father == null || !father.isCalled()){ parentsCalled = 1; pairSecondParentGenotype = father == null ? Genotype.Type.UNAVAILABLE : father.getType(); @@ -566,6 +573,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } } Map childLikelihoods = getLikelihoodsAsMapSafeNull(child); + bestChildGenotype.add(getTypeSafeNull(child)); //Prior vars double bestConfigurationLikelihood = 0.0; @@ -574,13 +582,6 @@ public class PhaseByTransmission extends RodWalker, HashMa ArrayList bestMVCount = new ArrayList(); bestMVCount.add(0); - ArrayList bestFirstParentGenotype = new ArrayList(); - ArrayList bestSecondParentGenotype = new ArrayList(); - ArrayList bestChildGenotype = new ArrayList(); - bestFirstParentGenotype.add(getTypeSafeNull(mother)); - bestSecondParentGenotype.add(getTypeSafeNull(father)); - bestChildGenotype.add(getTypeSafeNull(child)); - //Get the most likely combination //Only check for most likely combination if at least a parent and the child have genotypes if(child.isCalled() && parentsCalled > 0){ From 611a395783c861f70b25157368045ffb257e762b Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Sat, 5 Nov 2011 12:18:56 -0400 Subject: [PATCH 024/380] Now properly extending candidate haplotypes with bases from the reference context instead of filling with padding bases. Functionality in the private Haplotype class is no longer necessary so removing it. No need to have four different Haplotype classes in the GATK. --- .../gatk/walkers/annotator/HaplotypeScore.java | 10 +++++----- .../indels/HaplotypeIndelErrorModel.java | 18 +++++++++--------- .../walkers/indels/PairHMMIndelErrorModel.java | 4 ++-- .../broadinstitute/sting/utils/Haplotype.java | 12 ++++++++---- 4 files changed, 24 insertions(+), 20 deletions(-) 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 c142109fa..803bf514c 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 @@ -180,12 +180,12 @@ public class HaplotypeScore extends InfoFieldAnnotation implements StandardAnnot final Haplotype haplotype1 = consensusHaplotypeQueue.poll(); Listhlist = new ArrayList(); - hlist.add(new Haplotype(haplotype1.getBasesAsBytes(), 60)); + hlist.add(new Haplotype(haplotype1.getBases(), 60)); for (int k=1; k < haplotypesToCompute; k++) { Haplotype haplotype2 = consensusHaplotypeQueue.poll(); if(haplotype2 == null ) { haplotype2 = haplotype1; } // Sometimes only the reference haplotype can be found - hlist.add(new Haplotype(haplotype2.getBasesAsBytes(), 20)); + hlist.add(new Haplotype(haplotype2.getBases(), 20)); } return hlist; } else @@ -229,8 +229,8 @@ public class HaplotypeScore extends InfoFieldAnnotation implements StandardAnnot } private Haplotype getConsensusHaplotype(final Haplotype haplotypeA, final Haplotype haplotypeB) { - final byte[] a = haplotypeA.getBasesAsBytes(); - final byte[] b = haplotypeB.getBasesAsBytes(); + final byte[] a = haplotypeA.getBases(); + final byte[] b = haplotypeB.getBases(); if (a.length != b.length) { throw new ReviewedStingException("Haplotypes a and b must be of same length"); @@ -313,7 +313,7 @@ public class HaplotypeScore extends InfoFieldAnnotation implements StandardAnnot // actually be a miscall in a matching direction, which would happen at a e / 3 rate. If b != c, then // the chance that it is actually a mismatch is 1 - e, since any of the other 3 options would be a mismatch. // so the probability-weighted mismatch rate is sum_i ( matched ? e_i / 3 : 1 - e_i ) for i = 1 ... n - final byte[] haplotypeBases = haplotype.getBasesAsBytes(); + final byte[] haplotypeBases = haplotype.getBases(); final SAMRecord read = p.getRead(); byte[] readBases = read.getReadBases(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/HaplotypeIndelErrorModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/HaplotypeIndelErrorModel.java index 3b3f54b05..200a250f2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/HaplotypeIndelErrorModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/HaplotypeIndelErrorModel.java @@ -205,7 +205,7 @@ public class HaplotypeIndelErrorModel { byte haplotypeBase; if (haplotypeIndex < RIGHT_ALIGN_INDEX) - haplotypeBase = haplotype.getBasesAsBytes()[haplotypeIndex]; + haplotypeBase = haplotype.getBases()[haplotypeIndex]; else haplotypeBase = (byte)0; // dummy @@ -217,7 +217,7 @@ public class HaplotypeIndelErrorModel { if (readQual > 3) pRead += pBaseRead; haplotypeIndex++; - if (haplotypeIndex >= haplotype.getBasesAsBytes().length) + if (haplotypeIndex >= haplotype.getBases().length) haplotypeIndex = RIGHT_ALIGN_INDEX; //System.out.format("H:%c R:%c RQ:%d HI:%d %4.5f %4.5f\n", haplotypeBase, readBase, (int)readQual, haplotypeIndex, pBaseRead, pRead); } @@ -227,8 +227,8 @@ public class HaplotypeIndelErrorModel { System.out.println(read.getReadName()); System.out.print("Haplotype:"); - for (int k=0; k LEFT_ALIGN_INDEX && indX < RIGHT_ALIGN_INDEX) - haplotypeBase = haplotype.getBasesAsBytes()[indX-1]; + haplotypeBase = haplotype.getBases()[indX-1]; else haplotypeBase = readBase; @@ -296,8 +296,8 @@ public class HaplotypeIndelErrorModel { System.out.println(read.getReadName()); System.out.print("Haplotype:"); - for (int k=0; k Date: Sat, 5 Nov 2011 23:53:15 -0400 Subject: [PATCH 025/380] Adding integration test to cover using expressions with IDs (-E foo.ID) --- .../annotator/VariantAnnotatorIntegrationTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 8e887c32a..189f643d4 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 @@ -124,6 +124,14 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { executeTest("using expression", spec); } + @Test + 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")); + executeTest("using expression with ID", spec); + } + @Test public void testTabixAnnotations() { final String MD5 = "13269d5a2e16f06fd755cc0fb9271acf"; From a12bc63e5c2b99e52baa9eebe85667a4d1abec3d Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sat, 5 Nov 2011 23:54:28 -0400 Subject: [PATCH 026/380] Get rid of support for bams without sample information in the read groups. This hidden option wasn't being used anyways because it wasn't hooked up properly in the AlignmentContext. --- .../gatk/contexts/AlignmentContextUtils.java | 2 +- .../walkers/annotator/VariantAnnotator.java | 13 ++----------- .../walkers/genotyper/UGCalcLikelihoods.java | 8 +------- .../genotyper/UnifiedArgumentCollection.java | 6 ------ .../walkers/genotyper/UnifiedGenotyper.java | 7 +------ .../genotyper/UnifiedGenotyperEngine.java | 17 ++++++----------- 6 files changed, 11 insertions(+), 42 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java b/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java index 4e75f3ddb..d589f9029 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java @@ -131,7 +131,7 @@ public class AlignmentContextUtils { } } - public static Map splitContextBySampleName(ReadBackedPileup pileup, String assumedSingleSample) { + public static Map splitContextBySampleName(ReadBackedPileup pileup) { return splitContextBySampleName(new AlignmentContext(pileup.getLocation(), pileup)); } 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 c9937f3d6..8f4bc0abd 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 @@ -164,10 +164,6 @@ public class VariantAnnotator extends RodWalker implements Ann @Argument(fullName="list", shortName="ls", doc="List the available annotations and exit") protected Boolean LIST = false; - @Hidden - @Argument(fullName = "assume_single_sample_reads", shortName = "single_sample", doc = "The single sample that we should assume is represented in the input bam (and therefore associate with all reads regardless of whether they have read groups)", required = false) - protected String ASSUME_SINGLE_SAMPLE = null; - @Hidden @Argument(fullName="vcfContainsOnlyIndels", shortName="dels",doc="Use if you are annotating an indel vcf, currently VERY experimental", required = false) protected boolean indelsOnly = false; @@ -213,11 +209,6 @@ public class VariantAnnotator extends RodWalker implements Ann List rodName = Arrays.asList(variantCollection.variants.getName()); Set samples = SampleUtils.getUniqueSamplesFromRods(getToolkit(), rodName); - // if there are no valid samples, warn the user - if ( samples.size() == 0 ) { - logger.warn("There are no samples input at all; use the --sampleName argument to specify one if desired."); - } - if ( USE_ALL_ANNOTATIONS ) engine = new VariantAnnotatorEngine(annotationsToExclude, this, getToolkit()); else @@ -301,9 +292,9 @@ public class VariantAnnotator extends RodWalker implements Ann Map stratifiedContexts; if ( BaseUtils.simpleBaseToBaseIndex(ref.getBase()) != -1 ) { if ( ! context.hasExtendedEventPileup() ) { - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(context.getBasePileup(), ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(context.getBasePileup()); } else { - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(context.getExtendedEventPileup(), ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(context.getExtendedEventPileup()); } if ( stratifiedContexts != null ) { annotatedVCs = new ArrayList(VCs.size()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCalcLikelihoods.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCalcLikelihoods.java index 503d87cbe..c7e577393 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCalcLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGCalcLikelihoods.java @@ -39,7 +39,6 @@ import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.HashSet; import java.util.Set; -import java.util.TreeSet; /** @@ -71,12 +70,7 @@ public class UGCalcLikelihoods extends LocusWalker public void initialize() { // get all of the unique sample names - // if we're supposed to assume a single sample, do so - Set samples = new TreeSet(); - if ( UAC.ASSUME_SINGLE_SAMPLE != null ) - samples.add(UAC.ASSUME_SINGLE_SAMPLE); - else - samples = SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeader()); + Set samples = SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeader()); UG_engine = new UnifiedGenotyperEngine(getToolkit(), UAC, logger, null, null, samples); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index 07d9892a1..62218416d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java @@ -96,11 +96,6 @@ public class UnifiedArgumentCollection { @Input(fullName="alleles", shortName = "alleles", doc="The set of alleles at which to genotype when in GENOTYPE_MODE = GENOTYPE_GIVEN_ALLELES", required=false) public RodBinding alleles; - // control the error modes - @Hidden - @Argument(fullName = "assume_single_sample_reads", shortName = "single_sample", doc = "The single sample that we should assume is represented in the input bam (and therefore associate with all reads regardless of whether they have read groups)", required = false) - public String ASSUME_SINGLE_SAMPLE = null; - /** * The minimum confidence needed in a given base for it to be used in variant calling. Note that the base quality of a base * is capped by the mapping quality so that bases on reads with low mapping quality may get filtered out depending on this value. @@ -170,7 +165,6 @@ public class UnifiedArgumentCollection { uac.GenotypingMode = GenotypingMode; uac.OutputMode = OutputMode; uac.COMPUTE_SLOD = COMPUTE_SLOD; - uac.ASSUME_SINGLE_SAMPLE = ASSUME_SINGLE_SAMPLE; uac.STANDARD_CONFIDENCE_FOR_CALLING = STANDARD_CONFIDENCE_FOR_CALLING; uac.STANDARD_CONFIDENCE_FOR_EMITTING = STANDARD_CONFIDENCE_FOR_EMITTING; uac.MIN_BASE_QUALTY_SCORE = MIN_BASE_QUALTY_SCORE; 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 72dc217e1..bdd4e2c65 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 @@ -206,12 +206,7 @@ public class UnifiedGenotyper extends LocusWalker samples = new TreeSet(); - if ( UAC.ASSUME_SINGLE_SAMPLE != null ) - samples.add(UAC.ASSUME_SINGLE_SAMPLE); - else - samples = SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeader()); + Set samples = SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeader()); // initialize the verbose writer if ( verboseWriter != null ) 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 993a434ac..cee128a6a 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 @@ -106,12 +106,7 @@ public class UnifiedGenotyperEngine { // --------------------------------------------------------------------------------------------------------- @Requires({"toolkit != null", "UAC != null"}) public UnifiedGenotyperEngine(GenomeAnalysisEngine toolkit, UnifiedArgumentCollection UAC) { - this(toolkit, UAC, Logger.getLogger(UnifiedGenotyperEngine.class), null, null, - // get the number of samples - // if we're supposed to assume a single sample, do so - UAC.ASSUME_SINGLE_SAMPLE != null ? - new TreeSet(Arrays.asList(UAC.ASSUME_SINGLE_SAMPLE)) : - SampleUtils.getSAMFileSamples(toolkit.getSAMFileHeader())); + this(toolkit, UAC, Logger.getLogger(UnifiedGenotyperEngine.class), null, null, SampleUtils.getSAMFileSamples(toolkit.getSAMFileHeader())); } @Requires({"toolkit != null", "UAC != null", "logger != null", "samples != null && samples.size() > 0"}) @@ -253,7 +248,7 @@ public class UnifiedGenotyperEngine { pileup = rawContext.getExtendedEventPileup(); else if (rawContext.hasBasePileup()) pileup = rawContext.getBasePileup(); - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup, UAC.ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup); vc = annotationEngine.annotateContext(tracker, ref, stratifiedContexts, vc); } @@ -435,7 +430,7 @@ public class UnifiedGenotyperEngine { pileup = rawContext.getExtendedEventPileup(); else if (rawContext.hasBasePileup()) pileup = rawContext.getBasePileup(); - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup, UAC.ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup); vcCall = annotationEngine.annotateContext(tracker, refContext, stratifiedContexts, vcCall); } @@ -569,7 +564,7 @@ public class UnifiedGenotyperEngine { return null; // stratify the AlignmentContext and cut by sample - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup, UAC.ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup); } else { @@ -586,12 +581,12 @@ public class UnifiedGenotyperEngine { return null; // stratify the AlignmentContext and cut by sample - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup, UAC.ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(pileup); } } else if ( model == GenotypeLikelihoodsCalculationModel.Model.SNP ) { // stratify the AlignmentContext and cut by sample - stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(rawContext.getBasePileup(), UAC.ASSUME_SINGLE_SAMPLE); + stratifiedContexts = AlignmentContextUtils.splitContextBySampleName(rawContext.getBasePileup()); if( !(UAC.OutputMode == OUTPUT_MODE.EMIT_ALL_SITES && UAC.GenotypingMode != GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES) ) { int numDeletions = 0; From 3517489a22a3af3cad192a7c5378930c4f94bd26 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sun, 6 Nov 2011 01:07:49 -0400 Subject: [PATCH 027/380] Better --sample selection integration test for VE. The previous one would return true even if --sample was not working at all. --- .../walkers/varianteval/VariantEvalIntegrationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 cd2493dde..cbfe0ceab 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 @@ -9,7 +9,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { private static String variantEvalTestDataRoot = validationDataLocation + "VariantEval"; private static String fundamentalTestVCF = variantEvalTestDataRoot + "/" + "FundamentalsTest.annotated.db.subset.snps_and_indels.vcf"; private static String fundamentalTestSNPsVCF = variantEvalTestDataRoot + "/" + "FundamentalsTest.annotated.db.subset.final.vcf"; - private static String fundamentalTestSNPsOneSampleVCF = variantEvalTestDataRoot + "/" + "FundamentalsTest.annotated.db.subset.final.HG00625.vcf"; + private static String fundamentalTestSNPsOneSampleVCF = variantEvalTestDataRoot + "/" + "FundamentalsTest.annotated.db.subset.final.NA12045.vcf"; private static String cmdRoot = "-T VariantEval" + " -R " + b36KGReference; @@ -359,7 +359,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { @Test public void testPerSampleAndSubsettedSampleHaveSameResults() { - String md5 = "b0565ac61b2860248e4abd478a177b5e"; + String md5 = "7425ca5c439afd7bb33ed5cfea02c2b3"; WalkerTestSpec spec = new WalkerTestSpec( buildCommandLine( @@ -369,7 +369,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "--eval " + fundamentalTestSNPsVCF, "-noEV", "-EV CompOverlap", - "-sn HG00625", + "-sn NA12045", "-noST", "-L " + fundamentalTestSNPsVCF, "-o %s" From c1986b63356db6e816ce864164e0b1b3b59d2ab5 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 7 Nov 2011 11:06:19 -0500 Subject: [PATCH 031/380] Add notes to the GATKdocs as to when a particular annotation can/cannot be calculated. --- .../sting/gatk/walkers/annotator/BaseQualityRankSumTest.java | 3 ++- .../sting/gatk/walkers/annotator/FisherStrand.java | 3 ++- .../sting/gatk/walkers/annotator/HaplotypeScore.java | 2 ++ .../sting/gatk/walkers/annotator/InbreedingCoeff.java | 3 ++- .../gatk/walkers/annotator/MappingQualityRankSumTest.java | 1 + .../sting/gatk/walkers/annotator/QualByDepth.java | 3 ++- .../sting/gatk/walkers/annotator/ReadPosRankSumTest.java | 1 + 7 files changed, 12 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseQualityRankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseQualityRankSumTest.java index 6cab6d95f..312b505ec 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseQualityRankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseQualityRankSumTest.java @@ -14,7 +14,8 @@ import java.util.List; /** - * The phred-scaled p-value (u-based z-approximation) from the Mann-Whitney Rank Sum Test for base qualities (ref bases vs. bases of the alternate allele) + * The phred-scaled p-value (u-based z-approximation) from the Mann-Whitney Rank Sum Test for base qualities (ref bases vs. bases of the alternate allele). + * Note that the base quality rank sum test can not be calculated for homozygous sites. */ public class BaseQualityRankSumTest extends RankSumTest { public List getKeyNames() { return Arrays.asList("BaseQRankSum"); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/FisherStrand.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/FisherStrand.java index 2d1d1978c..c4025a25c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/FisherStrand.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/FisherStrand.java @@ -46,7 +46,8 @@ import java.util.*; /** * Phred-scaled p-value using Fisher's Exact Test to detect strand bias (the variation * being seen on only the forward or only the reverse strand) in the reads? More bias is - * indicative of false positive calls. + * indicative of false positive calls. Note that the fisher strand test may not be + * calculated for certain complex indel cases or for multi-allelic sites. */ public class FisherStrand extends InfoFieldAnnotation implements StandardAnnotation { private static final String FS = "FS"; 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 803bf514c..94b0636f4 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 @@ -52,6 +52,8 @@ import java.util.*; /** * Consistency of the site with two (and only two) segregating haplotypes. Higher scores * are indicative of regions with bad alignments, often leading to artifactual SNP and indel calls. + * Note that the Haplotype Score is only calculated for sites with read coverage; also, for SNPs, the + * site must be bi-allelic. */ public class HaplotypeScore extends InfoFieldAnnotation implements StandardAnnotation { private final static boolean DEBUG = false; 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 a14007147..8728e5aa4 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 @@ -23,7 +23,8 @@ import java.util.Map; * * A continuous generalization of the Hardy-Weinberg test for disequilibrium that works * well with limited coverage per sample. See the 1000 Genomes Phase I release for - * more information. + * more information. Note that the Inbreeding Coefficient will not be calculated for files + * with fewer than a minimum (generally 10) number of samples. */ public class InbreedingCoeff extends InfoFieldAnnotation implements StandardAnnotation { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MappingQualityRankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MappingQualityRankSumTest.java index 157c615d7..9857c339f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MappingQualityRankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MappingQualityRankSumTest.java @@ -16,6 +16,7 @@ import java.util.List; /** * The phred-scaled p-value (u-based z-approximation) from the Mann-Whitney Rank Sum Test for mapping qualities (reads with ref bases vs. those with the alternate allele) + * Note that the mapping quality rank sum test can not be calculated for homozygous sites. */ public class MappingQualityRankSumTest extends RankSumTest { 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 ffc852903..b942d9817 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 @@ -19,7 +19,8 @@ import java.util.Map; /** * Variant confidence (given as (AB+BB)/AA from the PLs) / unfiltered depth. * - * Low scores are indicative of false positive calls and artifacts. + * Low scores are indicative of false positive calls and artifacts. Note that QualByDepth requires sequencing + * reads associated with the samples with polymorphic genotypes. */ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotation { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ReadPosRankSumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ReadPosRankSumTest.java index 27a9306d4..d762af428 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ReadPosRankSumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/ReadPosRankSumTest.java @@ -20,6 +20,7 @@ import java.util.List; /** * The phred-scaled p-value (u-based z-approximation) from the Mann-Whitney Rank Sum Test for the distance from the end of the read for reads with the alternate allele; if the alternate allele is only seen near the ends of reads this is indicative of error). + * Note that the read position rank sum test can not be calculated for homozygous sites. */ public class ReadPosRankSumTest extends RankSumTest { From 759f4fe6b85c7341daf5a90af78d74f806ec330e Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 7 Nov 2011 13:16:38 -0500 Subject: [PATCH 032/380] Moving unclaimed walker with bad integration test to archive --- .../phasing/MergeAndMatchHaplotypes.java | 118 ------------------ ...ergeAndMatchHaplotypesIntegrationTest.java | 28 ----- 2 files changed, 146 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypes.java delete mode 100644 public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypesIntegrationTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypes.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypes.java deleted file mode 100644 index 306509d0c..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypes.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.phasing; - -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.RodWalker; -import org.broadinstitute.sting.utils.SampleUtils; -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.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; - -import java.util.*; - -/** - * Merges read-back-phased and phase-by-transmission files. - */ -public class MergeAndMatchHaplotypes extends RodWalker { - @Output - protected VCFWriter vcfWriter = null; - - @Input(fullName="pbt", shortName = "pbt", doc="Input VCF truth file", required=true) - public RodBinding pbtTrack; - - @Input(fullName="rbp", shortName = "rbp", doc="Input VCF truth file", required=true) - public RodBinding rbpTrack; - - private Map pbtCache = new HashMap(); - private Map rbpCache = new HashMap(); - - private final String SOURCE_NAME = "MergeReadBackedAndTransmissionPhasedVariants"; - - public void initialize() { - ArrayList rodNames = new ArrayList(); - rodNames.add(pbtTrack.getName()); - - Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit(), rodNames); - Set vcfSamples = SampleUtils.getSampleList(vcfRods, VariantContextUtils.GenotypeMergeType.REQUIRE_UNIQUE); - Set headerLines = new HashSet(); - headerLines.addAll(VCFUtils.getHeaderFields(this.getToolkit())); - - vcfWriter.writeHeader(new VCFHeader(headerLines, vcfSamples)); - } - - @Override - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if (tracker != null) { - Collection pbts = tracker.getValues(pbtTrack, ref.getLocus()); - Collection rbps = tracker.getValues(rbpTrack, ref.getLocus()); - - VariantContext pbt = pbts.iterator().hasNext() ? pbts.iterator().next() : null; - VariantContext rbp = rbps.iterator().hasNext() ? rbps.iterator().next() : null; - - if (pbt != null && rbp != null) { - Map genotypes = pbt.getGenotypes(); - - if (!rbp.isFiltered()) { - for (String sample : rbp.getSampleNames()) { - Genotype rbpg = rbp.getGenotype(sample); - Genotype pbtg = pbt.getGenotype(sample); - - // Propagate read-backed phasing information to genotypes unphased by transmission - //if (!pbtg.isPhased() && rbpCache.containsKey(sample)) { - if (!pbtg.isPhased() && rbpg.isPhased() && rbpCache.containsKey(sample)) { - boolean orientationMatches = rbpCache.get(sample).sameGenotype(pbtCache.get(sample), false); - - if (orientationMatches) { - pbtg = rbpg; - } else { - List fwdAlleles = rbpg.getAlleles(); - List revAlleles = new ArrayList(); - - for (int i = fwdAlleles.size() - 1; i >= 0; i--) { - revAlleles.add(fwdAlleles.get(i)); - } - - pbtg = new Genotype(sample, revAlleles, rbpg.getNegLog10PError(), rbpg.getFilters(), rbpg.getAttributes(), rbpg.isPhased()); - } - } - - genotypes.put(sample, pbtg); - - // Update the cache - if (/*rbpg.isPhased() &&*/ rbpg.isHet()) { - rbpCache.put(sample, rbpg); - pbtCache.put(sample, pbtg); - } else if (!rbpg.isPhased()) { - rbpCache.remove(sample); - pbtCache.remove(sample); - } - } - } - - VariantContext newvc = new VariantContext(SOURCE_NAME, pbt.getChr(), pbt.getStart(), pbt.getStart(), pbt.getAlleles(), genotypes, pbt.getNegLog10PError(), pbt.getFilters(), pbt.getAttributes()); - vcfWriter.add(newvc); - } - } - - return null; - } - - @Override - public Integer reduceInit() { - return null; - } - - @Override - public Integer reduce(Integer value, Integer sum) { - return null; - } -} diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypesIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypesIntegrationTest.java deleted file mode 100644 index cf6b4e581..000000000 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/MergeAndMatchHaplotypesIntegrationTest.java +++ /dev/null @@ -1,28 +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 MergeAndMatchHaplotypesIntegrationTest extends WalkerTest { - private static String mergeAndMatchHaplotypesTestDataRoot = validationDataLocation + "/MergeAndMatchHaplotypes"; - private static String fundamentalTestPBTVCF = mergeAndMatchHaplotypesTestDataRoot + "/" + "FundamentalsTest.pbt.vcf"; - private static String fundamentalTestRBPVCF = mergeAndMatchHaplotypesTestDataRoot + "/" + "FundamentalsTest.pbt.rbp.vcf"; - - @Test - public void testBasicFunctionality() { - WalkerTestSpec spec = new WalkerTestSpec( - buildCommandLine( - "-T MergeAndMatchHaplotypes", - "-R " + b37KGReference, - "--pbt " + fundamentalTestPBTVCF, - "--rbp " + fundamentalTestRBPVCF, - "-o %s" - ), - 1, - Arrays.asList("") - ); - executeTest("testBasicMergeAndMatchHaplotypesFunctionality", spec); - } -} From 571c724cfdc378dac05573146f64e3da7e6424ec Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Tue, 8 Nov 2011 15:15:51 +0100 Subject: [PATCH 034/380] Added reporting of the number of genotypes updated. --- .../walkers/phasing/PhaseByTransmission.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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 244527212..6394e0e24 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 @@ -113,6 +113,7 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_PAIR_GENOTYPES_PHASED = 7; private final Byte NUM_PAIR_HET_HET = 8; private final Byte NUM_PAIR_VIOLATIONS = 9; + private final Byte NUM_GENOTYPES_MODIFIED = 11; //Random number generator private Random rand = new Random(); @@ -742,6 +743,8 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + metricsCounters.put(NUM_GENOTYPES_MODIFIED,0); + String mvfLine; if (tracker != null) { @@ -775,15 +778,21 @@ public class PhaseByTransmission extends RodWalker, HashMa genotypeMap.put(phasedFather.getSampleName(), 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())) + metricsCounters.put(NUM_GENOTYPES_MODIFIED,metricsCounters.get(NUM_GENOTYPES_MODIFIED)+1); } else{ updatePairMetricsCounters(phasedMother,phasedChild,mvCount,metricsCounters); + if(!(phasedMother.getType()==mother.getType() && phasedChild.getType()==child.getType())) + metricsCounters.put(NUM_GENOTYPES_MODIFIED,metricsCounters.get(NUM_GENOTYPES_MODIFIED)+1); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t.:.:.:.\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(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } } else{ genotypeMap.put(phasedFather.getSampleName(),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); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t.:.:.:.\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedFather.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),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()); } @@ -820,7 +829,9 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); - metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + metricsCounters.put(NUM_GENOTYPES_MODIFIED,0); + return metricsCounters; } @@ -844,6 +855,8 @@ public class PhaseByTransmission extends RodWalker, HashMa sum.put(NUM_PAIR_HET_HET,value.get(NUM_PAIR_HET_HET)+sum.get(NUM_PAIR_HET_HET)); sum.put(NUM_PAIR_VIOLATIONS,value.get(NUM_PAIR_VIOLATIONS)+sum.get(NUM_PAIR_VIOLATIONS)); sum.put(NUM_TRIO_DOUBLE_VIOLATIONS,value.get(NUM_TRIO_DOUBLE_VIOLATIONS)+sum.get(NUM_TRIO_DOUBLE_VIOLATIONS)); + sum.put(NUM_GENOTYPES_MODIFIED,value.get(NUM_GENOTYPES_MODIFIED)+sum.get(NUM_GENOTYPES_MODIFIED)); + return sum; } @@ -865,5 +878,7 @@ public class PhaseByTransmission extends RodWalker, HashMa logger.info("Number of pair-genotypes phased: " + result.get(NUM_PAIR_GENOTYPES_PHASED)); logger.info("Number of resulting Het/Het pairs: " + result.get(NUM_PAIR_HET_HET)); logger.info("Number of remaining mendelian violations in pairs: " + result.get(NUM_PAIR_VIOLATIONS)); + logger.info("Number of genotypes updated: " + result.get(NUM_GENOTYPES_MODIFIED)); + } } From e1b4c3968f7df4b4f50495b0ee15ab20f7c33ffd Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 8 Nov 2011 16:50:03 -0500 Subject: [PATCH 036/380] Fixing GATKSAMRecord bug when constructing a GATKSAMRecord from scratch, we should set "mRestOfBinaryData" to null so the BAMRecord doesn't try to retrieve missing information from the non-existent bam file. --- .../org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 ede75817a..4c1cbf547 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -24,7 +24,10 @@ package org.broadinstitute.sting.utils.sam; -import net.sf.samtools.*; +import net.sf.samtools.BAMRecord; +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMReadGroupRecord; +import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.utils.NGSPlatform; import java.util.HashMap; @@ -83,7 +86,7 @@ public class GATKSAMRecord extends BAMRecord { read.getMateReferenceIndex(), read.getMateAlignmentStart(), read.getInferredInsertSize(), - new byte[]{}); + null); super.clearAttributes(); } From e639f0798e07a93c90b0ead785dd3c9d98ba13a6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 9 Nov 2011 14:35:50 -0500 Subject: [PATCH 037/380] mergeEvals allows you to treat -eval 1.vcf -eval 2.vcf as a single call set -- A bit of code cleanup in VCFUtils -- VariantEval table to create 1000G Phase I variant summary table -- First version of 1000G Phase I summary table Qscript --- .../varianteval/VariantEvalWalker.java | 24 +-- .../evaluators/G1KPhaseITable.java | 162 ++++++++++++++++++ .../varianteval/stratifications/EvalRod.java | 6 +- .../varianteval/util/VariantEvalUtils.java | 25 ++- .../sting/utils/codecs/vcf/VCFUtils.java | 10 ++ 5 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.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 28f4f2a56..a9448bc06 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 @@ -143,10 +143,10 @@ public class VariantEvalWalker extends RodWalker implements Tr /** * See the -list argument to view available modules. */ - @Argument(fullName="evalModule", shortName="EV", doc="One or more specific eval modules to apply to the eval track(s) (in addition to the standard modules, unless -noE is specified)", required=false) + @Argument(fullName="evalModule", shortName="EV", doc="One or more specific eval modules to apply to the eval track(s) (in addition to the standard modules, unless -noEV is specified)", required=false) protected String[] MODULES_TO_USE = {}; - @Argument(fullName="doNotUseAllStandardModules", shortName="noEV", doc="Do not use the standard modules by default (instead, only those that are specified with the -E option)", required=false) + @Argument(fullName="doNotUseAllStandardModules", shortName="noEV", doc="Do not use the standard modules by default (instead, only those that are specified with the -EV option)", required=false) protected Boolean NO_STANDARD_MODULES = false; // Other arguments @@ -171,6 +171,13 @@ public class VariantEvalWalker extends RodWalker implements Tr @Argument(fullName="requireStrictAlleleMatch", shortName="strict", doc="If provided only comp and eval tracks with exactly matching reference and alternate alleles will be counted as overlapping", required=false) private boolean requireStrictAlleleMatch = false; + /** + * If true, VariantEval will treat -eval 1 -eval 2 as separate tracks from the same underlying + * variant set, and evaluate the union of the results. Useful when you want to do -eval chr1.vcf -eval chr2.vcf etc. + */ + @Argument(fullName="mergeEvals", shortName="mergeEvals", doc="If provided, all -eval tracks will be merged into a single eval track", required=false) + public boolean mergeEvals = false; + // Variables private Set jexlExpressions = new TreeSet(); @@ -224,13 +231,8 @@ public class VariantEvalWalker extends RodWalker implements Tr knowns.add(compRod); } - // Collect the eval rod names - Set evalNames = new TreeSet(); - for ( RodBinding evalRod : evals ) - evalNames.add(evalRod.getName()); - // Now that we have all the rods categorized, determine the sample list from the eval rods. - Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit(), evalNames); + Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit(), evals); Set vcfSamples = SampleUtils.getSampleList(vcfRods, VariantContextUtils.GenotypeMergeType.REQUIRE_UNIQUE); // Load the sample list @@ -289,8 +291,8 @@ 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); - HashMap, HashMap>> compVCs = variantEvalUtils.bindVariantContexts(tracker, ref, comps, byFilterIsEnabled, 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 ) { @@ -353,6 +355,8 @@ public class VariantEvalWalker extends RodWalker implements Tr } } } + + if ( mergeEvals ) break; // stop processing the eval tracks } } 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 new file mode 100644 index 000000000..0e2ee70ef --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/evaluators/G1KPhaseITable.java @@ -0,0 +1,162 @@ +/* + * 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.ReviewedStingException; +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 double SNPNoveltyRate = 0; + @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; + @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; + @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 ) return null; + + switch (eval.getType()) { +// case NO_VARIATION: +// // shouldn't get here +// break; + 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().values()) { + 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 double noveltyRate(VariantContext.Type type) { + int all = allVariantCounts.get(type); + int known = knownVariantCounts.get(type); + int novel = all - known; + return (novel / (1.0 * all)); + } + + 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/stratifications/EvalRod.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/EvalRod.java index e276adc32..b2b6d4165 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/EvalRod.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/EvalRod.java @@ -6,6 +6,7 @@ import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -15,8 +16,11 @@ public class EvalRod extends VariantStratifier implements RequiredStratification @Override public void initialize() { states = new ArrayList(); - for ( RodBinding rod : getVariantEvalWalker().getEvals() ) + for ( RodBinding rod : getVariantEvalWalker().getEvals() ) { states.add(rod.getName()); + if ( getVariantEvalWalker().mergeEvals ) + break; + } } public List getRelevantStates(ReferenceContext ref, RefMetaDataTracker tracker, VariantContext comp, String compName, VariantContext eval, String evalName, String sampleName) { 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 6a057a456..aa8c6cfb9 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 @@ -312,12 +312,20 @@ public class VariantEvalUtils { * * @return the mapping of track to VC list that should be populated */ - public HashMap, HashMap>> bindVariantContexts(RefMetaDataTracker tracker, ReferenceContext ref, List> tracks, boolean byFilter, boolean subsetBySample, boolean trackPerSample) { + public HashMap, HashMap>> + bindVariantContexts(RefMetaDataTracker tracker, + ReferenceContext ref, + List> tracks, + boolean byFilter, + boolean subsetBySample, + boolean trackPerSample, + boolean mergeTracks) { if ( tracker == null ) return null; HashMap, HashMap>> bindings = new HashMap, HashMap>>(); + RodBinding firstTrack = tracks.isEmpty() ? null : tracks.get(0); for ( RodBinding track : tracks ) { HashMap> mapping = new HashMap>(); @@ -346,7 +354,20 @@ public class VariantEvalUtils { } } - bindings.put(track, mapping); + 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()); + if ( firstMappingSet != null ) { + firstMappingSet.addAll(elt.getValue()); + } else { + firstMapping.put(elt.getKey(), elt.getValue()); + } + } + } else { + bindings.put(track, mapping); + } } return bindings; diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java index 2d8421507..3c55c1ff9 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java @@ -26,6 +26,8 @@ package org.broadinstitute.sting.utils.codecs.vcf; 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 org.broadinstitute.sting.utils.variantcontext.VariantContext; @@ -41,6 +43,14 @@ public class VCFUtils { */ 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, Collection rodNames) { Map data = new HashMap(); From 9427ada49839bde78037dc63dd938c62f7afb07a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 9 Nov 2011 12:15:54 -0500 Subject: [PATCH 038/380] Fixing no cigar bug empty GATKSAMRecords will have a null cigar. Treat them accordingly. --- .../sting/utils/clipreads/ReadClipper.java | 20 +++++++++---------- .../sting/utils/sam/GATKSAMRecord.java | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) 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 a6df986ba..6e4ddddc4 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -58,15 +58,6 @@ public class ReadClipper { return hardClipByReferenceCoordinates(refStart, -1); } - private int numDeletions(GATKSAMRecord read) { - int result = 0; - for (CigarElement e: read.getCigar().getCigarElements()) { - if ( e.getOperator() == CigarOperator.DELETION || e.getOperator() == CigarOperator.D ) - result =+ e.getLength(); - } - return result; - } - protected GATKSAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart, ReadUtils.ClippingTail.RIGHT_TAIL); int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop, ReadUtils.ClippingTail.LEFT_TAIL); @@ -90,7 +81,7 @@ public class ReadClipper { @Requires("left <= right") public GATKSAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { - if (left == right) + if (read.isEmpty() || left == right) return new GATKSAMRecord(read.getHeader()); GATKSAMRecord leftTailRead = hardClipByReferenceCoordinates(right, -1); @@ -104,6 +95,9 @@ public class ReadClipper { } public GATKSAMRecord hardClipLowQualEnds(byte lowQual) { + if (read.isEmpty()) + return read; + byte [] quals = read.getBaseQualities(); int leftClipIndex = 0; int rightClipIndex = read.getReadLength() - 1; @@ -126,6 +120,9 @@ public class ReadClipper { } public GATKSAMRecord hardClipSoftClippedBases () { + if (read.isEmpty()) + return read; + int readIndex = 0; int cutLeft = -1; // first position to hard clip (inclusive) int cutRight = -1; // first position to hard clip (inclusive) @@ -182,6 +179,9 @@ public class ReadClipper { } public GATKSAMRecord hardClipLeadingInsertions() { + if (read.isEmpty()) + return read; + for(CigarElement cigarElement : read.getCigar().getCigarElements()) { if (cigarElement.getOperator() != CigarOperator.HARD_CLIP && cigarElement.getOperator() != CigarOperator.SOFT_CLIP && cigarElement.getOperator() != CigarOperator.INSERTION && cigarElement.getOperator() != CigarOperator.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 4c1cbf547..63a618aed 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -237,4 +237,8 @@ public class GATKSAMRecord extends BAMRecord { // note that we do not consider the GATKSAMRecord internal state at all return super.equals(o); } + + public boolean isEmpty() { + return this.getReadLength() == 0; + } } From f9530e07683250cc2da6e2b178dc3d07230dce00 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 9 Nov 2011 12:37:14 -0500 Subject: [PATCH 039/380] Clean unnecessary attributes from the read this gives on average 40% file size reduction. --- .../org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 63a618aed..d36ec3411 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -241,4 +241,10 @@ public class GATKSAMRecord extends BAMRecord { public boolean isEmpty() { return this.getReadLength() == 0; } + + public void simplify () { + GATKSAMReadGroupRecord rg = getReadGroup(); + this.clearAttributes(); + setReadGroup(rg); + } } From f080f64f99423bdb75d047843ed75499862fb1fe Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 9 Nov 2011 12:46:27 -0500 Subject: [PATCH 040/380] Preserve RG information on new GATKSAMRecord from SAMRecord --- .../sting/utils/sam/GATKSAMRecord.java | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 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 d36ec3411..9e07b1112 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -87,7 +87,12 @@ public class GATKSAMRecord extends BAMRecord { read.getMateAlignmentStart(), read.getInferredInsertSize(), null); - super.clearAttributes(); + SAMReadGroupRecord samRG = read.getReadGroup(); + clearAttributes(); + if (samRG != null) { + GATKSAMReadGroupRecord rg = new GATKSAMReadGroupRecord(samRG); + setReadGroup(rg); + } } public GATKSAMRecord(final SAMFileHeader header, @@ -134,6 +139,21 @@ public class GATKSAMRecord extends BAMRecord { return mReadGroup; } + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (!(o instanceof GATKSAMRecord)) return false; + + // note that we do not consider the GATKSAMRecord internal state at all + return super.equals(o); + } + /** * Efficient caching accessor that returns the GATK NGSPlatform of this read * @return @@ -147,11 +167,9 @@ public class GATKSAMRecord extends BAMRecord { retrievedReadGroup = true; } - // - // - // Reduced read functions - // - // + /////////////////////////////////////////////////////////////////////////////// + // *** ReduceReads functions ***// + /////////////////////////////////////////////////////////////////////////////// public byte[] getReducedReadCounts() { if ( ! retrievedReduceReadCounts ) { @@ -170,6 +188,12 @@ public class GATKSAMRecord extends BAMRecord { return getReducedReadCounts()[i]; } + + /////////////////////////////////////////////////////////////////////////////// + // *** GATKSAMRecord specific methods ***// + /////////////////////////////////////////////////////////////////////////////// + + /** * Checks whether an attribute has been set for the given key. * @@ -223,28 +247,26 @@ public class GATKSAMRecord extends BAMRecord { return null; } - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - - if (!(o instanceof GATKSAMRecord)) return false; - - // note that we do not consider the GATKSAMRecord internal state at all - return super.equals(o); - } - + /** + * Checks whether if the read has any bases. + * + * Empty reads can be dangerous as it may have no cigar strings, no read names and + * other missing attributes. + * + * @return true if the read has no bases + */ public boolean isEmpty() { return this.getReadLength() == 0; } + /** + * Clears all attributes except ReadGroup of the read. + */ public void simplify () { GATKSAMReadGroupRecord rg = getReadGroup(); this.clearAttributes(); setReadGroup(rg); } + + } From 0111e58d4e259368da67df551012177e85bcec04 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 9 Nov 2011 14:45:40 -0500 Subject: [PATCH 042/380] Don't generate PDF unless you have -run specified --- .../src/org/broadinstitute/sting/queue/QCommandLine.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala index e8091cde7..768eab7e4 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala @@ -129,9 +129,11 @@ class QCommandLine extends CommandLineProgram with Logging { logger.info("Writing JobLogging GATKReport to file " + reportFile) QJobReport.printReport(qGraph.getFunctionsAndStatus(script.functions), reportFile) - val pdfFile = new File(jobStringName + ".pdf") - logger.info("Plotting JobLogging GATKReport to file " + pdfFile) - QJobReport.plotReport(reportFile, pdfFile) + if ( settings.run ) { + val pdfFile = new File(jobStringName + ".pdf") + logger.info("Plotting JobLogging GATKReport to file " + pdfFile) + QJobReport.plotReport(reportFile, pdfFile) + } } } } From d64f8a89a9af3e9675f348d00584d380d9327a3a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 9 Nov 2011 15:24:29 -0500 Subject: [PATCH 043/380] Instead of the SelfScopingFeatureCodec interface, pushed this functionality into Tribble itself. Now we can e.g. determine that a file can be parsed by the BedCodec on the fly. --- .../gatk/refdata/SelfScopingFeatureCodec.java | 48 ------------------ .../gatk/refdata/tracks/FeatureManager.java | 10 +--- .../utils/codecs/beagle/BeagleCodec.java | 2 + .../utils/codecs/hapmap/RawHapMapCodec.java | 4 +- .../utils/codecs/refseq/RefSeqCodec.java | 4 ++ .../codecs/sampileup/SAMPileupCodec.java | 4 +- .../utils/codecs/samread/SAMReadCodec.java | 4 +- .../sting/utils/codecs/table/TableCodec.java | 4 ++ .../utils/codecs/vcf/AbstractVCFCodec.java | 3 +- .../{tribble-40.jar => tribble-41.jar} | Bin 300819 -> 301217 bytes .../{tribble-40.xml => tribble-41.xml} | 2 +- 11 files changed, 20 insertions(+), 65 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/refdata/SelfScopingFeatureCodec.java rename settings/repository/org.broad/{tribble-40.jar => tribble-41.jar} (92%) rename settings/repository/org.broad/{tribble-40.xml => tribble-41.xml} (51%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/SelfScopingFeatureCodec.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/SelfScopingFeatureCodec.java deleted file mode 100644 index de781b839..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/SelfScopingFeatureCodec.java +++ /dev/null @@ -1,48 +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.refdata; - -import java.io.File; - -/** - * An interface marking that a given Tribble codec can look at the file and determine whether the - * codec specifically parsing the contents of the file. - */ -public interface SelfScopingFeatureCodec { - /** - * This function returns true iff the File potentialInput can be parsed by this - * codec. - * - * The GATK assumes that there's never a situation where two SelfScopingFeaetureCodecs - * return true for the same file. If this occurs the GATK splits out an error. - * - * Note this function must never throw an error. All errors should be trapped - * and false returned. - * - * @param potentialInput the file to test for parsiability with this codec - * @return true if potentialInput can be parsed, false otherwise - */ - public boolean canDecode(final File potentialInput); -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java index c99aea254..c41444ef3 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java @@ -30,16 +30,12 @@ import org.broad.tribble.Feature; import org.broad.tribble.FeatureCodec; import org.broad.tribble.NameAwareCodec; import org.broadinstitute.sting.gatk.refdata.ReferenceDependentFeatureCodec; -import org.broadinstitute.sting.gatk.refdata.SelfScopingFeatureCodec; import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet; import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.classloader.PluginManager; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.help.GATKDocUtils; -import org.broadinstitute.sting.utils.help.HelpUtils; -import javax.mail.Header; import java.io.File; import java.util.*; @@ -159,10 +155,8 @@ public class FeatureManager { public FeatureDescriptor getByFiletype(File file) { List canParse = new ArrayList(); for ( FeatureDescriptor descriptor : featureDescriptors ) - if ( descriptor.getCodec() instanceof SelfScopingFeatureCodec ) { - if ( ((SelfScopingFeatureCodec) descriptor.getCodec()).canDecode(file) ) { - canParse.add(descriptor); - } + if ( descriptor.getCodec().canDecode(file) ) { + canParse.add(descriptor); } if ( canParse.size() == 0 ) diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java index 413848543..90d305d73 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java @@ -249,4 +249,6 @@ public class BeagleCodec implements ReferenceDependentFeatureCodec getFeatureType() { return RefSeqFeature.class; } + + public boolean canDecode(final File potentialInput) { return false; } + } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/sampileup/SAMPileupCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/sampileup/SAMPileupCodec.java index f4633b2ce..d9f16c353 100644 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/sampileup/SAMPileupCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/sampileup/SAMPileupCodec.java @@ -25,8 +25,8 @@ package org.broadinstitute.sting.utils.codecs.sampileup; +import org.broad.tribble.AbstractFeatureCodec; import org.broad.tribble.Feature; -import org.broad.tribble.FeatureCodec; import org.broad.tribble.exception.CodecLineParsingException; import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; @@ -76,7 +76,7 @@ import static org.broadinstitute.sting.utils.codecs.sampileup.SAMPileupFeature.V * @author Matt Hanna * @since 2009 */ -public class SAMPileupCodec implements FeatureCodec { +public class SAMPileupCodec extends AbstractFeatureCodec { // the number of tokens we expect to parse from a pileup line private static final int expectedTokenCount = 10; private static final char fldDelim = '\t'; diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/samread/SAMReadCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/samread/SAMReadCodec.java index d4bdb5aa9..0f2b94e63 100644 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/samread/SAMReadCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/samread/SAMReadCodec.java @@ -27,8 +27,8 @@ package org.broadinstitute.sting.utils.codecs.samread; import net.sf.samtools.Cigar; import net.sf.samtools.TextCigarCodec; import net.sf.samtools.util.StringUtil; +import org.broad.tribble.AbstractFeatureCodec; import org.broad.tribble.Feature; -import org.broad.tribble.FeatureCodec; import org.broad.tribble.exception.CodecLineParsingException; import org.broad.tribble.readers.LineReader; import org.broad.tribble.util.ParsingUtils; @@ -52,7 +52,7 @@ import org.broad.tribble.util.ParsingUtils; * @author Matt Hanna * @since 2009 */ -public class SAMReadCodec implements FeatureCodec { +public class SAMReadCodec extends AbstractFeatureCodec { /* SL-XBC:1:10:628:923#0 16 Escherichia_coli_K12 1 37 76M = 1 0 AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGCTTCTGA B@>87<;A@?@957:>>@AA@B>@A9AB@B>@A@@@@@A;=AAB@BBBBBCBBBB@>A>:ABB@BAABCB=CA@CB */ // the number of tokens we expect to parse from a read line diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java index 1919ccbf0..97c15fbb8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java @@ -6,6 +6,7 @@ import org.broadinstitute.sting.gatk.refdata.ReferenceDependentFeatureCodec; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.exceptions.UserException; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -101,4 +102,7 @@ public class TableCodec implements ReferenceDependentFeatureCodec { } return header; } + + public boolean canDecode(final File potentialInput) { return false; } + } 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 0e0cb14bf..3377172dd 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 @@ -8,7 +8,6 @@ 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.gatk.refdata.SelfScopingFeatureCodec; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; @@ -20,7 +19,7 @@ import java.util.*; import java.util.zip.GZIPInputStream; -public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, VCFParser, SelfScopingFeatureCodec { +public 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 diff --git a/settings/repository/org.broad/tribble-40.jar b/settings/repository/org.broad/tribble-41.jar similarity index 92% rename from settings/repository/org.broad/tribble-40.jar rename to settings/repository/org.broad/tribble-41.jar index 7f68b4b365817bcb3bcae230d547ed8559e36d5c..76903f007552b51d801c3b77e61623cbed774194 100644 GIT binary patch delta 10131 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 delta 9681 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 diff --git a/settings/repository/org.broad/tribble-40.xml b/settings/repository/org.broad/tribble-41.xml similarity index 51% rename from settings/repository/org.broad/tribble-40.xml rename to settings/repository/org.broad/tribble-41.xml index 6a01b3790..6ee8bfb78 100644 --- a/settings/repository/org.broad/tribble-40.xml +++ b/settings/repository/org.broad/tribble-41.xml @@ -1,3 +1,3 @@ - + From 02d5e3025e3672f945c3a6f9701b3ab0a2a25a69 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 9 Nov 2011 15:34:19 -0500 Subject: [PATCH 044/380] Added integration test for intervals from bed file --- .../utils/interval/IntervalIntegrationTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java index 75bdc3142..3fb330853 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java @@ -162,6 +162,20 @@ public class IntervalIntegrationTest extends WalkerTest { executeTest("testMixedIntervalMerging", spec); } + @Test(enabled = true) + public void testBed() { + String md5 = "cf4278314ef8e4b996e1b798d8eb92cf"; + WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( + "-T CountLoci" + + " -I " + validationDataLocation + "OV-0930.normal.chunk.bam" + + " -R " + hg18Reference + + " -o %s" + + " -L " + validationDataLocation + "intervalTest.bed", + 1, // just one output file + Arrays.asList(md5)); + executeTest("testBed", spec); + } + @Test(enabled = true) public void testComplexVCF() { String md5 = "166d77ac1b46a1ec38aa35ab7e628ab5"; From d00b2c6599b2d20b4b926a0aa6c5649e3fbae20a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 9 Nov 2011 20:09:12 -0500 Subject: [PATCH 047/380] Adding a synthetic read for filtered data * Generalized the concept of a synthetic read to cread both running consensus and a synthetic reads of filtered data. * Synthetic reads can now have deletions (but not insertions) * New reduced read tag for filtered data synthetic reads *(RF)* * Sliding window header now keeps information of consensus and filtered data * Synthetic reads are created simultaneously, new functionality is controlled internally by addToSyntheticReads --- .../org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 6 ++++-- .../org/broadinstitute/sting/utils/ReadUtilsUnitTest.java | 2 +- 2 files changed, 5 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 9e07b1112..d95570d9b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -46,7 +46,9 @@ import java.util.Map; * */ public class GATKSAMRecord extends BAMRecord { - public static final String REDUCED_READ_QUALITY_TAG = "RR"; + public static final String REDUCED_READ_CONSENSUS_TAG = "RR"; + public static final String REDUCED_READ_FILTERED_TAG = "RF"; + // the SAMRecord data we're caching private String mReadString = null; private GATKSAMReadGroupRecord mReadGroup = null; @@ -173,7 +175,7 @@ public class GATKSAMRecord extends BAMRecord { public byte[] getReducedReadCounts() { if ( ! retrievedReduceReadCounts ) { - reducedReadCounts = getByteArrayAttribute(REDUCED_READ_QUALITY_TAG); + reducedReadCounts = getByteArrayAttribute(REDUCED_READ_CONSENSUS_TAG); retrievedReduceReadCounts = true; } diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java index bc39d714e..46134cd24 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java @@ -29,7 +29,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_QUALITY_TAG, REDUCED_READ_COUNTS); + reducedRead.setAttribute(GATKSAMRecord.REDUCED_READ_CONSENSUS_TAG, REDUCED_READ_COUNTS); } private void testReadBasesAndQuals(GATKSAMRecord read, int expectedStart, int expectedStop) { From 04b122be2922868c77fa3826892ac15e653fc6a5 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 9 Nov 2011 20:33:36 -0500 Subject: [PATCH 048/380] Fix for bug reported on GetSatisfaction --- .../broadinstitute/sting/gatk/walkers/annotator/BaseCounts.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseCounts.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseCounts.java index 46aa6d0f3..761251259 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseCounts.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/BaseCounts.java @@ -59,6 +59,8 @@ public class BaseCounts extends InfoFieldAnnotation { int[] counts = new int[4]; for ( Map.Entry sample : stratifiedContexts.entrySet() ) { + if ( !sample.getValue().hasBasePileup() ) + continue; for (byte base : sample.getValue().getBasePileup().getBases() ) { int index = BaseUtils.simpleBaseToBaseIndex(base); if ( index != -1 ) From 82bf09edf347b42e8fe8e26c5e6af331611e3c5b Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 9 Nov 2011 20:42:31 -0500 Subject: [PATCH 049/380] Mark Standard Annotations with an asterisk --- .../sting/gatk/walkers/annotator/VariantAnnotator.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 8f4bc0abd..087218cfb 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 @@ -33,10 +33,7 @@ import org.broadinstitute.sting.gatk.contexts.AlignmentContextUtils; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.*; -import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.AnnotationType; -import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.AnnotatorCompatibleWalker; -import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.GenotypeAnnotation; -import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.InfoFieldAnnotation; +import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.*; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.classloader.PluginManager; @@ -180,15 +177,16 @@ public class VariantAnnotator extends RodWalker implements Ann private void listAnnotationsAndExit() { + System.out.println("\nStandard annotations in the list below are marked with a '*'."); List> infoAnnotationClasses = new PluginManager(InfoFieldAnnotation.class).getPlugins(); System.out.println("\nAvailable annotations for the VCF INFO field:"); for (int i = 0; i < infoAnnotationClasses.size(); i++) - System.out.println("\t" + infoAnnotationClasses.get(i).getSimpleName()); + System.out.println("\t" + (StandardAnnotation.class.isAssignableFrom(infoAnnotationClasses.get(i)) ? "*" : "") + infoAnnotationClasses.get(i).getSimpleName()); System.out.println(); List> genotypeAnnotationClasses = new PluginManager(GenotypeAnnotation.class).getPlugins(); System.out.println("\nAvailable annotations for the VCF FORMAT field:"); for (int i = 0; i < genotypeAnnotationClasses.size(); i++) - System.out.println("\t" + genotypeAnnotationClasses.get(i).getSimpleName()); + System.out.println("\t" + (StandardAnnotation.class.isAssignableFrom(genotypeAnnotationClasses.get(i)) ? "*" : "") + genotypeAnnotationClasses.get(i).getSimpleName()); System.out.println(); System.out.println("\nAvailable classes/groups of annotations:"); for ( Class c : new PluginManager(AnnotationType.class).getInterfaces() ) From 8942406aa2e364a3c68a6268282f9a0979acacc6 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Wed, 9 Nov 2011 22:05:21 -0500 Subject: [PATCH 051/380] Use MathUtils to compare doubles instead of testing for equality --- .../sting/gatk/walkers/indels/PairHMMIndelErrorModel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 3715d2d14..1e41a084d 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 @@ -619,7 +619,7 @@ public class PairHMMIndelErrorModel { return 0; // sanity check for (int i=0; i < b1.length; i++ ){ - if ( b1[i]!= b2[i]) + if ( b1[i]!= b2[i] ) return i; } return 0; // sanity check @@ -630,7 +630,7 @@ public class PairHMMIndelErrorModel { return 0; // sanity check for (int i=0; i < b1.length; i++ ){ - if ( b1[i]!= b2[i]) + if ( MathUtils.compareDoubles(b1[i], b2[i]) != 0 ) return i; } return 0; // sanity check From 6313aae2c4b7f2a8921cf80044cba3b2462396a3 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 9 Nov 2011 22:37:26 -0500 Subject: [PATCH 052/380] Adding checks for hasBasePileup() before calling getBasePileup() as per GS thread --- .../sting/gatk/walkers/annotator/AlleleBalance.java | 2 +- .../sting/gatk/walkers/annotator/AlleleBalanceBySample.java | 3 +++ .../gatk/walkers/validation/GenotypeAndValidateWalker.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) 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 e5f75f06d..3a21e97a4 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 @@ -69,7 +69,7 @@ public class AlleleBalance extends InfoFieldAnnotation { if ( context == null ) continue; - if ( vc.isSNP() ) { + if ( vc.isSNP() && context.hasBasePileup() ) { final String bases = new String(context.getBasePileup().getBases()); if ( bases.length() == 0 ) return null; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalanceBySample.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalanceBySample.java index 820fd248a..06e91bf26 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalanceBySample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/AlleleBalanceBySample.java @@ -51,6 +51,9 @@ public class AlleleBalanceBySample extends GenotypeAnnotation implements Experim if ( altAlleles.size() == 0 ) return null; + if ( !stratifiedContext.hasBasePileup() ) + return null; + final String bases = new String(stratifiedContext.getBasePileup().getBases()); if ( bases.length() == 0 ) return null; 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 fd55d78a0..e64d00bf5 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 @@ -365,7 +365,7 @@ public class GenotypeAndValidateWalker extends RodWalker 0 && context.getBasePileup().getBases().length < minDepth)) { + if (!context.hasReads() || !context.hasBasePileup() || (minDepth > 0 && context.getBasePileup().getBases().length < minDepth)) { counter.nUncovered = 1L; if (vcComp.getAttribute("GV").equals("T")) counter.nAltNotCalled = 1L; From 0d8983feee7655bca60db3657268902ddaece21c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 9 Nov 2011 23:34:29 -0500 Subject: [PATCH 053/380] outputting the RG information setReadGroup now sets the read group attribute for the GATKSAMRecord --- .../src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 1 + 1 file changed, 1 insertion(+) 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 d95570d9b..3fe1060dd 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -167,6 +167,7 @@ public class GATKSAMRecord extends BAMRecord { public void setReadGroup( final GATKSAMReadGroupRecord readGroup ) { mReadGroup = readGroup; retrievedReadGroup = true; + setAttribute("RG", mReadGroup.getId()); // todo -- this should be standardized, but we don't have access to SAMTagUtils! } /////////////////////////////////////////////////////////////////////////////// From dc4932f93d991b7e0247e86f911c64fd87d20083 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 10 Nov 2011 10:58:40 -0500 Subject: [PATCH 054/380] VariantEval module to stratify the variants by whether they overlap an interval set The primary use of this stratification is to provide a mechanism to divide asssessment of a call set up by whether a variant overlaps an interval or not. I use this to differentiate between variants occurring in CCDS exons vs. those in non-coding regions, in the 1000G call set, using a command line that looks like: -T VariantEval -R human_g1k_v37.fasta -eval 1000G.vcf -stratIntervals:BED ccds.bed -ST IntervalStratification Note that the overlap algorithm properly handles symbolic alleles with an INFO field END value. In order to safely use this module you should provide entire contigs worth of variants, and let the interval strat decide overlap, as opposed to using -L which will not properly work with symbolic variants. Minor improvements to create() interval in GenomeLocParser. --- .../varianteval/VariantEvalWalker.java | 31 +++++++ .../IntervalStratification.java | 93 +++++++++++++++++++ .../sting/utils/GenomeLocParser.java | 24 +++++ 3 files changed, 148 insertions(+) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/IntervalStratification.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 a9448bc06..21c1610c7 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 @@ -3,6 +3,7 @@ package org.broadinstitute.sting.gatk.walkers.varianteval; import net.sf.picard.reference.IndexedFastaSequenceFile; import net.sf.samtools.SAMSequenceRecord; import org.apache.log4j.Logger; +import org.broad.tribble.Feature; import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.DbsnpArgumentCollection; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; @@ -15,11 +16,14 @@ import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.gatk.walkers.TreeReducible; import org.broadinstitute.sting.gatk.walkers.Window; import org.broadinstitute.sting.gatk.walkers.varianteval.evaluators.VariantEvaluator; +import org.broadinstitute.sting.gatk.walkers.varianteval.stratifications.IntervalStratification; import org.broadinstitute.sting.gatk.walkers.varianteval.stratifications.JexlExpression; import org.broadinstitute.sting.gatk.walkers.varianteval.stratifications.VariantStratifier; import org.broadinstitute.sting.gatk.walkers.varianteval.util.*; import org.broadinstitute.sting.gatk.walkers.variantrecalibration.Tranche; import org.broadinstitute.sting.gatk.walkers.variantrecalibration.VariantRecalibrator; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; import org.broadinstitute.sting.utils.codecs.vcf.VCFUtils; @@ -178,6 +182,12 @@ public class VariantEvalWalker extends RodWalker implements Tr @Argument(fullName="mergeEvals", shortName="mergeEvals", doc="If provided, all -eval tracks will be merged into a single eval track", required=false) public boolean mergeEvals = false; + /** + * File containing tribble-readable features for the IntervalStratificiation + */ + @Input(fullName="stratIntervals", shortName="stratIntervals", doc="File containing tribble-readable features for the IntervalStratificiation", required=true) + protected IntervalBinding intervalsFile = null; + // Variables private Set jexlExpressions = new TreeSet(); @@ -260,6 +270,16 @@ public class VariantEvalWalker extends RodWalker implements Tr perSampleIsEnabled = true; } + if ( intervalsFile != null ) { + boolean fail = true; + for ( final VariantStratifier vs : stratificationObjects ) { + if ( vs.getClass().equals(IntervalStratification.class) ) + fail = false; + } + if ( fail ) + throw new UserException.BadArgumentValue("ST", "stratIntervals argument provided but -ST IntervalStratification not provided"); + } + // Initialize the evaluation contexts evaluationContexts = variantEvalUtils.initializeEvaluationContexts(stratificationObjects, evaluationObjects, null, null); @@ -532,6 +552,14 @@ public class VariantEvalWalker extends RodWalker implements Tr public Set getJexlExpressions() { return jexlExpressions; } + public List getIntervals() { + if ( intervalsFile == null ) + throw new UserException.MissingArgument("stratIntervals", "Must be provided when IntervalStratification is enabled"); + + return intervalsFile.getIntervals(getToolkit()); + } + + public Set getContigNames() { final TreeSet contigs = new TreeSet(); for( final SAMSequenceRecord r : getToolkit().getReferenceDataSource().getReference().getSequenceDictionary().getSequences()) { @@ -540,4 +568,7 @@ public class VariantEvalWalker extends RodWalker implements Tr return contigs; } + public GenomeLocParser getGenomeLocParser() { + return getToolkit().getGenomeLocParser(); + } } \ No newline at end of file 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 new file mode 100644 index 000000000..bf001588a --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/IntervalStratification.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.walkers.varianteval.stratifications; + +import net.sf.picard.util.IntervalTree; +import org.apache.log4j.Logger; +import org.broad.tribble.Feature; +import org.broadinstitute.sting.gatk.contexts.ReferenceContext; +import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; +import org.broadinstitute.sting.gatk.walkers.annotator.SnpEff; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLocSortedSet; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.interval.IntervalUtils; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.util.*; + +/** + * Stratifies the variants by whether they overlap an interval in the set provided on the command line. + * + * The primary use of this stratification is to provide a mechanism to divide asssessment of a call set up + * by whether a variant overlaps an interval or not. I use this to differentiate between variants occurring + * in CCDS exons vs. those in non-coding regions, in the 1000G call set, using a command line that looks like: + * + * -T VariantEval -R human_g1k_v37.fasta -eval 1000G.vcf -stratIntervals:BED ccds.bed -ST IntervalStratification + * + * Note that the overlap algorithm properly handles symbolic alleles with an INFO field END value. In order to + * safely use this module you should provide entire contigs worth of variants, and let the interval strat decide + * overlap, as opposed to using -L which will not properly work with symbolic variants. + */ +public class IntervalStratification extends VariantStratifier { + final protected static Logger logger = Logger.getLogger(IntervalStratification.class); + final Map> intervalTreeByContig = new HashMap>(); + + @Override + public void initialize() { + final List locs = getVariantEvalWalker().getIntervals(); + + 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))); + + // set up the map from contig -> interval tree + for ( final String contig : getVariantEvalWalker().getContigNames() ) + intervalTreeByContig.put(contig, new IntervalTree()); + + for ( final GenomeLoc loc : locs ) { + intervalTreeByContig.get(loc.getContig()).put(loc.getStart(), loc.getStop(), true); + } + + states = new ArrayList(Arrays.asList("all", "overlaps.intervals", "outside.intervals")); + } + + public List getRelevantStates(ReferenceContext ref, RefMetaDataTracker tracker, VariantContext comp, String compName, VariantContext eval, String evalName, String sampleName) { + final ArrayList relevantStates = new ArrayList(Arrays.asList("all")); + + if (eval != null) { + final GenomeLoc loc = getVariantEvalWalker().getGenomeLocParser().createGenomeLoc(eval, true); + IntervalTree intervalTree = intervalTreeByContig.get(loc.getContig()); + IntervalTree.Node node = intervalTree.minOverlapper(loc.getStart(), loc.getStop()); + //logger.info(String.format("Overlap %s found %s", loc, node)); + relevantStates.add( node != null ? "overlaps.intervals" : "outside.intervals"); + } + + return relevantStates; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java index 8d9768681..e10bcbaa0 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java @@ -35,8 +35,10 @@ import net.sf.samtools.SAMSequenceDictionary; import net.sf.samtools.SAMSequenceRecord; import org.apache.log4j.Logger; import org.broad.tribble.Feature; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; /** * Factory class for creating GenomeLocs @@ -453,6 +455,28 @@ public class GenomeLocParser { return createGenomeLoc(feature.getChr(), feature.getStart(), feature.getEnd()); } + /** + * Creates a GenomeLoc corresponding to the variant context vc. If includeSymbolicEndIfPossible + * is true, and VC is a symbolic allele the end of the created genome loc will be the value + * of the END info field key, if it exists, or vc.getEnd() if not. + * + * @param vc + * @param includeSymbolicEndIfPossible + * @return + */ + public GenomeLoc createGenomeLoc(final VariantContext vc, boolean includeSymbolicEndIfPossible) { + if ( includeSymbolicEndIfPossible && vc.isSymbolic() ) { + int end = vc.getAttributeAsInt(VCFConstants.END_KEY, vc.getEnd()); + return createGenomeLoc(vc.getChr(), vc.getStart(), end); + } + else + return createGenomeLoc(vc.getChr(), vc.getStart(), vc.getEnd()); + } + + public GenomeLoc createGenomeLoc(final VariantContext vc) { + return createGenomeLoc(vc, false); + } + /** * create a new genome loc, given the contig name, and a single position. Must be on the reference * From 714cac21c9d4b1a5a3d96b0c1299d1aa4d4348fd Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 10 Nov 2011 11:08:34 -0500 Subject: [PATCH 055/380] Testdata for IntervalStratification --- public/testdata/overlapTest.bed | 3 + public/testdata/withSymbolic.b37.vcf | 96 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 public/testdata/overlapTest.bed create mode 100644 public/testdata/withSymbolic.b37.vcf diff --git a/public/testdata/overlapTest.bed b/public/testdata/overlapTest.bed new file mode 100644 index 000000000..6859f1fdc --- /dev/null +++ b/public/testdata/overlapTest.bed @@ -0,0 +1,3 @@ +20 315000 315100 # should overlap 2 in withSymbolic.vcf at 315006 and 315072 +20 316955 316959 # should overlap only deletion variant at 316952 +20 317900 400000 # should overlap only the symbolic variant at 317173 diff --git a/public/testdata/withSymbolic.b37.vcf b/public/testdata/withSymbolic.b37.vcf new file mode 100644 index 000000000..4974f1229 --- /dev/null +++ b/public/testdata/withSymbolic.b37.vcf @@ -0,0 +1,96 @@ +##fileformat=VCFv4.1 +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##ALT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +#CHROM POS ID REF ALT QUAL FILTER INFO +20 315006 . A G 100 PASS LCSNP;EXSNP;BAVGPOST=0.998;BRSQ=0.721;LDAF=0.0025;AVGPOST=0.9978;RSQ=0.6449;ERATE=0.0008;THETA=0.0006;AC=4;AN=2184 +20 315072 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.996;LDAF=0.0057;AVGPOST=0.9996;RSQ=0.9743;ERATE=0.0003;THETA=0.0016;AC=12;AN=2184 +20 315162 . T G 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.950;LDAF=0.0005;AVGPOST=0.9998;RSQ=0.8078;ERATE=0.0003;THETA=0.0004;AC=1;AN=2184 +20 315168 rs71327439 G GA 7142 PASS INDEL;BAVGPOST=0.999;BRSQ=0.990;LDAF=0.0575;AVGPOST=0.9985;RSQ=0.9891;ERATE=0.0003;THETA=0.0004;AC=125;AN=2184 +20 315201 . A G 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.965;LDAF=0.0005;AVGPOST=0.9999;RSQ=0.8599;ERATE=0.0003;THETA=0.0008;AC=1;AN=2184 +20 315214 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.974;LDAF=0.0009;AVGPOST=0.9990;RSQ=0.5860;ERATE=0.0003;THETA=0.0005;AC=1;AN=2184 +20 315270 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.999;LDAF=0.0557;AVGPOST=0.9992;RSQ=0.9950;ERATE=0.0003;THETA=0.0004;AC=121;AN=2184 +20 315279 . A G 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.991;LDAF=0.0572;AVGPOST=0.9990;RSQ=0.9926;ERATE=0.0003;THETA=0.0013;AC=125;AN=2184 +20 315320 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.995;LDAF=0.0028;AVGPOST=0.9998;RSQ=0.9594;ERATE=0.0005;THETA=0.0003;AC=6;AN=2184 +20 315322 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.814;LDAF=0.0007;AVGPOST=0.9994;RSQ=0.6510;ERATE=0.0003;THETA=0.0004;AC=1;AN=2184 +20 315431 . A T 100 PASS LCSNP;EXSNP;BAVGPOST=0.987;BRSQ=0.864;LDAF=0.0431;AVGPOST=0.9873;RSQ=0.8675;ERATE=0.0006;THETA=0.0010;AC=86;AN=2184 +20 315481 . T A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.998;LDAF=0.0619;AVGPOST=0.9993;RSQ=0.9948;ERATE=0.0003;THETA=0.0007;AC=135;AN=2184 +20 315490 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.984;LDAF=0.0138;AVGPOST=0.9971;RSQ=0.9260;ERATE=0.0003;THETA=0.0046;AC=31;AN=2184 +20 315523 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.822;LDAF=0.0015;AVGPOST=0.9988;RSQ=0.6777;ERATE=0.0005;THETA=0.0003;AC=2;AN=2184 +20 315547 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.893;LDAF=0.0026;AVGPOST=0.9995;RSQ=0.9024;ERATE=0.0003;THETA=0.0004;AC=5;AN=2184 +20 315549 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.888;LDAF=0.0029;AVGPOST=0.9988;RSQ=0.8565;ERATE=0.0003;THETA=0.0013;AC=5;AN=2184 +20 315551 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.829;LDAF=0.0008;AVGPOST=0.9992;RSQ=0.5723;ERATE=0.0003;THETA=0.0012;AC=1;AN=2184 +20 315704 . G C 100 PASS LCSNP;EXSNP;BAVGPOST=0.998;BRSQ=0.945;LDAF=0.0184;AVGPOST=0.9978;RSQ=0.9523;ERATE=0.0003;THETA=0.0017;AC=40;AN=2184 +20 315798 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.773;LDAF=0.0012;AVGPOST=0.9985;RSQ=0.4929;ERATE=0.0003;THETA=0.0005;AC=1;AN=2184 +20 315842 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.979;LDAF=0.0110;AVGPOST=0.9991;RSQ=0.9673;ERATE=0.0003;THETA=0.0005;AC=23;AN=2184 +20 315876 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.926;LDAF=0.0018;AVGPOST=0.9988;RSQ=0.7731;ERATE=0.0003;THETA=0.0007;AC=3;AN=2184 +20 316028 . G C 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.999;LDAF=0.0714;AVGPOST=0.9997;RSQ=0.9982;ERATE=0.0003;THETA=0.0003;AC=156;AN=2184 +20 316055 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.997;LDAF=0.1006;AVGPOST=0.9993;RSQ=0.9969;ERATE=0.0003;THETA=0.0007;AC=220;AN=2184 +20 316137 . G C 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.976;LDAF=0.0037;AVGPOST=0.9982;RSQ=0.7861;ERATE=0.0004;THETA=0.0009;AC=6;AN=2184 +20 316142 . C A 100 PASS LCSNP;EXSNP;BAVGPOST=0.998;BRSQ=0.793;LDAF=0.0033;AVGPOST=0.9980;RSQ=0.7527;ERATE=0.0003;THETA=0.0003;AC=6;AN=2184 +20 316143 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.839;LDAF=0.0034;AVGPOST=0.9984;RSQ=0.8054;ERATE=0.0003;THETA=0.0003;AC=6;AN=2184 +20 316211 . C G 100 PASS LCSNP;EXSNP;BAVGPOST=0.997;BRSQ=0.976;LDAF=0.0565;AVGPOST=0.9983;RSQ=0.9872;ERATE=0.0003;THETA=0.0010;AC=124;AN=2184 +20 316285 . A AT 5514 PASS INDEL;BAVGPOST=0.999;BRSQ=0.993;LDAF=0.0552;AVGPOST=0.9978;RSQ=0.9829;ERATE=0.0004;THETA=0.0005;AC=119;AN=2184 +20 316295 . A G 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.808;LDAF=0.0021;AVGPOST=0.9980;RSQ=0.6390;ERATE=0.0003;THETA=0.0008;AC=4;AN=2184 +20 316481 . G T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=1.000;LDAF=0.0499;AVGPOST=0.9997;RSQ=0.9970;ERATE=0.0003;THETA=0.0007;AC=109;AN=2184 +20 316488 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.897;LDAF=0.0011;AVGPOST=0.9997;RSQ=0.8509;ERATE=0.0003;THETA=0.0006;AC=2;AN=2184 +20 316553 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.945;LDAF=0.0007;AVGPOST=0.9996;RSQ=0.7074;ERATE=0.0003;THETA=0.0004;AC=1;AN=2184 +20 316659 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.998;LDAF=0.0497;AVGPOST=0.9995;RSQ=0.9960;ERATE=0.0003;THETA=0.0007;AC=109;AN=2184 +20 316691 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.985;LDAF=0.0058;AVGPOST=0.9989;RSQ=0.9301;ERATE=0.0003;THETA=0.0003;AC=13;AN=2184 +20 316700 . A G 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.737;LDAF=0.0030;AVGPOST=0.9971;RSQ=0.5700;ERATE=0.0009;THETA=0.0016;AC=4;AN=2184 +20 316725 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.853;LDAF=0.0011;AVGPOST=0.9995;RSQ=0.7944;ERATE=0.0003;THETA=0.0004;AC=2;AN=2184 +20 316770 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.966;LDAF=0.0017;AVGPOST=0.9991;RSQ=0.7543;ERATE=0.0005;THETA=0.0005;AC=3;AN=2184 +20 316813 . GTTC G 1701 PASS INDEL;BAVGPOST=0.995;BRSQ=0.850;LDAF=0.0156;AVGPOST=0.9987;RSQ=0.9611;ERATE=0.0003;THETA=0.0006;AC=34;AN=2184 +20 316824 rs11472305 T TTTC 5129 PASS INDEL;BAVGPOST=0.979;BRSQ=0.863;LDAF=0.0697;AVGPOST=0.9782;RSQ=0.8837;ERATE=0.0016;THETA=0.0006;AC=153;AN=2184 +20 316839 . CT C 1122 PASS INDEL;BAVGPOST=0.980;BRSQ=0.804;LDAF=0.0505;AVGPOST=0.9743;RSQ=0.8100;ERATE=0.0040;THETA=0.0009;AC=104;AN=2184 +20 316841 . TTC T 1096 PASS INDEL;BAVGPOST=0.981;BRSQ=0.824;LDAF=0.0491;AVGPOST=0.9783;RSQ=0.8367;ERATE=0.0029;THETA=0.0005;AC=105;AN=2184 +20 316842 . TCC T 110 PASS INDEL;BAVGPOST=0.983;BRSQ=0.592;LDAF=0.0203;AVGPOST=0.9772;RSQ=0.5576;ERATE=0.0022;THETA=0.0005;AC=30;AN=2184 +20 316845 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.645;LDAF=0.0015;AVGPOST=0.9989;RSQ=0.6666;ERATE=0.0003;THETA=0.0010;AC=2;AN=2184 +20 316853 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=0.990;BRSQ=0.922;LDAF=0.0688;AVGPOST=0.9742;RSQ=0.8456;ERATE=0.0049;THETA=0.0006;AC=133;AN=2184 +20 316882 rs11479165 CT C 145 PASS INDEL;BAVGPOST=0.991;BRSQ=0.484;LDAF=0.0074;AVGPOST=0.9920;RSQ=0.5655;ERATE=0.0005;THETA=0.0014;AC=9;AN=2184 +20 316889 . T TTC 108 PASS INDEL;BAVGPOST=0.978;BRSQ=0.235;LDAF=0.0521;AVGPOST=0.9304;RSQ=0.4479;ERATE=0.0035;THETA=0.0016;AC=63;AN=2184 +20 316901 . T TTC 272 PASS INDEL;BAVGPOST=0.979;BRSQ=0.510;LDAF=0.0363;AVGPOST=0.9527;RSQ=0.4348;ERATE=0.0071;THETA=0.0003;AC=34;AN=2184 +20 316917 . CT C 67 PASS INDEL;BAVGPOST=0.983;BRSQ=0.483;LDAF=0.0265;AVGPOST=0.9828;RSQ=0.7509;ERATE=0.0006;THETA=0.0011;AC=51;AN=2184 +20 316918 rs112071142 TTTC T 3049 PASS INDEL;BAVGPOST=0.980;BRSQ=0.801;LDAF=0.0588;AVGPOST=0.9761;RSQ=0.8444;ERATE=0.0026;THETA=0.0005;AC=120;AN=2184 +20 316924 . CT C 8 PASS INDEL;BAVGPOST=0.999;BRSQ=0.581;LDAF=0.0053;AVGPOST=0.9932;RSQ=0.5513;ERATE=0.0004;THETA=0.0011;AC=6;AN=2184 +20 316936 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=0.994;BRSQ=0.651;LDAF=0.0098;AVGPOST=0.9884;RSQ=0.5505;ERATE=0.0018;THETA=0.0005;AC=13;AN=2184 +20 316939 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=0.997;BRSQ=0.720;LDAF=0.0071;AVGPOST=0.9899;RSQ=0.4845;ERATE=0.0010;THETA=0.0033;AC=8;AN=2184 +20 316952 . CTCTTCCTCTTCT C 15835 PASS INDEL;BAVGPOST=0.891;BRSQ=0.667;LDAF=0.1650;AVGPOST=0.9042;RSQ=0.7274;ERATE=0.0037;THETA=0.0008;AC=294;AN=2184 +20 317003 . TCC T 333 PASS INDEL;BAVGPOST=0.890;BRSQ=0.114;LDAF=0.3450;AVGPOST=0.5526;RSQ=0.1674;ERATE=0.0300;THETA=0.0011;AC=350;AN=2184 +20 317022 rs111424933 TTTC T 4776 PASS INDEL;BAVGPOST=0.982;BRSQ=0.865;LDAF=0.0561;AVGPOST=0.9837;RSQ=0.8889;ERATE=0.0017;THETA=0.0004;AC=110;AN=2184 +20 317057 . C A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.775;LDAF=0.0014;AVGPOST=0.9985;RSQ=0.6049;ERATE=0.0003;THETA=0.0002;AC=3;AN=2184 +20 317135 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=0.995;BRSQ=0.992;LDAF=0.0489;AVGPOST=0.9964;RSQ=0.9749;ERATE=0.0004;THETA=0.0025;AC=106;AN=2184 +20 317173 MERGED_DEL_2_99440 A . . SV;BAVGPOST=0.998;BRSQ=0.577;LDAF=0.0018;AVGPOST=0.9990;RSQ=0.7465;ERATE=0.0003;THETA=0.0007;CIEND=-61,92;CIPOS=-84,73;END=319201;SOURCE=BreakDancer_317182_319211,GenomeStrip_317164_319190;SVTYPE=DEL;AC=2;AN=2184 +20 317174 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=0.995;BRSQ=0.991;LDAF=0.0502;AVGPOST=0.9962;RSQ=0.9727;ERATE=0.0005;THETA=0.0007;AC=107;AN=2184 +20 317266 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.999;LDAF=0.1748;AVGPOST=0.9988;RSQ=0.9973;ERATE=0.0003;THETA=0.0005;AC=383;AN=2184 +20 317448 . A G 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.999;LDAF=0.0068;AVGPOST=0.9988;RSQ=0.9405;ERATE=0.0003;THETA=0.0002;AC=16;AN=2184 +20 317491 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=0.999;BRSQ=0.991;LDAF=0.0609;AVGPOST=0.9993;RSQ=0.9939;ERATE=0.0003;THETA=0.0004;AC=133;AN=2184 +20 317546 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.837;LDAF=0.0007;AVGPOST=0.9994;RSQ=0.6154;ERATE=0.0003;THETA=0.0012;AC=1;AN=2184 +20 317578 . C T 100 PASS LCSNP;EXSNP;BAVGPOST=0.996;BRSQ=0.882;LDAF=0.0180;AVGPOST=0.9981;RSQ=0.9606;ERATE=0.0003;THETA=0.0004;AC=40;AN=2184 +20 317658 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=1.000;LDAF=0.0609;AVGPOST=0.9998;RSQ=0.9980;ERATE=0.0003;THETA=0.0005;AC=133;AN=2184 +20 317676 . T C 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=1.000;LDAF=0.0608;AVGPOST=0.9999;RSQ=0.9992;ERATE=0.0003;THETA=0.0004;AC=133;AN=2184 +20 317710 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.952;LDAF=0.0014;AVGPOST=0.9997;RSQ=0.8880;ERATE=0.0004;THETA=0.0008;AC=3;AN=2184 +20 317824 . G A 100 PASS LCSNP;EXSNP;BAVGPOST=1.000;BRSQ=0.999;LDAF=0.0555;AVGPOST=0.9993;RSQ=0.9947;ERATE=0.0003;THETA=0.0009;AC=121;AN=2184 From 67b022c34b6a3fc624f813de806e00bd7d625a3e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 10 Nov 2011 13:27:13 -0500 Subject: [PATCH 057/380] Cleanup for new SampleUtils function -- getVCFHeadersFromRods(rods) is now available so that you don't have getVCFHeadersFromRods(rods, null) throughout the codebase --- .../gatk/walkers/varianteval/evaluators/CountVariants.java | 6 ++++-- .../gatk/walkers/varianteval/evaluators/G1KPhaseITable.java | 2 +- .../sting/gatk/walkers/variantutils/CombineVariants.java | 2 +- .../src/org/broadinstitute/sting/utils/SampleUtils.java | 2 +- .../org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java | 4 ++++ 5 files changed, 11 insertions(+), 5 deletions(-) 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 e83434037..cba2781d8 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 @@ -41,6 +41,9 @@ public class CountVariants extends VariantEvaluator implements StandardEval { public long nDeletions = 0; @DataPoint(description = "Number of complex indels") public long nComplex = 0; + @DataPoint(description = "Number of symbolic events") + public long nSymbolic = 0; + @DataPoint(description = "Number of mixed loci (loci that can't be classified as a SNP, Indel or MNP)") public long nMixed = 0; @@ -131,8 +134,7 @@ public class CountVariants extends VariantEvaluator implements StandardEval { nMixed++; break; case SYMBOLIC: - // ignore symbolic alleles, but don't fail - // todo - consistent way of treating symbolic alleles thgoughout codebase? + nSymbolic++; break; default: throw new ReviewedStingException("Unexpected VariantContext type " + 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 0e2ee70ef..3ab618496 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 @@ -103,7 +103,7 @@ public class G1KPhaseITable extends VariantEvaluator { } public String update2(VariantContext eval, VariantContext comp, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - if ( eval == null ) return null; + if ( eval == null || eval.isMonomorphic() ) return null; switch (eval.getType()) { // case NO_VARIATION: 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 ce03dfffe..573e15971 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 @@ -162,7 +162,7 @@ public class CombineVariants extends RodWalker { private boolean sitesOnlyVCF = false; public void initialize() { - Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit(), null); + Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit()); if ( PRIORITY_STRING == null ) { PRIORITY_STRING = Utils.join(",", vcfRods.keySet()); diff --git a/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java b/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java index edc1413ba..68b220aab 100755 --- a/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java @@ -150,7 +150,7 @@ public class SampleUtils { // iterate to get all of the sample names - for ( Map.Entry pair : VCFUtils.getVCFHeadersFromRods(toolkit, null).entrySet() ) { + for ( Map.Entry pair : VCFUtils.getVCFHeadersFromRods(toolkit).entrySet() ) { Set vcfSamples = pair.getValue().getGenotypeSamples(); for ( String sample : vcfSamples ) addUniqueSample(samples, sampleOverlapMap, rodNamesToSampleNames, sample, pair.getKey()); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java index 3c55c1ff9..5bd6a9b32 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFUtils.java @@ -51,6 +51,10 @@ public class VCFUtils { 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(); From dd1810140fc95e1a6d50d6fc0912bd9d26796d1f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 10 Nov 2011 13:27:32 -0500 Subject: [PATCH 058/380] -stratIntervals is optional --- .../sting/gatk/walkers/varianteval/VariantEvalWalker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 21c1610c7..4e4a1550d 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 @@ -185,7 +185,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=true) + @Input(fullName="stratIntervals", shortName="stratIntervals", doc="File containing tribble-readable features for the IntervalStratificiation", required=false) protected IntervalBinding intervalsFile = null; // Variables From 39678b6a20fc56f46eb5aff3732d9cabeaa9dca0 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 10 Nov 2011 13:34:03 -0500 Subject: [PATCH 060/380] Check for reads with missing read groups and throw a UserException when encountered. Mauricio said this wouldn't break integration tests. --- .../gatk/filters/MalformedReadFilter.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java b/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java index 11bbf9e4c..46d28185b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java @@ -50,19 +50,20 @@ public class MalformedReadFilter extends ReadFilter { public boolean filterOut(SAMRecord read) { // slowly changing the behavior to blow up first and filtering out if a parameter is explicitly provided - if (!checkMismatchingBasesAndQuals(read)) { - if (!filterMismatchingBaseAndQuals) - throw new UserException.MalformedBAM(read, "BAM file has a read with mismatching number of bases and base qualities. Offender: " + read.getReadName() +" [" + read.getReadLength() + " bases] [" +read.getBaseQualities().length +"] quals"); - else - return true; - } - return !checkInvalidAlignmentStart(read) || !checkInvalidAlignmentEnd(read) || !checkAlignmentDisagreesWithHeader(this.header,read) || + !checkHasReadGroup(read) || + !checkMismatchingBasesAndQuals(read, filterMismatchingBaseAndQuals) || !checkCigarDisagreesWithAlignment(read); } + private static boolean checkHasReadGroup(SAMRecord read) { + if ( read.getReadGroup() == null ) + throw new UserException.ReadMissingReadGroup(read); + return true; + } + /** * Check for the case in which the alignment start is inconsistent with the read unmapped flag. * @param read The read to validate. @@ -127,7 +128,9 @@ public class MalformedReadFilter extends ReadFilter { * @param read the read to validate * @return true if they have the same number. False otherwise. */ - private static boolean checkMismatchingBasesAndQuals(SAMRecord read) { + private static boolean checkMismatchingBasesAndQuals(SAMRecord read, boolean filterMismatchingBaseAndQuals) { + if (!filterMismatchingBaseAndQuals) + throw new UserException.MalformedBAM(read, "BAM file has a read with mismatching number of bases and base qualities. Offender: " + read.getReadName() +" [" + read.getReadLength() + " bases] [" +read.getBaseQualities().length +"] quals"); return (read.getReadLength() == read.getBaseQualities().length); } } From 060c7ce8aea6939c77aca62d87e8f5665bab1d80 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 10 Nov 2011 14:03:22 -0500 Subject: [PATCH 065/380] It wouldn't harm integrationtests if we had our logic right... :-) --- .../sting/gatk/filters/MalformedReadFilter.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java b/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java index 46d28185b..37a6cfc36 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java @@ -129,8 +129,14 @@ public class MalformedReadFilter extends ReadFilter { * @return true if they have the same number. False otherwise. */ private static boolean checkMismatchingBasesAndQuals(SAMRecord read, boolean filterMismatchingBaseAndQuals) { - if (!filterMismatchingBaseAndQuals) - throw new UserException.MalformedBAM(read, "BAM file has a read with mismatching number of bases and base qualities. Offender: " + read.getReadName() +" [" + read.getReadLength() + " bases] [" +read.getBaseQualities().length +"] quals"); - return (read.getReadLength() == read.getBaseQualities().length); + boolean result; + if (read.getReadLength() == read.getBaseQualities().length) + result = true; + else if (filterMismatchingBaseAndQuals) + result = false; + else + throw new UserException.MalformedBAM(read, String.format("BAM file has a read with mismatching number of bases and base qualities. Offender: %s [%d bases] [%d quals]", read.getReadName(), read.getReadLength(), read.getBaseQualities().length)); + + return result; } } From 153e52ffed98a0676d69b215c3bf1b803ac3176f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 10 Nov 2011 14:10:39 -0500 Subject: [PATCH 066/380] VariantEvalIntegrationTest for IntervalStratification --- .../VariantEvalIntegrationTest.java | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) 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 cbfe0ceab..3dceb9bd2 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 @@ -70,7 +70,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("1fefd6cf9c2554d5f886c3998defd4d0") + Arrays.asList("fb926edfd3d811e18b33798a43ef4379") ); executeTest("testFundamentalsCountVariantsSNPsandIndels", spec); } @@ -91,7 +91,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("d470e00a368b5a0468012818994c6a89") + Arrays.asList("26b7d57e3a204ac80a28cb29485b59b7") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithNovelty", spec); } @@ -113,7 +113,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("12856e52c2682328f91594089328596c") + Arrays.asList("1df8184062f330bea9da8bacacc5a09d") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithNoveltyAndFilter", spec); } @@ -134,7 +134,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("91610b9240f64e0eb03cfd2602cf57af") + Arrays.asList("927f26414509db9e7c0a2c067d57c949") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithCpG", spec); } @@ -155,7 +155,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("e40b77e7ed6581328e373a24b93cd170") + Arrays.asList("e6fddefd95122cabc5a0f0b95bce6d34") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithFunctionalClass", spec); } @@ -176,7 +176,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("15beaf3823c131cabc5fb0445239f978") + Arrays.asList("df10486dae73a9cf8c647964f51ba3e0") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithDegeneracy", spec); } @@ -197,7 +197,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("7ddd4ee74938d229ce5cb7b9b9104abe") + Arrays.asList("524adb0b7ff70e227b8803a88f36713e") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithSample", spec); } @@ -220,7 +220,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("a90f33906a732ef5eb346e559c96ccc1") + Arrays.asList("ef6449789dfc032602458b7c5538a1bc") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithJexlExpression", spec); } @@ -245,7 +245,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("2567f90d3d7354850c5a59730ecc6e4f") + Arrays.asList("13b90e94fa82d72bb04a0a5addb27c3f") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithMultipleJexlExpressions", spec); } @@ -264,7 +264,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("fa091aa8967893389c51102fd9f0bebb") + Arrays.asList("8458b9d7803d75aae551fac7dbd152d6") ); 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("f70997b6a3e7fdc89d11e1d61a2463d4")); + 1, Arrays.asList("b954dee127ec4205ed7d33c91aa3e045")); 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("407682de41dcf139ea635e9cda21b912")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("ae0027197547731a9a5c1eec5fbe0221")); executeTestParallel("testCompVsEvalAC",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("424c9d438b1faa59b2c29413ba32f37b")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("835b44fc3004cc975c968c9f92ed25d6")); 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("18fa0b89ebfff51141975d7e4ce7a159")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("f0e003f1293343c3210ae95e8936b19a")); executeTestParallel("testMultipleEvalTracksWithoutGenotypes",spec); } @@ -414,8 +414,29 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("da65fc8f0d0eeaf0a0b06a07f444bb8e") + Arrays.asList("924b6123edb9da540d0abc66f6f33e16") ); executeTest("testAlleleCountStrat", spec); } + + @Test + public void testIntervalStrat() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T VariantEval", + "-R " + b37KGReference, + "-eval " + testDir + "/withSymbolic.b37.vcf", + "-noEV", + "-EV CountVariants", + "-noST", + "-stratIntervals " + testDir + "/overlapTest.bed", + "-ST IntervalStratification", + "-L 20", + "-o %s" + ), + 1, + Arrays.asList("9794e2dba205c6929dc89899fdf0bf6b") + ); + executeTest("testIntervalStrat", spec); + } } From dc9b351b5e21e84b4296995ea97ae25427ab4a94 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 10 Nov 2011 17:10:26 -0500 Subject: [PATCH 067/380] Meaningful error message when an IntervalArg file fails to parse correctly --- .../broadinstitute/sting/commandline/IntervalBinding.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java b/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java index 86ca6c2df..f920d90ef 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java +++ b/public/java/src/org/broadinstitute/sting/commandline/IntervalBinding.java @@ -92,7 +92,10 @@ public final class IntervalBinding { codec.readHeader(lineReader); String line = lineReader.readLine(); while ( line != null ) { - intervals.add(toolkit.getGenomeLocParser().createGenomeLoc(codec.decodeLoc(line))); + final Feature feature = codec.decodeLoc(line); + if ( feature == null ) + throw new UserException.MalformedFile(featureIntervals.getSource(), "Couldn't parse line '" + line + "'"); + intervals.add(toolkit.getGenomeLocParser().createGenomeLoc(feature)); line = lineReader.readLine(); } } catch (IOException e) { From ee40791776dfcdb8d192820c9a61f3a1b0a01012 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 09:55:42 -0500 Subject: [PATCH 072/380] 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 073/380] 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 074/380] 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 075/380] 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 40fbeafa3789abee8eeb84eba467e2be600bc110 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Fri, 11 Nov 2011 11:52:30 -0500 Subject: [PATCH 076/380] VQSR will now detect if the negative model failed to converge properly because of having too few data points and automatically retry with more appropriate clustering parameters. --- .../GaussianMixtureModel.java | 1 + .../VariantRecalibrator.java | 17 ++++++++++++++++- .../VariantRecalibratorEngine.java | 10 ++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/GaussianMixtureModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/GaussianMixtureModel.java index 3fa9c3883..82776ca2e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/GaussianMixtureModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/GaussianMixtureModel.java @@ -52,6 +52,7 @@ public class GaussianMixtureModel { private final double[] empiricalMu; private final Matrix empiricalSigma; public boolean isModelReadyForEvaluation; + public boolean failedToConverge = false; public GaussianMixtureModel( final int numGaussians, final int numAnnotations, final double shrinkage, final double dirichletParameter, final double priorCounts ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java index f60a94a22..20b000978 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java @@ -309,8 +309,23 @@ public class VariantRecalibrator extends RodWalker negativeTrainingData = dataManager.selectWorstVariants( VRAC.PERCENT_BAD_VARIANTS, VRAC.MIN_NUM_BAD_VARIANTS ); + GaussianMixtureModel badModel = engine.generateModel( negativeTrainingData ); engine.evaluateData( dataManager.getData(), badModel, true ); + + // Detect if the negative model failed to converge because of too few points and/or too many Gaussians and try again + while( badModel.failedToConverge && VRAC.MAX_GAUSSIANS > 4 ) { + logger.info("Negative model failed to converge. Retrying..."); + VRAC.MAX_GAUSSIANS--; + badModel = engine.generateModel( negativeTrainingData ); + engine.evaluateData( dataManager.getData(), goodModel, false ); + engine.evaluateData( dataManager.getData(), badModel, true ); + } + + if( badModel.failedToConverge || goodModel.failedToConverge ) { + throw new UserException("NaN LOD value assigned. Clustering with this few variants and these annotations is unsafe. Please consider raising the number of variants used to train the negative model (via --percentBadVariants 0.05, for example) or lowering the maximum number of Gaussians to use in the model (via --maxGaussians 4, for example)"); + } + engine.calculateWorstPerformingAnnotation( dataManager.getData(), goodModel, badModel ); // Find the VQSLOD cutoff values which correspond to the various tranches of calls requested by the user diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibratorEngine.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibratorEngine.java index adfb38a25..6d2ac643b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibratorEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibratorEngine.java @@ -67,14 +67,20 @@ public class VariantRecalibratorEngine { public void evaluateData( final List data, final GaussianMixtureModel model, final boolean evaluateContrastively ) { if( !model.isModelReadyForEvaluation ) { - model.precomputeDenominatorForEvaluation(); + try { + model.precomputeDenominatorForEvaluation(); + } catch( Exception e ) { + model.failedToConverge = true; + return; + } } logger.info("Evaluating full set of " + data.size() + " variants..."); for( final VariantDatum datum : data ) { final double thisLod = evaluateDatum( datum, model ); if( Double.isNaN(thisLod) ) { - throw new UserException("NaN LOD value assigned. Clustering with this few variants and these annotations is unsafe. Please consider raising the number of variants used to train the negative model (via --percentBadVariants 0.05, for example) or lowering the maximum number of Gaussians to use in the model (via --maxGaussians 4, for example)"); + model.failedToConverge = true; + return; } datum.lod = ( evaluateContrastively ? From cd3146f4cff4bc81aee7fd728dfb9c9a080e4247 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Fri, 11 Nov 2011 14:07:07 -0500 Subject: [PATCH 077/380] Add hidden option to ValidationAmplicons to output slightly modified format to make file work with downstream SQNM tools more seamlessly at request of GAP: one line per record, keep probe identifier to 20 characters, no * in ref allele. --- .../validation/ValidationAmplicons.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 035d8d2ca..088c4ddc4 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 @@ -7,10 +7,7 @@ import org.broadinstitute.sting.alignment.Alignment; import org.broadinstitute.sting.alignment.bwa.BWAConfiguration; import org.broadinstitute.sting.alignment.bwa.BWTFiles; import org.broadinstitute.sting.alignment.bwa.c.BWACAligner; -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.commandline.*; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; @@ -126,6 +123,11 @@ public class ValidationAmplicons extends RodWalker { @Argument(doc="Do not use BWA, lower-case repeats only",fullName="doNotUseBWA",required=false) boolean doNotUseBWA = false; + @Hidden + @Argument(doc="Use Sequenom output format instead of regular FASTA",fullName="sqnm",required=false) + boolean sequenomOutput = false; + + GenomeLoc prevInterval; GenomeLoc allelePos; String probeName; @@ -485,6 +487,13 @@ public class ValidationAmplicons extends RodWalker { } String seqIdentity = sequence.toString().replace('n', 'N').replace('i', 'I').replace('d', 'D'); - out.printf(">%s %s %s%n%s%n", allelePos != null ? allelePos.toString() : "multiple", valid, probeName, seqIdentity); + + if (!sequenomOutput) + out.printf(">%s %s %s%n%s%n", allelePos != null ? allelePos.toString() : "multiple", valid, probeName, seqIdentity); + else { + seqIdentity = seqIdentity.replace("*",""); // identifier < 20 letters long, no * in ref allele, one line per record + probeName = probeName.replace("amplicon_","a"); + out.printf("%s_%s %s%n", allelePos != null ? allelePos.toString() : "multiple", probeName, seqIdentity); + } } } From fee9b367e49e8fdc798c9d511ecb1f7b9eed9efd Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 11 Nov 2011 15:00:35 -0500 Subject: [PATCH 078/380] 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 76d357be408d4f58348aafc32b6df63c59d79825 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sat, 12 Nov 2011 23:20:05 -0500 Subject: [PATCH 081/380] Updating docs example to use -L since that's best practice --- .../sting/gatk/walkers/annotator/VariantAnnotator.java | 1 + 1 file changed, 1 insertion(+) 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 087218cfb..58762c054 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 @@ -70,6 +70,7 @@ import java.util.*; * -o output.vcf \ * -A DepthOfCoverage * --variant input.vcf \ + * -L input.vcf * --dbsnp dbsnp.vcf * * From b7c33116af89ae3b5cf070fb1f461ac3d11a8746 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sat, 12 Nov 2011 23:21:07 -0500 Subject: [PATCH 082/380] Minor docs update --- .../sting/gatk/walkers/annotator/VariantAnnotator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 58762c054..ea11391d9 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 @@ -68,9 +68,9 @@ import java.util.*; * -T VariantAnnotator \ * -I input.bam \ * -o output.vcf \ - * -A DepthOfCoverage + * -A DepthOfCoverage \ * --variant input.vcf \ - * -L input.vcf + * -L input.vcf \ * --dbsnp dbsnp.vcf * * From 1202a809cb8ac10193a4fb2cd49f3b843e18f82e Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Sun, 13 Nov 2011 22:27:49 -0500 Subject: [PATCH 083/380] 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 34acf8b9789a263b991b65477e28b8fe3f25881d Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 14 Nov 2011 10:47:02 +0100 Subject: [PATCH 084/380] Added Unit tests for new methods in GenotypeLikelihoods --- .../GenotypeLikelihoodsUnitTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) 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 9243588ab..f3d0dedcd 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java @@ -29,10 +29,13 @@ package org.broadinstitute.sting.utils.variantcontext; // the imports for unit testing. +import org.broadinstitute.sting.utils.MathUtils; import org.testng.Assert; import org.testng.annotations.Test; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import java.util.EnumMap; + /** * Basic unit test for Genotype likelihoods objects @@ -69,6 +72,50 @@ public class GenotypeLikelihoodsUnitTest { gl.getAsVector(); } + @Test + public void testGetAsMap(){ + GenotypeLikelihoods gl = new GenotypeLikelihoods(v); + //Log scale + EnumMap glMap = gl.getAsMap(false); + Assert.assertEquals(v[Genotype.Type.HOM_REF.ordinal()-1],glMap.get(Genotype.Type.HOM_REF)); + Assert.assertEquals(v[Genotype.Type.HET.ordinal()-1],glMap.get(Genotype.Type.HET)); + Assert.assertEquals(v[Genotype.Type.HOM_VAR.ordinal()-1],glMap.get(Genotype.Type.HOM_VAR)); + + //Linear scale + glMap = gl.getAsMap(true); + double [] vl = MathUtils.normalizeFromLog10(v); + Assert.assertEquals(vl[Genotype.Type.HOM_REF.ordinal()-1],glMap.get(Genotype.Type.HOM_REF)); + Assert.assertEquals(vl[Genotype.Type.HET.ordinal()-1],glMap.get(Genotype.Type.HET)); + Assert.assertEquals(vl[Genotype.Type.HOM_VAR.ordinal()-1],glMap.get(Genotype.Type.HOM_VAR)); + + //Test missing likelihoods + gl = new GenotypeLikelihoods("."); + glMap = gl.getAsMap(false); + Assert.assertNull(glMap); + + } + + @Test + public void testGetNegLog10GQ(){ + GenotypeLikelihoods gl = new GenotypeLikelihoods(vPLString); + + //GQ for the best guess genotype + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HET),3.9); + + double[] test = MathUtils.normalizeFromLog10(gl.getAsVector()); + + //GQ for the other genotypes + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_REF), -1.0 * Math.log10(1.0 - test[Genotype.Type.HOM_REF.ordinal()-1])); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_VAR), -1.0 * Math.log10(1.0 - test[Genotype.Type.HOM_VAR.ordinal()-1])); + + //Test missing likelihoods + gl = new GenotypeLikelihoods("."); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_REF),Double.NEGATIVE_INFINITY); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HET),Double.NEGATIVE_INFINITY); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_VAR),Double.NEGATIVE_INFINITY); + + } + private void assertDoubleArraysAreEqual(double[] v1, double[] v2) { Assert.assertEquals(v1.length, v2.length); for ( int i = 0; i < v1.length; i++ ) { From 6881d4800c3f782255152f63d453f12451a96509 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 14 Nov 2011 10:47:51 +0100 Subject: [PATCH 085/380] Added Integration tests for Phasing by Transmission --- .../PhaseByTransmissionIntegrationTest.java | 122 +++++++++++++++++- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java index c663c1dd7..2cd76e7a5 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java @@ -6,23 +6,131 @@ import org.testng.annotations.Test; import java.util.Arrays; public class PhaseByTransmissionIntegrationTest extends WalkerTest { - private static String phaseByTransmissionTestDataRoot = validationDataLocation + "/PhaseByTransmission"; - private static String fundamentalTestVCF = phaseByTransmissionTestDataRoot + "/" + "FundamentalsTest.unfiltered.vcf"; + private static String phaseByTransmissionTestDataRoot = validationDataLocation + "PhaseByTransmission/"; + private static String goodFamilyFile = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.goodFamilies.ped"; + private static String TNTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.TN.vcf"; + private static String TPTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.TP.vcf"; + private static String FPTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.FP.vcf"; + private static String SpecialTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.Special.vcf"; + //Tests using PbT on all genotypes with default parameters + //And all reporting options @Test - public void testBasicFunctionality() { + public void testTrueNegativeMV() { WalkerTestSpec spec = new WalkerTestSpec( buildCommandLine( "-T PhaseByTransmission", "-NO_HEADER", "-R " + b37KGReference, - "--variant " + fundamentalTestVCF, - "-f NA12892+NA12891=NA12878", + "--variant " + TNTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("16fefda693156eadf1481fd9de23facb","9418a7a6405b78179ca13a67b8bfcc14") + ); + executeTest("testTrueNegativeMV", spec); + } + + @Test + public void testTruePositiveMV() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + TPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("14cf1d21a54d8b9fb506df178b634c56","efc66ae3d036715b721f9bd35b65d556") + ); + executeTest("testTruePositiveMV", spec); + } + + @Test + public void testFalsePositiveMV() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + FPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("f9b0fae9fe1e0f09b883a292b0e70a12","398724bc1e65314cc5ee92706e05a3ee") + ); + executeTest("testFalsePositiveMV", spec); + } + + @Test + public void testSpecialCases() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + SpecialTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("b8d1aa3789ce77b45430c62d13ee3006","a1a333e08fafb288cda0e7711909e1c3") + ); + executeTest("testSpecialCases", spec); + } + + //Test using a different prior + //Here the FP file is used but as the prior is lowered, 3 turn to TP + @Test + public void testPriorOption() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + FPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-prior 1e-4", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("7201ce7cc47db5840ac6b647709f7c33","c11b5e7cd7459d90d0160f917eff3b1e") + ); + executeTest("testPriorOption", spec); + } + + //Test when running without MV reporting option + //This is the exact same test file as FP but should not generate a .mvf file + @Test + public void testMVFileOption() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + FPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", "-o %s" ), 1, - Arrays.asList("") + Arrays.asList("398724bc1e65314cc5ee92706e05a3ee") ); - executeTest("testBasicFunctionality", spec); + executeTest("testMVFileOption", spec); } + } From 3d2970453b79fff8cd89dd5e5984d3cacb9b58ea Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 14 Nov 2011 09:41:54 -0500 Subject: [PATCH 086/380] 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 087/380] 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 088/380] 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 090/380] 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 091/380] 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 092/380] 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 093/380] 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 094/380] 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 096/380] 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 097/380] 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 098/380] 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 099/380] 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 101/380] 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 102/380] 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 103/380] 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 104/380] 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 105/380] 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 107/380] 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 108/380] 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 109/380] 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 110/380] 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 111/380] 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 112/380] 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 113/380] 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 114/380] 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 0dc3d20d58986a323eaeaef7bcdee51e9f642826 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 16 Nov 2011 09:33:13 +0100 Subject: [PATCH 115/380] 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 e56d52006a9a31079960bd55dc9249b10892c93b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 16 Nov 2011 10:39:17 -0500 Subject: [PATCH 116/380] 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 118/380] 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 119/380] 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 120/380] 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 121/380] 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 122/380] 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 123/380] 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 124/380] 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 125/380] 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 126/380] 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 zcmaHS1#n!uvaK0oW@cvQn3FfbqrPL6`+Ch1d#ARr*t zf4=B{zVhN~!VJ<15=`>K3epnds%nh#5(JYhaQ@6+gyy^F_#VIzIy^AAAcdXc>X(GF zBG2BHv%m5`v%!^>PdnZ!*P!Yl<#4byJd+gJI0Y-69%lDmIH65yvjt8Fl%iLFE~zNR zMoFfyRknw@86A+`oVerDT6|=|Bd8>;QvSL2O;ve_%DT55QymiW9pxN z{V5Qq-Rgjm{kJJ$NYQ`vL_z+eGZCDMz|PT9)y>$=Rn^?Z!`$V61>;{G0)?PLw|8{2 zw)pQKW+2)A(fFrP8ZtUR6yATL;rd?@r%F-=$4yh<1V{dF*TSLx32ZnFbsDoeI7HG7 z6!hO+eTU-ttN2G>5hLNg{z^&|4tCio&dwJT z&UaAZt-BMD6~xV8&by#q)I%5R+{zfnnRke%R61i%|Y-!oe?1a!%H$8X3uOC0GbQYF0 zhN8$%GbpvRlMrA9bYt%*#P4RO9}ANxp-&1T*Da|XMIW}(zw!&4Ut%=!n1)~MsaWJ) z<8^DGXuy!E8Yq5;^Nt3PBOtI_o7dH4k{T)tYj>=Dca2?-73FScdu$cU3y{Y!L)8$m zfwQ6Mo<^tj#7rsr1k-e87#1X>X=qxaIHoemt6F)(_6v2$osTGehQw%QrCOG0Us;lK ziIHr0=n46YLGJegUzgX4$7pnl`OyTfz*fvzY6AXFKHIH^qq}k~tC&5Yn3d^Qru~cMtvDkON zy85Dsl*)ku7ITOL1=M6w7z7l_vPR`U$4HpE)d?WcX-s?qym)aC#2&JdeEXGJMZ;c4 z`R@(|U~O*c9H;8cKQ-?Nn@6Rho84Hz8)g|rfuOK*r}@`gKvM#UX>Kr{KwB#al9XfvYKPNU)4k zEkjsMJ#YoxUX{jyqG`c)9~Fg7ZzQESK;9(XMkxEb-e!Vx0f6S`Z{b_2rok~*)zA#t z{Ta@+sKXi)=JLC$EtOff60L-MV^q!RYyXV5%h~2iy?RC<8%^8O8JL$T&9lUB#9v9C z<@7#5YlWP!)S#D;l`=9*Bh$e@9SV!c!@S|$wnV`@^W*6%ZxGC*{$#+1`_YijCXj;y4b*=<0HB9`3O%6 zIJiY-^z>F%8&(5$0ei6s1Ww?X1?QAs;}pDcCtSVP#%2a#2SZ;S z?V%q?iQ;GgPV_{}BLig|$l8n{$r(*V7$VKgrTMv$(u6abY?wrTVN|Mdj17n6vS%2f z+eWk9)}3tvb%45nYD!KVUM?1{2yi^xRSPXj5`ZR)7S%ZW<8YOdhh^Gb-nf|Ef*1Q; zkOYmT7+;hJJC}~G8*ednQtKeoL-W_co>Cw_ZnLsW2($T z_%my-0RJmQ=j^AD_5(DiS;5fe^z0=nnWsJ~mH1O~6Ce%349+S7cNg{D2SeY!#5N0jzuOUMPcTKwLzAfn;>^hik7q3pMsdv zsS>YxoUgRP@GV?iWpwCY4z|~!Ij?cB-C?@i@MyjE=-jfuG*xZvz24+w;~MvWh{MQm zz4G?GzJKj;eID4z^NR>iy>|uR@}y7r49J5Ee4p3)BJT+_Qe@vw3lGWSM55$yVZkuO z+D3DT3_18NuiIJ6_H99~Mz*EJ-i#LEsFZg>`a*a5P#=gEHBxluE;sWt^HOUwF&x`~ z*60qQB-+drVA6M^N-{lHZiV_7i>G33DpY1dYfO8}wva}F79_#?9UKVQ|9SSqrWENV z+`3f3;f{G4M*KkHbxx=?VH5Kz7KhZKkP3f;P(k8o1NY&F^1$zLX;=45c>|d2tGLp1 zTG2AD=+iX<1)R%0P|+eoR~~)Htgg99h{A~~@>fL!_p(LA(zT`PYHtoV+*k6-p+dP3f-|SttCpMm zBN9(fc0xT85xeoKQJoGYGBI6c2gYP%hGjGc?X_P7W~@ zIA*>I1y2cxxdE|r^(S07?T@~sQapb^5oWVj3N>-oehXKCHlL~CYeMZUAr4#6? zRYvxZeJ8r|fMJ9|ggbXi#Oj}rvvBJDswGRB>JONXTcCm%j}z9&Pm5I5G~AyMwt}tK zMl|ILt--_Xi`UI8UV74IC>01woudFA&YuG1T$j$+7`pinrnU{hi!&=5w{4~wCg5=z zK!3k9LQ|H^pV(d5ZDr=npXNCuv!NYpkdd|-6Q6y;t7;RL9yjF~lxhx<*NR*LVW8u= z#Q}L~;)Wofo1{r##~i;+LO9a?x{w7eDJ@&yK@;HpT*mtz?T4uQNWO0kfoW1b(9>Nan0>)q zL&Ul-0M$awez03VWfQ>BNN-K-Lb{$5lCASQMxnRlGnw4wU5PuDS6)X*=Y{FHRM_LK zRpC<<&}SWM!OZITo-cIIknVI9lsnXp{JiB1_4#jVX)yN(QI^ z?gV#xGH1)-RJ+tsr5~T9W^~xgkV6lgT||JaOI%jM*MuawSXdq#BdPD@yNX;3sm)Oe zFqhYhvi>mN6=r&k@2HiCI0{FU!T;hBerFAivKxU2c3~7QLh&9dM!Jl0#qq`;B+Jg2 zTU;ZDyVo{TqEd6VhJYR9TVdy`I0Gn*w+_|$=4=Ah)ChQ7vpi`xs&|kn2WzDcY>^h!bS2o0Iyb(-_pQjp+Z~BI!vMr&Ik`_r zdkrh`6A&;rSUj;JYSv$9AuASRJy3@3CqT+}`4Z|i4$a1}6KVAc(-ePWH^@L)*xp}aRY@VhTJ`S5{`}Rm~w_!u1X=G!dlD=~G0F?*NfArX=+7oVV@Hms|E8YR= z;BW3Q|6($7IZKi3Bv&Uoa|fW={&7#?0&w{?gh~@^N9KrvXOKCG7P;H6CDn!whZrAE@Vz)#B%~~51vC`^RYHdeM!wqRF#@`t=hN7sP<@a& zcI#;4Xub2PKJ{bCc<~bH;TIy5OllMClszj*VR4nX_Gv1hcR$nsCXTw??Yj6>N6~4@ zWqi2r2JU4-<6iy;)EC?&I91{k1bKFAZKG2Aga*lL+X=DdIw}aOwocNnE|R+>1duAd z#6IiTa^5%+PK)fa#sI4?69jyl2+r8wd=_AVFW2$c7hK4Gc{5zM4021B&Cr^tF*|u> z`)di&rOLO=GS@1v5v-%{U3r4UB)+4`x>3XBvUR%}ANlm{wkidtSBCa8J4N^1l}g)m zMXZ78^Qal;%MYZ3>(w>B4How?xI}$x*@#xUVGEpMJ}^TUI)GKn_qY7uXYjXhDIerA z5~}L&IStpKeqw&a$6r(f?G|m`#GsxU_P%CJAFM{@4e|=g3xrFSG_@;@a7_+Qoc$6# zOQfMc%6{fU6&)uWWKZ|JH)>Fpn&Y7^%NBcPNRlLaz)Zqw^&F7?pm$7-1M+ z_x)tmiD03s9`FUd-LXoLNWpFSU7n$#n@BJU#;|Cl;9gw6lsP078%6AZI~RKvap{t$h{ylDw+r8rj!Av?H0?|Wlx%71Lyuqb z(vp#ySCELY5Z} z>Ov=CNcl6rM!+k~mi9NU-_5m&BZJ7U!B-sin-`V*QsPW=VH0{}4}HuS zT7a#vILqnpj9v0>9Mk!>`GcJ(Eh{PrUpC=^h{9LD1`-Q5cvXM?aNyGIkiJG1fSa~d z3M4EV73Kl~3IsedDs6}>n&jxVpYprND`9!FyRh0OCo+?|=}-<)CrJ=si(9rp6$!Xo zNOALHpn90vUl0)Q{j@uTss@>7V@FAnECUEF#WRRJ&^MDkC`XN`{7B_dY`=#P@Ufe? z%Cheicwk8%+x}oLj+p zfNJ(Xj)drX2Q>t|uxXXbLp1rXV2mSrvF!4S3{#NjNYR%gBw-N=Yg@$lm~Yy~_yE9r zrrH!ozAv}c?i1t8>Y$q zp9DmE{5#)nhJICoZR?*`+L1A7LN)2z(k)5#;_~pytptKg^c|?M@4EEdd?m;WA3jlz zIz;LxZ>XWYf1%|TTh_4n8khFsY6S4gkzsz_3)o0NzVgSq4!*uFoR)&qcVim(7)&mH zwPF(Rp9g>b24nn=BDOR5KrbM|&GBIF+Z%B!xFDRFFM3fR*&a@K;B7TK_zP;$H|+o> zyqBmyz+aI8AbZVy$}&=0fW>^A@BEh8JsRMeCpJ5-(6M?k&lX;(2>ajy$_!9??0eUN z9Ci53M!I_O@~rYrIbe>|e-h>!F2kWYdt@#a1`v)nAUBSGQ{huAZv>}b8RDC!JTW=E z){zI=%LsA{8aGLXaOy#L|GrAvTZT})N#rz+s|}dQIJP3LYnEv?9v$6OtIF(`g+t*{ zb`;9|KDX^${;uY;uJ;$O*E_)N)84G*X(O`Ghy5&MXSUkOE1QzEiuojVjtHZVw$r|$ zx9No2R=-gt_oG0Yz2S8=Rav7`sr&^zJJ#${T+SlYVS zBl6AuOq}fDJV>Ooli zs+;wq0f8?qFTeK!GLtZmllcvNJ?&o$dl4(f$a)+k z13!O|;8KKHWV@Q9~yuG<%A7W`yT)lBI2zaGfx7}6Rl8E&Wo}7zrXgi+ z+siLJsNL}@;@v~SZ0U!mi4DaJX1S;Rmk$Et57Y+GT;dY;lLaa(y*5Dc#^~zJgO)T- zeJ1nLRa`RnUQ!u;c1H@B2QGk{Vp_z|n8yv(wLU>$5g-6~;wl*!Pa8H!waA5|gt>Ny z!DC#Uua7chG~*U5QZarZ8x>Ky!$bQ|;V{P1r}5;$V0_BFPtkWkI+Y>`HO$;OUG0~l?!Qp2!ybK60_aNvz+BygnQH=6GU^GmhC7K=KWW?U5vXZ+OdA&Lv^K-lqPJD z?V&1bs@ej)7m@YZ^E2kwxhP+j?jby3V>GMXw|mUx7nR&`5+g0V*<%kzNeT*OCs-9; zUz@EL{BSY0caA*;Yz?l9_Xp6bU!?i2DdZx;=wk2-(4-RIsu=q86t{5tz&7 z0^@~qyll`F7{j)r8slItHz@r$`L`N}U@in;v_d7 z7hH(<&Wi9_$Vzvh-Bkf=>|DDQ-fBL^rYu1Cs^}4a8V3f;0w~mIP1s5UUOj5e>SLf2 zwSV3DlVu-ldm=4B7xq-ICUS!KB_&{rZ~a?)QA)x_Q(!W(FC=1>Cm5euJgcbCYLW`7 zfCOf!5Oel2DxYBeiVg#Bbw)N19yS)tk-u_@~^ zzhw?Q=cyF!44f8m4QLQ$eoe(VG)Re&&EJCjX>65}xJAPS9hYIP%sAt7if@*w5+5CZU%9KXM(7x3I@$BQL<6U+ze_jxO8S(6-C4*w3YAQcn0}@mX zT6XST;ZW`FVUG=feOKVPDB#W1A5u&ppEfzSX*=O?Txo0e8m^7jqum2?3zVyn_+tUc zPHx}V-LeD^9t+v1oS)l_!o1-fs*#gFB_uZ@iZW zT&I2?hyuaxiZ{PfXlgi@n&kp=A3=^v!qoNsWyu7_xU^Cts`89{h^E*&4GXN~Awl(Vn8<_R<@+#3&ZvfrZ%#^!bGN^?@Vwj0Li1 zxVP`T!Dk|>kqjk^e%4&&IfRW{q~C>!Yd}n^Ir1>~?qz!ttQlxgmUiQ6N!nG>&gd`CQL^m#8GKvy;^9 z0{rUn(zLTw-0AW8_WK*8SgVk|=T}X73fB^Wal5s;`|KsKnF6zUYPi)W7xCR1t!pE@ zomi&=y&W`T8Jx(sJ#M+1&c zHn7>5TbrK1YLjHgCo$wpTw0ho#Aa(*Lxx^ylX~CwW`ktke2yX>;SDzdp%(`PBkL^x zN{nrLk3IL9M2>%ze5B)#^){_TyJ z7{s0~^zQ3#*=G3Xluek<+mm?dH8e%Tc zu(@tG{t*SlDC*1tcM26pUjx*qw=^4QrLj37vre8IVJEt2+PN5hNz^oXYVD4swipTS z5z3v!nAB9DPaWUC2CYr`k)?kDWL9#tv&bZ61rELpXf+%Te;>11WvVkXxT`U7?-SVoaLdNFx$V*7&!hagq0 z-h|o4D|a^0#^%E1FwuS-$J_iSL%xm1=%2kei9cay6hDj^5S}D+@XaDtYEs3YPHQ|f zn&GxW4=jpY*OWa6EM36~s^wATPp)~114|*3b*8$R&YlI{zE)4-@^JYH@%MC7Z{1Sy zexM5$wniNsmU?Gk@i+ubrr-ho99f;0ME5SaKkhXcLh}FCSD1x;zu8Qgdt=5hft?AhSsSxj+W>AFEF~d=#;&6d zetW7p=q_^$z~>7)&-ATmmA*NAQl-$h&841=M*B`}aqL!yw(HKOCD@a-;Ji?So`ig{ zeQJTz0D4=j;$A6K5o=P3awlDogmnJ2uSV*Jtb%?~)RBu!?T0%FmB#7*`p<}MtJy96 z%<|4t2dv4_$%=Ub9I~t{8uU9zgvCX?*)({X!NVh5!2RzH%3nqt%A|mNZY5|cy|;yD z^PnujFV8^#cV2HiXkP>Cuf5;1btkw0A2;XVn>3$K)@3=cowc^adpGV#wr;IL`6 zfL-h7?;JaQcfnh|q`6Mdnj>fmfbaZ!ceRmJ?4OUf!5c);RKAR#xIG*IvK@y{ogRS? zi=OTe!16N?MzAE==D;UHr4{q>qB{w$r3yzWCyP#|xsgI`*G?ef@m^QoW^S`E3;srS zwQo5{ch1_Ovk528+hn1`1CUxVrd6QPU|+08ui4Fu~>PyV0UxRPD&I)33Uk z${lhE`$<4mAGAUGp$|OGk=T*G@J>w}a82|Fq|$2JKI>5t?X)P2LS*_@nY-3E-B2um z#fG|-vF-Y*(XR2v^vl=qu4Djbk?ivIz zK|=8ZvG4FMXksLfXUl>k;0rFkzk%b;W$}q%R|| zp$F%y;g-&HxnvKQtE6wM)VN+r345>Q!G%^LpyFq;Pcqc9o(-Py0(goo2gsrhWsB|G z4sT;W2{huOcE5X;ct)r3HvWQmY$rkjAj42mpX)b`h}*k(#S6rkb47%4pO;!V4l^~L z6rj&l>M)$TacMWu!f=R*+q{7C)c*VXcB3V#7cw~r@TL|4iW^lA$Z>Dt#vd|xQ(CW4A(J_9p#6p$3%gaaMbt5iUM@0bqa^g+DQl~#CIU@|L z>+(niv&+N{p1WD)@!}rW?~U03)F-wjp_==h4n3ve1HD|J6czOoC(kPNmRVwwXwxjQ z=~YJ#^cc1_q;Y1M#S5Pp(5${xuh%vAr}2l{;`vsXYAx^L@q}hV8J=Nh$Bq|q*3*iG z_uA$C5b8AWHjaKeDfh~47_|n(#xqhQ=uE5LAWX{IbAxLGy0a6M>YXJ40{s^a{3_tD z-i0CgR&~n8+g9`;JEM>b>GIX<>>qvPk;qfMJPX>UD9VkswhJo)XB#|c=XuG@H-c2I zPpBnCMqWf>XwK`AWVAqsk$Kuy`+@ssMBR#1D9fT4fZAE7SHH?x5T=->q33%0q(Wj9 z0vB=#6C!?dROor^Y;Fob4Y?;ARs$K8Rg%Cfz3{7qfgd*A?Jcclb&Enu_qDPlrfEY6 zJvXP#rKSR#{&2!QD!ybey_8|rB>wuYQWrqB>hl%&T+mI1d-u7<{;`ijkFb^j&%_4= zKHsX1eI`Ldov1(3xqp?N&m&V882RQTSJ6IzUUNgwf-xdkN0khaAzYo=`&Q`5(^;#= z{<99`+(97V=F7*eO5In3BX_QkHPkm7^Ur&=@o5jd{@-`jmh5gL3EQL*0xgX7(tN

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

-JL-b5U;1b2PLgx5=Wg^*zL7XmO#?ou$CA{_&i^=gebFx?DC1yd4G@-oU}l zJ}o^J>g|cSglj$^K^u*VN@19$4~hRaT?~y5zZ6?e94Gj?jEa?uyId)c1x6Sp(*>V& zP3#I#=9{4&I~SdjeUhCBP+KW1&y*7JExGO9uP`p)^ghB{lB?TDl0G9RRrO7HW)>#> zg88)K`~bshFI1}1@5@vjd$R_JA5rO6r&yI@H(aQglvYlPmII*AHJ%+@NFnN-uD7NKAX!*(a5WC39Z5 z>R!ijw>2%Cp7PhjVO^_ZK+un^B>Att%Hv1Rjc3$L>i!BZ9OE6(ZE6FgoPnYl)%lXH zKZcKkO~|MZo}L*8)8dfRG81bQ$+=(;%2Mbu%bSn=V*YvvI`s2Mb`0T0pm#+K z8!_lz)Wm5;wI;fyD8WJ$^*>)XP+SR>EM3;jwoZ^lScGThLS8o6$6PI_p^{ND>i0!=%`+0OTGGG>~ zwkvjtYn+|6N`j$iGBC9?4P@u3C0^0J9&+mDsAoIca-hqG5X}v)ox=oHkS^mRV5<%A zz1;Yel_@ncrhXOS3ZV0xTj7agiL-HVPGhpVI%IKMRt|*n96AAX3}UvtJrzbvhTDkT z-?E@bO$w_|f2unTj}eQL@%?IiAeo>4JVT!1|B9t-xf!TC@^XkeahJD%KB|yy9d{A} zufEGl@;~vCo9Y#uvA}$Fp6Q46E;e%j*E`qD@t?s0V|UMBx*b<^&Jdroz!fdAS^R7( z*kwh}<}H?*K5zm=D*!U!?7E% zUiY@G`nz6|C6CmlsLwy#LQjueyy9#Ra0&VCDAAcM{~~k#C8W|7z-11PO}O1f^H0%a z$o)!JA<;Lw9%DTdvW50siRy`s_K{4f^N8A>0X!oJ14{&mtPp9=3~--}-g@+hfwJqh ze$0SP(gtOQP0HJOghnX-LrjRx=u{UThYOV?HkkK_vM_`Zk5p_3X#xkIqNU_E$YptflBdN0;|DBqzmD=VGnn^5?hvZtsY^?QuMes-hH;KoDPHd0WC91;Bn_TE6G16V+i)3GRTIlTY3mMlH7XcGQ@m zj&yKRoB<(u#D7b*RGBp4`a_^ssFVy+dm@#|J8S}I@`*{`L2 zAd55EIa^KNn>Bf#dMb1qC|x))j^ZJ=ImAiWF~-u&z7Fz^(i|^@f4U;`c@>ON89)7( zZn{Aoa;rzYb?GKJLXh%-6T2@DeB_w*L6F8;IgL;zr@xH!u8x8a(crW8i_0#8wu+uQ zF2NdrjFf6{k=NzL^ocJ&E#PxJdtkPwHaVc~0B$?LbnZraV99#&Ph2wY%4L+eX<~dK zVZ{btBGfC zmZ<Q| zkF86{pwBhSVs>+gc=|-DU9fH*1Ln@@Lm`Q|Cas19quB?vs#ZK$ACdmt1IL{ojrog5 zXU59e)HB-q{e%xZ*HK&+5>0)rkLa26J2WAua@E9r?u%d6)-|=%%>%rok$t&cd0Q(J z=~Oq-&SFyKK3&g@bR4R;XI_?c6Ico0?S6jNc_)eGuwjNCZ}6(yV%ty|iP?K$VOHiZ zJ=_#i@B=(dONfw1B<^nwM!YN^foZE3*{A8-X%gXvt|8(3wq2YZoSVv|Aa7+KC#Cr@ z(G{OkBC%<ylX)K3oi{e z8ccBgnhgE^!CbIoof4Cat5Kd#elIXer;NWXN~br8>3Vu1&hOfMJyIs4h_9>es!@}; zwhAT3qbQ-a6r=0|p*KURW<+i8L3I4D+mI><6mK67#0P8|GF!ZN#(yv1wyqlTJ9G<&f)B%=VAA z3;TA8U?=g+zODyaPcr*$PG>a#=*Me86(XP>?SNJrYP-ed07M%HfBpR~{>swZx&tn- zSrAFex`CI(bz^4ekC$-0YI!^xPC-6@wxxA*_PtAr`LlI1E|ctak(^vzRiaIOc6)h4 zQ<~(m|4}np+l&mCl}!_%K9IY9&Y~jz1IpsGDS3LJw) z({n|*IJ$7pdoEwJ`=Vfv2$nxabA>{)c!*gspg|FO3aKaSlXy6P+nC0W?!Ee#mGW^; z*m(u>=a%omqq!fz{v(3spQ(Z4xSLKx+jK-TL%2j))KXiEpWi0pvW&B%E3D5&y|8E_ z&F<1f^`=^QsHyf4v84&4vZxj{J;_`(xm0)yjIL+!qRNKJ^VlGBlqJOuQlW=Ot@q1H zw@jvQEJ$3dYh9+OQr7JkSSibtg)rp{Z-T{BR_%$83{(vI!Ou(bzy!iK`2VGA%n~eH zN>V|$;2L|f@(njcM7isv;zj|e-|#zKOb=e0|C8)rrP&{ z;HA!{f`X;`k?`Q8QVxJ(0pc|B!2_2;=U@J$&swDD)^oF*0q%z&81MRCFJqZzud|@Hd`B`b}{Lqj(P}*Y2A)+z?wvdsHxzA!r4-lxLjL1`LOU0CmC9I2CcGaXaPq z<;^!s)NXR0vU6B@m>(p49%BiXfE)?`KVu=m(iVg9b@4(egnF~^=_!vx8*{nTLMTJE zo6tJfxc|}}ZpxVQu>ZI-0WjaFSPk1uDL|d=|B%nC6Y=cj2$oG9t6CS=gAyO(5PQ^ioHxK)Ye5DXI6c3vBr7qb?70+e?=>Z3(f=#C}ewSTdN2rYLNIg0@_H4MX}!!NtJV!6@@y^ z{<%=O^F)XQ;Y5?jnf;>4T*{Q|@ywEg_SA?Z;T4Y3BhaM+1nIs9P2itWhfr&o(QviL zm?T&yG(A(PMdgt3ih9WAV6D)_I&=w{MQ!{O3XsTdcK40SeimbBdHQwMZMaJ)(R^gt zvlq9Xma0044Y`zB zm1vDb1jS^fa8gY$@PD8Sgptk28dpU-Zd~;nBe!1g9~+5EauybLJmr3hn@e`QP=p!g zhC>Dpr>1;fd|x_tw7)-3R(61_hvlizDNZHMMlfFE@g)Y?YROdOC{3|D7U88VY~8H3%J=r>Gswu8?4C9RHGZjhA*jD z55N!8DAm}Dv=i_#O%#=CjaOsDRTu|bbMiL&46UFv*rhI>3Ul$9nrgT$(kU0V%5sSi zc`9@3*?CPD1^SraSZ}E{aF~^c4O|@;YeRXCu?^1(ZE=^^;JQzumbL&)QTS*YMSs9D zM5C6lEcevwQm&|oMq^nuNIRFUN(Y3{up>G(^6+hP)Fp&Lu!c%?14Dv?&gIe$eJ7@< z>57EVukqP8^s;U88`*M@Cg&m%t7Y|uog^rl>4F2>lC6ceUX3<%Wg_;XaI)^n8 zBGMq%)jEtn6B(^piHq{9Pi!9IZ7i3Fg7UwEs{5=~5Jq^F$@^GWj?|Z42ng11H=4vl zGiRSedfqSW{@$rS9Mr)NYj|_xaWKoYd{sR`W?T)ykpUTaZAaz21m=X=m$N_w;5K$A`>JZ^dGE zgN!X!hCgm9X6*wg1_z0)6%qeC30Xh0t-$$E@>8O&(;|=Is{SS}>}&eHniP>Gq9qz} zn9M6H3{GBjsACHvRK}x{pRWbID$0y7S!iZjbii0m@TG7{CoWxPUbsmX#di5y5@P%u%(kX;}r}N%(Zfqc3;&H@nq2ZoQT> zcZZ^9-YJv>E(e|~r8rRStlnn@t2%m=9MJ3}w1c#Dj%rC0sBI-Q6bDETY`BM(O{yqS zRIgH>vmWrm-5dnsbc{8&md0)I@lWp&qn(`SR=m$^(iy^WDEMlp@O}GW@fkM z?lF5$OBe%~f0wCj^LgMSZb1BVy3S1c2Ut@^wA#3iwkF!h2*CEV5h>;GM7XTTqj`%p z1SX`esxn11p@(ff#q>JtQo8(%imnR>3q+ME2d!e3#e#@ zuI8aVt4C*;!zCtRL#qu;%==6lgQ^Xu;;A*cC%P{etUD4zv%=@fmilv z1!#q$HzH4Zrpr7C84kJ`NMq14V_?APj_?teQ1nKJ=y&qN;`k+VXjJZ5tQqbL zRlEk12y$R_JAh$2{^3;Uow@CynsurJ|Sg(zkZjvz(Sz@ZjIFdXJE&p> z@|!>-M|{@r&;>HcM22X$Em53-4#_&jb^77ok9lc!9!-r1XpJkWb~g@Lx9b%w$jMTf z6K}K=RAvUc0>mSijkpOnLgwB(9D$ddDthD5C85IJe05z?uRKG~{JfDT&1&=&`-AF$ zjB>Nv;@7<`O>cL+ryNx*)7vruEOaEgqqeodWUwg26UJ(sY$8@fgDTv?wrHB0Jvz@g z@kNJK`&V+y^^3jfiP53ouS&~j`_$gFnl43+REMY7-V<6ab3`I)Lur{5w)`IJr0T9i z5$ip3!hPXIKiP$MA2>_=yW(vEXnMb~k52UeAIQWX`8hNb2MGi;4xi|s`oC`~8B!k9 zmX=A-RLK88r0`E3uQ6yKAVTz1e>5=S)Cy7_ctElmj5F#I<~JF42XhA|gRX`FRJ>~7 zq5oO{vK0XmAwl0@?jGSU8?sDN%+h8Q(Lh=f6`Cdl{MC0` zEBxl?vgYQK#*;^f^#!ROFEb|1doBz3$?dk6zc)YMFFtpHB{3ixLHt$}3Jmn7VjV`= zY5=0Xzt86KlTxn5$fhibtPSbr!b}@P=vXRXq8pQX%oz-c*>lIwHg_6of%1LMZB|A3 zw0DgrWR<$6UPRRb7i?}cEeV)lQh~s{DdVIKcP#p5?5<=^bJko-*s;~bd_Pre%(}9s zGss~6#ys0~-6aE)$U+vX4664UnQ}w>xPUem_8E(;IQt38eG!`RVy$|M@k-{66-`Y+ z8&6G-gIdaGO422?vY6Hw3uq~I$mZ`9otB=l%{{yNgzw^3N<3V2fL1;3)14kz39HuF zYf;^#(%=?Rs+jzT~iAPiH z$kv)#bp}j?MdK&5!cRrd8rztSa=|VrQY4Ek0$Z$|gx|n-m{tMM&}{6G&j93XYtQ*R zJ&VwF6S8Kd?Qs@2=eQ72p^YJbT`nat>0!69Zb z-DdpC!h?T*2BT;e4!D9qRUTT@NNgHMaVChwhM-Q^waR5oTBrI~#G^Os(psaODc=x4 zt<2>)>*XcqL01%PPi8pQECJwe!S^Pz!Cm)^Hm}DdPgodky`>{&K+-!U&V@}X_7b5@ zV`x^6IH~85b9iS9&>1k?a4m+7t7MqfF-H zxF^{D70xfto0#E zUKn@}^>?KLCLBi>U%~F|Fx`() z+n@7dZX}4Wc$|!T%yRKXxpIhu{eX>56Pv;F8;U0{e=xGVPcLw(pGsEW|b z6>*_BSV)fJwAdo%lMg4Y(`C$MeY>!;QaI5?IW;4fRvLz697HV&ZJ0$rKA@+VMvN64K5 z;QGx*bQ=Fo*}YyjTi44Z55Emc4!f|ECe246iDC zlww|PVoH~7pajg&V*5N`c9$i!M5uj2fB;adG_F#Jsb&+#i;V76Q*IZM^4hdXKZBXm zg7CgygpLgM;Dq2Y^xzNaYryYQjk0IbH~Ha2lfDsmruS=JA3y^`UzPpNyx$Lxt%D9@ z(o{MNIPeF{M6#x9Fa|3lb}xx3syRXuWCPQ@K;xmGAOX{dUT4K+fgB%@F37#!cS-&~Q@>l^-lN2aCPYWBE;vllS21hzM8F(3+b>V%QFh6|E8gq1Z4u3U**i*aRA*c z`g(Ppi<#NkmG3OECHLg+K-VQU&Kx4s(+jWecCh;cG=2} zegGYZcn>0+d3{ipG^?sY2zgS>HoI{xm%LNnL7E1Co)b4i#;Tl96slKvv5*$3;sI_C zzev48ZMt>%)DwOz-<$kh+2_~Ev;dTLg;SfFkuI&UnG|io6|R*duGWXayU=ozw5x&C z4gi?ye&5DDpq{gWpj++OE2I&-1$%Nv_Idq3n`G6iQC5u4NW$c?3l1oEE}nHbD|yM( z^iOU^%w0D|j7FuQ2jVL*7`Za5m6CekS99 zJWB6L^`u=3f4~?hD|vj_6)}nfb+U_G79i|Sa@Q6&yd%3cJluhny)W}M95I6t*bQhS zCcgs!>YOs$#&e2Ukmu3jMEN^P*kkE{*xn7D0x(#@?Q|K=<-7FLX~8!Bg!0 z$djs#P+rnl1QWbXJJ4!#{ACoF4(~k8QoBlL8nZVhy8aD;{WBLwj9syBN~Jb-3h=E~ zMqw!2`qhYmpZe7pCp`v3o%QCB=R`Tqfmf+GK;?s>7R!%YdgsXU_{Ve=`#$`j z7VBPhE_2GuTWVB19zwob8&Hp5G-7Y4tO4gm{TsB0KP=Ua%wE_cPqC_`vi(sq~+j`J)*JL_Ya(dWJXoxJwwEZh8lr?#V&-;Hw|i_B+<*6ZS$) z?IT{ggJM^3{yzDlGR)7Y3gEVP&N<$}?T{7>*kpaZV14_eA)4(*5rBp_&M&;r1dAi$ z<{3>^Cg7g_nzP)Y`lv+&g~`(}AlVOg^iK#;tePum-vcqrSd=m~BuYg=k25wEkw6bo zmNfPM4hN|5xZCUx{Tt^YFPN!wPK_(nA(!sKN>ngI)$lH#=k6Da=e5+YHhjT%JxANz zlgDo9TQ>kkgBymMoz%hUhUAW9MQP0rCiH*fo|@QdS(M@v;@+^(OMcfxn>qbdj~qTglI_{}(35 z+^|$utSyhCi1=OHVPmm|PQ&i+n5f+WhK_{lpN0_*6_yGr zl#g&)Z#|ykn%bxx(0v+dv5$W{Hi&_&FB1k89ggq~1PJ06&2n-L93F(oe4b=?n%?YA z_x*W$!uI2=97G~SrCBJ|5G~O$t+T?>iqU=?FIIez?J>2Ecu-twxCC^0F2x9wHl|Jz zFHmIK&bha0*VFRHVC2=DF2nVll{`A~7%fzDnXY;KxlXYWs@!jV zxjX^YLW%I8Nk(tB%{*$S&Go<$DpISt9dKd8OUD^(T9v2(Z8FTHBh<=Fq|Z-Lck_X7 z*>8;jMBX@6yxE|;bpm*~p}@MZ+Gj_8Ilb8&9lgDI00;eogCa>*mx)#gWo;y)oJE7v z@*8F;1ZK%2jArw9#9DWdB+wNm6HvzD0%j@5Ly;8P2sv0sI8WB?Xh&noS-Yia=%jr& z;AJWQ?p7w2vHKj6Nr7_sSZzM%g9tlbUcQ%aZ2%E8dZZwrQvzfX7p_imwc6o@w6%H^ z)3u!~RiyZ=AQhv^%Yy$!8t7+W&m{<;*p~^euX{wTKiflHV;TX zyJV*8>gkKUA5y?E#)i1qua9eK<$OXhQ#O`I3Gf^z5C!0N)^DX1;!P^k+bZK<&jGob zBYv>Zc+HWv6-DYvX&8)4=3hh+t?uP*?S7-u)U$%K7FlHAEKSv}lx&vWVjQ&>@8qd6 zeIsFJKi#qSuig&Aww^CU5-k{pu!OK-jS$F=8a5KhXiVm3c^A^sz8ttQJrS+N?LPD7 zpDhqL0Wm@gDZXHB?~FN{7WOV=zq{3|z-Xr)y3+jrJ=W>@I{HMfIHo>x$X`Ojxg^nW6z(Xt{kz6w)0^R9Hljnnwccmv1LUb# z)#@U~fg(zM{GOIdmYSP&zqYEsU8~lMcQ{|B51}HLWSCB}e4e*YxIZq{x4&+zN`BD{ z?Kq=C>C796eR#d^hl7S*p9;wHtffk&0p_A?*3-?!i;Ze^r=87#7hKZ=O!`BF0d9XHSet|$KC87JTS!HgpxjNfeZf|eTH#awVco|uZJQuq}+gN3SMKm`< zCK(q7Etx)RAO2k~pPpR56KQ14sk;FCyt?Zyv@lyp=?#F|pw<>AY~HZQ(cTe=2lP&> z0M3{5x>ZuBCPr>6rHYVK(n%t>6UWJ}VxmOqx@&PzZ>uf|zgxEmcFU^IP-iogxM}!7 zsbEk4h1f4*$!poHecMuR{c$CSq+ZQ9XGF<1<)T}mo9j?2gX#_B1n;Lv=F zZ``RU?arT%EoaJ-M8GM6 zgD)Fq3t%EO#YQI*%*lkIdMrJGcSm_drMruH7|o|F!hx@oG;!ou$iqvFlY!{Rd(u1F zwl7N+U*`RyRu1?-zKHro0P9XyknGkJ+syDCOGhl6pT9I@0)p26Ltxz%%L0mpc09346G(bzkB@2qC z^tRJ%?^G_FGgceppNiJfHLkUpqr4WZR=5^s>;(4MFgIdAq{VG;No!P( ztzdkwE;wCZ(Y(DE>?X0p*yn7q^&z(NmyW?6m?$!C8T}0wteUA+Pi9ra*B}>%W!FPt zJ(O#)(v-?F#ovB(z~*82=A-?t5Se36r|1Y@vX_tR_}=23Py3X}o9f!O9yM;S?_~Ti zhxxAI%EqLgr!7M0E=VR^bxG~Vk5>gv9^Uw>!m4l+1w3a^xv`Ox!GP2a zk|u*lTCTav$-WDL-`LZcsA>Ew{LMrqoy(Vm!Kq${r%sp%5M!{~+wjV`W2L|~E5>(E zs$4Wzmh;7slZtjc8Z?@B%{Q`)-^)0q-b65~G9`%=3RY7})IlVn5Y z%^_Fy3t#Sjx*(Xe;F`wnvAuJS63X%`srKcdQRJbxH>->Tn$3s11~4mSwg|=8gTSAt zvOL;Xt|5+=0A&UKh&c^@RNT0nXPO>Kw4G z(!OJSSee$ZrA>e9@O*&AhM8{buX)l=#AucvbLlwwJW(uCOOE8=#!K-L95p3a$tUh` zm2~}W211Q!^s$gLUhGAqDV~-MBRhypNoF96&JXwvV5% zLAWs6^TH(LyogB7s)lht{~R!fP7V5$KKCZvL1_&D){n*fV>eoIrGZ6V1&6&ik8{oA zXMw!Ed6ECiX@NniZS|Pu`Biij9~uXXvuT8wq_#t|P|Ox%c5q91IE2C4 zCUC2qOA6UqyJ7c&OMA&QcNE$m{m~8piyF>y|B!cepoYeU%mW6G5hF(=uO8BU$fGc^ zSq%F2lioXt!c1;QMuV!1zpVGtKS8>w((>&>z>_tt_L@Y-Y8c1H2)D-4wIBpuf77Ic z_ASpxcd9+F<7x=|*b`$%XHVic!n4Is{$@`+;x7_WQYAw?@Ih!-W!cfNs@h<{kPEc% zcV^Wy;@Rz%vuQMqp7v}@wOyp{XDOq_4w~{+ zX!-%>{NM*U9_r@0tERiGRyg`n*x3m!Nqh%pKl#l7{R8<=45}HPr;vr!V-P6YDU= zcO(Z(o;TJ{e-^l#x_XHW^+}Ut2h$0S4!&XXS&wxc)`xP>#h`>4q~$t*NQ&K2(?>Q# zn|qKLr6Xn3iIl9!VodEeIyZ@IffKzJ;v1}VG3$LAf4fQRuno#*Q+&&8+`wP44$zX^ zNTI(VONLB`%d7byb zy0zHGnYK{25m5u@0FD~cF8g9XeP-NV$GNrXeDk7g_LBW^Vkd1;#6ZyYvN_Urk~88q z4Y%WQQU@ZUazVi`+UM+PR#@E9K#moex2CYLf(eLzAu){4XiS%aAfjYeMUi-X7ELhq z$|uZW@?sx^b!0@diO|6+3m4tzWu?VtOV73_4tN|k-med#%{JBN$_Qh=<04aMLcTGi zvYo&9SeVn0#TG;Ea*=7v4tIBJ47IRb;1DX@_kr8m+Jz@HF0(#oJP6lIEc5P^opWE8 z(F0(GFVU<}*Oj=3$y?1|Mz|KENEU;M^RsvlvN2d?q3K~;BUEba==C&~{ZTVBP8kB7 z3ejXs)D)I61!EJEG)<|^c&_qk^*ab4cwE2^7gALor#Hu1Hs{C&&1`S8V!)ShPRFAv zLV?Y2Oj{q&R<>wRp9?=Ua?Fs_DO=n}k_9X_1_)wVPfFNj3z9QtGh}?sf16iM$h2E< zCDw7FoiToy=w(QeQ&SbB&pzav^JM7379SZ&9v3N}u>_hjqQTFA8^)_h%~@v*B<&a# z56o?Mr(s5fwUMRH@Fv9KaHuPSsqXhm=>MhcR^nau7l$=Ds&P_gUASbLpgt^RS^zjM z44Y5VbP&M(NV|{~_?#K6h&t*-=f8w`Upx039u{eF4dlMbkQ$lG&jHVc%#jP%wf;`w zP@dCtW|J|p4e!6wJrY!eP|h~DjbFW&cw5nwMyKXVO(9U>s!AIdLtV&Xe2%(843?d1 z#XP^+G9cR^#x^V-RLf^Z)<%&O3kFQXw-b%!pjNYlXft|*HnE~+gw~8==ZU0|Wj8LP zyTtSZ7rz|@&%FWxf4}WH7dkmP70O3iDLm{|XR}5nYKfFo-QL`{S@9?sWai_HyC`!n z6c&pV!c2_6U+zB6V{z@|Hpwgu$CvT0Talr;gh_UlMTbxHpH+t#&*#Dz1OT86<1*<2 zK9^542)n1iC3`b6AIcu-1O1nz1+#0pc1e&xq>!XbA= zhm-Jt=45PRfbrXv43Dj=U;$d-qhPLyD;a^+M|vR)CfGcC1aClcaZu<*Qy0+CeWOJ# zL{S(AIW+Xi>wW}9@$(UwDH}REbQLJkB?IkFF>1IHt~gq{rRKyz_ff0;9^=DGv^T@`exv%tgZtG8d{u|UpR%K~&EP7|RE8C@0guI* z-84Cc;nm2K5o>4WmjN16U6mqJ@p9=g9|`;}!m1*=WjgF5r;e9v!{gFa_!9U-&Vz<* zMG+R%NfcdOmPUZOy6=%HfWtLl*@gC0$;qZ>^#Y#-jEt%TVtkYEgini*C2`c55@I1M zA3o&PYfdYif!M-<;fb(figCy^8MyUQ#pP#LU_Fu74o5Xb^#LVzyS1%tq`d9$qwBM| zC5m8paMAt8q$zix!QDlt8;`iF)F!c7WF}lEr^$PWB?GW!HMX;E4pYPxa15b;d4y>_ z9&xVNPd`TYL+#MmeFf(bPDNqNGl|EhOdSmctg43g_y^w$Xl?P!Ejk~WzE*Sb38~0k z1M-C*QKUQ73IT^4i>j=@?gG}}KW_=T@=wi>%Q+2@FB4WhWams8hh%$zn!RXN>8T&Q z3OC=2RNQ*7R2QQou?9r9G6i62PCwZDcUy>#2g`-(PzJpt^c)uOdk2ZT-^fJ)%e^g( zzy=!$DFN>_5#PnqCnA7#*`HqHiE$>R4cQ-HM~~yyGXTE?h*M_{4b~|(uYeu(JAGH6 z4=-}LxbVPb9Ckun9nI=0Bpvv{7ORW9tTuory5IQp=+LA%u}Gkj2h2aWwzgNWrR`8c1_f=q|7zCtq;wtR+8rmM540((h1WD2*6HrR!;dVIBX8oLB zVj)H;7vTJT&o7R5j;<<=gO2y;b{>P1SsSkfw`^K;BsZ5>*MU(^k1GW;Cb){B48Q2c z;UzY44qdvgkDQX~(BhsW-AizGg1L3lpmkAmMB9=gXRM@`7FjZBGd_(DC%|_2VWuOu ziFS3f*4M==Gt(?Ehd+QW)GkCz=wbl(_1fH46M(CSZ?1_BOY7zYb}z-SxV?-F4NgF? zG)mlz{S^H3dO>KMdTrimwThc!+q6w9Jq#LaWKT{1clrUYIvZ$Ki?v~&+1+1t+CTu< z;(GAhhBO#eD+Pk<|1E- z1$YM(j7>b@Ft}6tIpT;-d?ZAqI)Eh6kY=h@Oyww~2!yfRISx^Cgt4TYM~-p(PBPg= zoit>s=TAb3?B9vB)6AI-PqZJUhH%;r6F0ldX?hT8qRY-mCZ{VI&SaBw7Kj?wtKL!B zCbk%HX>tRn&+yWn;Hcrl@460Ec)$sj0&4KpIjy1!S@KxGbjM99bH|WiWicBkr`+)# zJj4AXQF~HXv}+1_hmG^uKXK?Hs01T+M<_+oq z#XG-BI*x?7u85n&vVO*9-rQHg^a;31_?8xyiP==NoB33&<$5+$TRW@Lm4Oa4ewu;m zkcXUW?QuUBg8`=W)rwce^ieD83UMDqJtS-E)H|Pu7c2M)Y#}XN2vq)Rc_EM4 z*^oH{fsnr{pRAZUJUmg@fXGFwN|5fnS4uB^?XTHxJF4%L?nqUe(OEZCeqv$K<=H*! z9Mw-=S_CO{+L;%$e;@nKx^iD3w1N~_Wm~(tWp|Ep_wmOA5{S_o8^=Eazb z?Pc3)FN8zPX-M;a&e8f+s zDrXAUcloQ``p~e#5I^dV@&vecaKC@ELB;mH-Ox|`pO^FHKjdE8cK{Oxe9@!#H{xtd z1x^Y5p^UjA@?l)~f367dR~ih4p~T{xj-jqJwXPWOFMkl~6^J%>+UgiBhuc}&u~bN; zWFE=6Wh=Z0ba{bh2+Q1xo(&?b%n=3Y7_6m zXvvuC8F>gS1zk+S>UJfXr@A#>%0tGgUT0#A>r*Lp84#t0hqvSm);6^C--Sy(d>zPJ z8zCIm?(8vE#$`>X4I3867@uyP7iTTe99w#tO;)4IroFLHY)=Z*od#&S0)N^w)-Bk* z55yZ?0YUHZaAh}$TcU2lns}c6*K^y1Ik^-kHg7Yn#cOEE4UArNu=!}`s5Rm=w_W+O zGXoB8u9tZqgEo6jFJ`BCO?(`0#C+-@&u>l;?iKVeik?|#P@dy|iHBc~j88Lt2ij)4 zVsrYo&A#wD4xTV$=U;B`RY06u zcPD0MrR8TQzPNSkT^~nI0FYs;*Sg`j1orc{3Hnk0-MgQJs39 zA-Fj`dKc8FcfKX;NpC#$v+8M0eK(=0ZRqWyo%~E!7&_|b+j7!w3;HZ8JuoXcd++iz zfESO#7rV!DliMotQb`&Li1*I4dVxQF>rdN#Mq3v%19gs}3wf)m8&>!u@jd_FO#VxRL3ttvy&Kx~60oA#J=OzfcLsQPIfS~F<>@%(IK$&ex{3Y8L zi!8{Jx-IywBeli+7NHJZx}AalkE?eK4kX&zMw5v>6HaW~_Qagn$;7tRnb@{%+qP{R z6WcfEz4e~&ySJ*Vx~liC-9PtQ>k%D#IORTW`WR$BpC5Y-wx`3f7LRj#Iuw-dC`N5B z?s|P}U-}e;?S?a76x__M(1o07(4^luyX_CTZ~nTHOLmM&AnJGpqM6}yu!MP0QZr+~ z9_31Q^(6nF<2Mw$PN!0&B1!qT&~`r`l0Je)!W&SqX47to93N&%U_M+>?$2cQ5yK1o z#|pJves5kmi)mV+E?c${)s-t5lESV3GrQvEjEcVU83%h$7Sc>mU2zv&;*VeF%VUbv z%sgJ3@9v=Xei;HVn7%Am;?!!L(qO^B!eqAz1RwccUB18EhH`Jw4l`6%OIvrf{m+g< z^S~zkHLK-XT(NaT%u63`Z`rnKRHfA z+WU^`6R+W^M`4)9o{EbCba#0vZBTY!5MhO?*^5av{wR!V&hOP&h~yV$2iqe1SpG$= zG;_=7w1w;C6;GC!vwezCc9(tVyHgluJ~-`av(#B-irGk_H$E37>tj+r`0LABPTO^k zp*{iIvM^at+dW=mQq3d19D>n+*PXx+9%4W#*W2LVfR@Ps4H;20*@`V?P&B)+E$wp$ zDf;$@QXIMo%83q(tABaAkPZq>yu5&Ve90%Oh8fWO!gqm8@`g}!3$zFL zlH#Bxb2PUTB0l_!?e59Bx@B8DnOVEDZx~U7kWG<#0r!hkA=?Qrjz?<%%hYzHV5+pW zrU&koBaiURkGYt)?}pEzd-i3=1}3D@I*$A0v^!460sU^J84Z}7)0d^r3l|4r+S+gq zr8&V`o@fh(&1Z;If*2(!*zbW;wtq_f*7duihW*u#*LAZJri=fpx=15(acG|KX zf;Km3>hv~#h!SAqkMf`Ze#pLCsxW;n6)*JqE8${0xBx#m{~HW|JSGR)Sv3tc(}f4K zOZEcMe&ghhhyzI2>|Rz~KeDgh;m5tCW(b@0lthei9I<|6v%QnJhw%GnqL}dY;U7}} zdEvYx(S?AX>Un;BHvcyq0N&z>uqZ*ZKDnen?nA=-#MOys?TV5yrTo)D7&c256mNKU z9&tZ_Me#Y$Q@9a$kceXp9{brxNBn6$|2t5ghLCkIyfSe|1)n_iD`e!3Ws=BnEN&W6 zNKcH-vF1FKsOgRW??GrKS~D%pHis4a*8DX~i*v=sSQF0jrM7xa8b8E$x=|ZUf+Y?4 zHNk`B{&OZ}b43@}gSwKo0Ir_GPJ;Omwg$ywN;ao1`8q4$R7z-~z9h(>shqkVOO5dLq>>5Zo)9oq z%C5+Va716NyLLAJ)rMnMgnMqTA%{KfoZEXQ>;cmeRVnZa+8Svs;6bc#iCCYNLq9EB z|2W$hFW3ep{*F*DNBEW{_WDie)x=a+HFpU;Ec2OEHKUiXIl`SYC6J7Y`WmFzR%Gx7nSLMI)uZ^s^$gDi*EIekAF zl#^Cjer$n(T?Q;B9v%5@O5ql^Zzm?b$MgVhAj^{#cWbHH;}gFSMXz9JcqiI-d}r8! zLOY#tD%&dZjkNRsCg3o6S@BPA!Hn>Hk#9PXk{H>*aFhOL$eBjNgV#i&2q6sh-vO$% z2E^O{8MWH!Kv@2lC_|Ks1~Lf>0s;Y^R5S3U%KAD2r3udlViWW~lhptGB>lrSVfJg1 z`jhs*J5ZbW>>%i%f!xU!vdxihU(6xflZ!T34f0Go+ea2x@^hJtbMyd#@_Y*t(>u9tXt#iXxQ*Y4H;(q(>*h zFjy8*U?QZjRF)Q&*lBcXgI)H9q2|sF8nw!1!(OC{@G&xyfGFkzlt!(hCtER1&3mtu zkMWz4BK2>FWUb5Rjdc~UMJ=GOn`#`!{$jnO515*K)a>@O6D8f|IQxFSsqYSr>m=;d zkag1(23YEx;_J`*aPZ#&16_%m5yZ;dIxtmt@;T-1%c`t7oJBY_*93_l82(NdVbbtB z%6u&uAdye|fGi)~)d-!52%_oEzpTpUw4RPtvlFl0u~6x|5nh~I3rWSmD#p>bikij& zD{bRf8;;)8WFuG1e{n?)k6aE2Y20qTY8K6(qHT2!hj%+ygbwYT@phG!be_k$Bs@Lj z?JTZj{uc4;V((9`1VcI!1}gU^s#5%>V@(qknRq2(z$rw$AJW=QMrTjqGFcYSX)t5S zeYH0J8GFiUlZ}-SJ3rpK|Q%@w-pkqq_P_YgxzZ8*cW>Z(%P0e@1R^iL`+jRGCv68z_%KHhrBQPMRyb zkLSlNfgHc=aggrqU5`yBC^K9~Yclzz@ZrcLu*DPhTR*eCGS!4~blibNNHp~L$mk>T zaLvA195m;&qqx}lvPbP5Q&pSC8%ef!Qm@x5-7zP%>p(POq?#cy)W5|FttnZkhBeuA zp3#>fgy&_5u6L#X{u_HiDbprzo~x(gre9|8LFK(ajmvPYP@;*e>%y$ERFVn+M{Z;b zjC!Z{tp7V7L?OKNu2y|PJp6JtbHg0(fY2$!PApqY@|svWTG?Q@gtK@T1gx^LlOiV1 z$|thEL}qb4yXRM$4Vl*AMNdXO-SZmUh7h-PXI_GBn!v-(TS1TK)Oj(2d!xZPs8+N7 z4WCv$h6NHqE0N*32#;SQJXadz4EIj}`h~6(!O}^BLGP{w3<9yiR&|Pu7d+2QY-&C) zq%t$T=g`B9qlxj6X|H3>?2S3tpZVn8jAhqRNe_~U11=&tBuqZYDLI0TW!rvyh!tle zB%m2Z#uYWpH9^3JV@tKTX z*?IcD>I@;^4^`uZKDrU=JODbsI-W@SGR1_DIH9Cn8Hc1NVu76CiL0(1lvv{YAa^wQ zLFyN%B*eSoL$O1!bnBiJHmHAocTs;^VKjFf(D5V{ z62G<5g4CTGa()WE+z8rOAf)!|@URtFdA<~Y+)`ok6I;jRNhD=<9cB0sA0rx)9o`=D zs2&f0cSJ%|;r8rW_DhQQr1VQ-eiCM@*WL2tKRw20eyaL6T>^O6&U&@TbEL6iKe5QY zsJEVoLJqR|?mO^2%5WgCGyB<)Pa)C_RQT_l!-vxo259@=X!~z%Xw5{>-*$mLh1RSo z4Y8@WCD`7{fm2>0@4wdl1u#`RMMh;Zr?$JU`5kwh3S9^aPqzsPo{mWQr8%?{op!9W z{#>hT@_6UGmIKz($!ak@x`9D9!Y!g7B;+>}dXS%b^YrVdiJ>8w?G9p7aB(lbUGt`{ z_*>3%agQigZ6!{Frn=|CZHQ~hCM{w0l0 z@|=;5)yH>gED~@X8f<>vqVaqDZ_V284o#j?w~&3s>rfyZHGbHZj3%zQAenqvyxK#) z@djtjl)NuyPTK-EcK5cGcYLh{^%BPIrO4IhfmJt)Y`}wM;MKQN186D;UT5Tqj;TSP zqo4Ekp2mmQQnKJqi`)tk!ZlsE*5FR=LUFU6=69=a5wACQo3Dn$*jj^Zm*^ z!7|o1-8pF+vh}TVdHdy{TTu= z{v>Fki(yrV82$m5a<1W$&7dS%gQ?zyi-M#n_`qgeX(+a}D;ZRE`~J*gR5BA=A7H6f zqYAKzjS3SEspYjX60@u_CR&Ds*P2rcShw6*Lr7h&u}*4M>vO{j9pwCeJ=mk8 zSxOl6Mrt#Y{G**soKY-s+Fj&2u^QmiGTf|#<}?U}MC>H4QUUY6Fe*Wz3X}Kp{^J;-r8rj zLP2P-U1}?`KvvT2yj7)z&bx3ulB5L^(ZSTmEMTrcwh*iL`Fkz?<9>zKf|H(KVRib`p3tParI5SF{y_qL-j`@P zHs18gt};Zf+K1Z+>f!80a33_Pd$cW}K%CW6+qy3>obgn&?&o62zL@}ZmC56e_e|=M z)s+_%FmO)weN34SZwY+FK?W_$qVh@mNUHNpfztLoL1-xq*|CGFpt31{95|1Qx$vZP z2{MfL@KtKI;AE2;Y?+GLTgSK}^Q^$R?39tUy*#-6QDV_egLByN5YkuvHOBT)-hz@dVs_E{LAXA0#-Y*=o%B)TK=*QdSsB$togxf z2@wWaoyxXlwwAa0eBA3fa+$Lx+Cg8|_=>pk35bzXX@pMK&;(hniHPipN^^8V8T__` zCu--2`+QtAW*Nk(gRipu?R_j$eg$@RL&wL`83e+QUS@otc*Ty zFv#;)(=-u>?YFs}h68^{O7M31ZJYIUjv<4p5XxMntL$!4pS`p<8J0L>$_&Ha>{Px2 zz5YKO75*l&K!`{L)UV!W(87w}zd$(t78?Y_j2{GqE9u%Af;I_(p65F-p#kHmw6MTW zUe}(Ilr-|XaXbu4ZSny~)HIm?O`pMFPB;T1BQlRHURZ66^&mtECTKgLp zVHf>FE$o(5G+i_`t?tVz7FEtqi}qi$UN_re{w&jcc9pEs#oCU#J-@&PrxN zxQR}pY-=o_ZOt~&7G~)rS)UGda%-p&Z+0@{*d&^I$aX2!&x;EK?BQXfk;;)LXwRi{ zR_P4}QLDpQg4R;IJ?tgUQ?D?0LRENF=1F(e_zMQHPQ~l%RcH{KSO*tnC^vva(f4F~ zMdqWxwY0Nbm<;LB8A|a0ECj>RleWohA}nxdt1xRoa=xC_P*BD!LUw3YJVKOYL0;jCYZWyDTS=&(mUw3|^0;?F`n@{W zxywxP*T!$ih(dcyHIl_d=%#v5wK{ehIPAqQGf>KcF_Avl8Kd%51qyiy6c(&0mKTuM zX%Oof1hgNPp6%bb3GhHRBJ;j;u!#H_S&p3rv(jH3Q2ccakob`)qBS!9NGsuyyDCQ4 zEW~nbU*3gfXB(ySl)*9PiYqb{X?V&;!r{)f`f*_KdQKU)xmZ)F@eC=yjb$5Y0F^${ z;q@(!mflXe}IVVSDM@z~Y$UzkN~0p zz=h(R5E!v=)$>#tPGF)Q1rNjbqjK@kEkO%!w zt;djsF{DA*Xw=D)NS!&$qg);sjvBJmMq#p-dk$>ezfCwRm?)$XmU&#mlV_|&#(CV9 zOy!l{X4bH*EF@XZ=6}&*u`)v_j30i*ia}Q7H2463wp5y{a71Vp0b%_r#o)fprKzbZ zlXCHVKBG*&6$6Fr_beEhQye)e3JGpraopZ_ct%>@KFL+XY2z2JeK7^@yrlAERX>1L z-z&QF&$RmPtj;i8Q=^3=+gvwl-2kptNTSosN*dA3eMq8On&?*l;R705U0wm&>t)_; za^Mm`YGp-l9gfyCfIM*48zO!?z59?@*l3$ejK`}}VYGa#m_w-@Tv6;=6*zv7Co-o#^YFUWNmKB!1D-SJpoM+^3FE`E*b=GdBIL0^eePPbx9+D zTJ}okq__yA_E6K9(5k;&XiO>1I1fq97Kv|URJslRg_76D(=zPXh+c6aDZ%*KdpbL) zIIM(Xz(Z7XPXf9%vHvh0mE9K4#H(9Ht_>oK9nb30R7QMKsb2z%car5Jx~-uLJmgt) z=7`cSGjY8i`|XZ~w`#@#O17e`?>q!RKy%oyD#f#c@$;7L@`0O7`Q5ai|I&&HC!5^y zMDS6^59WV?=l(nPmOcd?AH80zTZ>>zm>$!Szhk_{QZnK;hRcq@|X1UH}ZaV9H8Tw@do;RVji z-=66Fd|a)Z70X%?;m{r?pi6@Km2ocEk=Cr$z3;BXCrIF-63(VnSsg)H_lGqJXNdNU zNNR`LTCZVgtZHlB*asO8;{vS40{I(#OtyZkaFVgwiJ}D&? zsP2D^%VTuuaPv1_tK`O6J>G7d49`s3lQ-n4F^Q=L6%o6w)U1>Gsb0m69vd&VJn6wD zo0=Sw_jQ~q-1JUt9M}@R4*3-tbXW!?Q`_*9CZV{aUUNf26G@t+w77LZ$nv4Lb6k^B zgGw%qqY>9mv`IuC6V}>^wb?q$6B<}IYNVz5|sxGsj$2{gJB-)C9Y|qfpVk4?vTbYKpG-4%3N)bQ&0OzthpuUD{ zkqe^_Xz8b2`4qiVTOjG<`|2BY0yWl12r~`k`Ou!4 z5y`}N`hNS%p?_&PV7*`qHM4g?i@b@IX~B(gjvd~euN$jG4P8kYnjRz{l3R4rA)P3y zFO~c)9Ov9o&rAV>4CS}l1N%zvfBo(rxlw<$EJ)I!*#r7^#KF6E3WIsb1;|oUuZUvB zw{s|G!Kr*XZb+^_#=~~CEZ52iaq@;BLxnK z!3{p~#STU&6J}H*xrn+1Kh!@KR+5$M*1x=iaHF3VIDLujv?O=Qe!c#v<`Cj|__T8@ zmOimPj5)ELPd8Y9ibvE;#v*lAjy0{-NAu4I|5e`apjLnovQcf1?HVyk784xmk)RbV zhYIyv_67}9#Tx_!M9^Gcb4oizBC_LW!Bs@Xa^pG9rMzec*)Uhkj&-f#Hv zG?t}$BwU@De!szrMwnR;(t%=X=OR>~a!V6Tdxiw=_?@N52E8#@wVKq|$62-omRk8b z(PU-_=|vtpzz!}M(eO_@6_3`QWg{-TPi{eImtfBJAYDf7XnX?o_ugzht+c;^^e;eY zpeAfBE!N1c7CV;_|89zYSPXix^fIzP%X0aV1(e+>Jz}*BJIU#t?7dF=o{X>>q*LGP z|4{`jVEE-dl*k)|Q z1p5VvN%AO-`IIJRJ(@$qN=5T+vKH2JR7g&p-KZ$hZ6Uj%znV;*Zh08V-Q0pP3naoZ zy#9jBL7n}+%0B8e7gQNfb$CtUcGNF3sLj+?9IjQy3Ilge#E5;`|Ar20Zv*+}JlC$U zDvT`fO)#oNhT*+w8YD;hb7b<3vIwxeuRWn%-JEMk^Bwcev`%%V-)~8XBrMP-WTDKp z#F8dM=-J9)2@RZa#gZMGt8x4mkqyBToA?_hBd`fUsZyn)@y^-Oya1C$ab-nvfkmok zUb4;%k>*Yj-1AV#oUI}sD;0|7#`H%vBbGBzR54Zw`{sBOQ%^x-- zv0d|%cqP6jNm}M(;rdeuTEbsF+qfO3@EPRsU#*Z{L;a|>Nh z?~bwY_#hv52IhsfU&?HaQXv4NliP24`c_i|dxgRYq9XRNbuaaOh&o?RZO20d7H@Vd z^~SVG^+XqIcB^wJ)h87U?Q|E;5AZtwmq__!4?7I)1(_v2mIsmUOw&rT6U1SmpAO*{;0HNE*7>rnQl8O$cV6;Xu!o*6pzrdY(I&GS+qrXE znd1E4z7QC?Tn_=v`o93!oq_F0F73D@eKCmRhrhGWXyD@1jI2E8^!uMyBqchUC&ob& zA=T^cz2OBgzZ;f(?nTpJQNeo~&X1M_`RXbD+>yANEAM10qs$3;t=VLCHuJme?6$C0 zTB75V+>H`xRC)DFN1Bo$zH)_OnL2c?84DpPYIhlTaHYUpya1qpPK38G8s*dkmNWLU z&61T5l>S+(KJ9@9TR9N?0UXuUo}711hF;0d$kB1dG~8V=FSt3ItvJkE`M|GpSikCD zuBu-qIVJUR%n!P-2+_JtR@5$Y(x{*3Zk@)$y}ld|9w_P zN1BLidSZCDO?pue)@|3DPNEn>^I5Ba7IJan4_x3SfK&B$Rgi4cdLfc1iKPY zOC4EHPnc6fBr)!q{e>-b5xqSs=|==rcw?Z=Ufb_UI1@mvH|Bj)ju-eOo~L*qc3K#K zS04EzuxL2>9lBo^Ezl{)Yr}j=1Z!HbrgVNL+OnD_lJcrEPL8a*A6WkD4Z%m)$4;%k z1sfN>O|GP}A783$g5D#wJ#_5McVe7m{d6$6P&9|*jqXbOC@|k*I91Us&Kcc=;$*Hl zF&aksgghu>92Tx!xasn6EJc{~?_{3=03}G|E*uF=-dtIxF!9x8b{7HaGCn>T#f5j_+?3ednauU33kl>lPIV`A>zlVV^>7EyP1~mjjG^8$oUg58I zj+GPZ7hS2D-WiqKJ<6mDs8_z4Fg||Lk~@#9^fColO)~>1f(|6mH}^BvX!wdogLF>M@m1X>Rt%qO6~?pD&#(mahLj$ZG5Vojy=l4 zYkWvYCqA&qkWh#R_MKmNbVYdJ4*X?^{P%g*H~*<@U;Z-7m)3#+gSl_OP$5L53>;1x zItLqVwNsTu#A}= zaq`CbY&Q(@Ra2DJP?rCbzHnmVtfs23#&T3sL)e6(cA+o#2Hnu{L8BM?)D^r<%Dx?& zeo)rCU;W*GN(WWehEom%;vttAc3Dl}8!vT{xX)&oYAwU@;y_UPMWF$QKYVk~XonN@ zTv^oB+KmcT4;(9S#D26@KS}EjQ&K#!$}4%678|OI7{S}#N&KD}8>eaDQ6%-+kbOyU zo@M7U6OwlrSzqjQU_Nhgx34T0dcG>#)Q$5(pqTy6w1zgy=*13{y*suEw^YjOkiSo$ zjk*NDY7x%eZAm+f6-cQR`Fac9UCN2e0KNan$$g_kkfUUqbqeRx-lgZpPj+K+JW&fQ zRT=aizU&F)1<5DqlFZmp)T=n|^@+~Vio8<5wR$YmLQ-td*M(R|5_z&>N}E!jwY6fR zm|3(f(JAbGqu&8kOyt?BVF{sBf^*T0@vfBiY8MNcC5z+dmg4=gSl^Q| znyNQe4|}nDxgw@+*{K~F(XJ#K04cKzr;Ik4%l$(T*#dMG6oGVc`d!#2qzA`6J|rrr zT+489N*Mqh5te-;mIF(Wo0@BTW0i*(hPo`?C8pIxlY?47vU3U%;bb&ZXB?;F{o?(B zTnLWQo&p3DW31f^8r5BBVG{rV-oZY^`RSJRf8mcPwSM z5e2D2E~!MxWX<26rN3%4W5xpRC~jB?>&lnW2YfZA8Qzb%7@XZp&6{@MWsARgyV~Gk z%&Gx*Fp6%_GDrHQBIz{#Nwj-A^{^=&5N;iQVz~p9IcY8NTrKe4+OktOKiw0kk8$TG z@bH3?EB1SMGR3a!c*4l9nKH+h`gAXUfs60!*tCSAv}DNcJ9EXXo&2&ksV)7hQBazS zUy~BPZt-t8W7Oh&IAc=Ad4F;@7`MftWit|3*UbsaLttQpENlctOO^Tq_hPVBdZz*lD@2evPLkF zg2V_%Pzb4yUVt{|Ou)$a!*2?~tbhK`4stLjrhcN(IOTT-s{VPw@tb=6)w$4+m25$2 zqKM=KHCL|wdEs%YU>IR(u>NxYUQi|x(AcEDJVDlt(!0r9(GTQ3)l0xVAW0 zhHT*mlu?R$j?cTLF{`9Xcrxf~CTl$IU0U)V<2BX%z*ZszHP!qZJ;?d5vNdxwNi&B2 z_L*p(pWIHz(#W%Oel%?I2~7oY)8T4Akzd*2%{=tr4y35{?#+?T>GFYwaq*(tHHg&y4c6DP$S-9`fI z&&C7#UF$jPKrm|9c`5&V^+MZIV+B@P>d8tcJHN7ck{bfN$r2xx=wFmgWkj+rvGBw>gDmhXHhNVD`5$d3u_O z;_+u|y&Bak+Ul+~Y+$aGVjA8U8Qy5$34=@OK$iLL1x{w&J}ReB?gAyf`yw}=N)47+ z<*so+_UJf?_it_D*FV5a+$olNkapVwt36qHo2oW3cJ;NhlUq83l#O!I5a_Z;r}<6o zpL?>ePhe|HrDJfdptY z!$%%M7dX888zcOaM;Y30C?nAISW$^pPEtd;C7b(25^@7Ly5usWlxz)$(M4Jpc93bC z+U?x?m+wA(>09$N0I%gsv|T@#m*tD}<3naqJpHJ;Q5y0aMRH-fJ>oWrH?-_CgV7F4 z^iWhz)+SgPfLCPj3{yI|xNxOdADmT~@CIM8b5xv>Rm9BrK|9`(uA6;UGgETe(ny*S zT=~1Q&($SlqilZ$V$^B?Hrk=~%x&$_d@KM7c70aJZ&NPks0GZzY;rr+*X`zOKimIUjXO;F;9^dTjqd zRh~zu;O`q2ZeP`&=^dodpz`OuUIRJ9^|^=gxu@%W;>GF*@)f+ZfWg}=Q>M=pm~Sxg zZ>Uxi{KNaeti0jn?>C#LIlz<%2<+`{lEhWV5@#@W*B4>qiP$8|r>f2{q*OtLT6Y^KM9{%}MY~)#5VQQ{~S)a95 zZ2YwNdk0)39P}gYT6gI>UbY-@Z++!*W=y&5oL70GbuYv+`~jd-b7Ky#kHsx5!>f7@ z4O!6fCBj8gnVq@jen_mH9Cn%&Q)ja6pv~i@1`jb!^py-Zd$GiDr5AligFl^aK4gGGM` zWiMW#Yr0U4#{ejb{56=`#2vqm5*%5GycbemXR|>1=(JzbvLxlU?sTA^&sob<&w(p5 z5SubEd^)D+U?Z}*nHkTE8fJ`4+0!b;UF0&!#zbuwanWFtQN8z@8{e-++%vz0J&p)N z0j)*p982S`UA7Fny|N$KS^?d6HlwC_gO`!dwCIJ%&ma;x#MIDImjrF@5@$9IGvc@Vtx)1*R$v8{ zZ9)JU+Ogu_RP|8H#%j@+giyzfxX193e6f{(mNa^d0+o}HU}|CLtd6`3rCpVOvQ{7Y z;K)45j;nw&^pp}l%Up%I&{x}v?BxRWb7L=%KlLTq&P)|q*r_c)Rg)^vwec8gWTx#T zv;&FFZ>|s3`IZlJwOyR_gGy&eO?9jD59j|Zt-D{Go|-Qg{i|%MMri{-Aa*V^P(f5# zU>|3t@!4qZ`8DOJMWv!Q1ZF8)Y(DO83U!cKHRp9ZCfA4*M4j#UUM zipo2{X-ydE-<=heqj%p+mr>yFSH_sSLt0bi`8OMB}s6LSru8&>`LL_3q%QbTJ6|5Pf z%VOs-K8Bnte({pO`YPxtwOf*00n?+HJr0|gzO*o-#xXGAuHE<^ZhSCIRr>9kNb*-5 zs}IJt6WmsJR_nW&2G&s)o%X8(9FSwdXF?l0N^wI92b73U=B3E;iY@JGqIUujm(zT- z>y@s9=XHkHOHbG+RnUp_*%J?jp~y=wXj*7j727XVUep4h%;SvSC5gh2C`YI z^ZXi5Y+xKF}x{Jp^MRxY5LH|A#@u^l=5`)S`2?OT-nebGN+XDX|$pRCti{|=L2 z8NEh!2>E&`|B;V3C4jd7ZK_AyLSIhm6!{mLkI1~yhR&QjAzZ(}X||=OB{JXWn><+w zdM*B!vRzDhs#C0?V9uG!Kq4o39o4Rr%O)c@yru+w{_&Ch-)%@1-)bXJWTko}a2Z)? z<;6#-Z{_%Jv$f5`C!@$gPIu|z;-%0y3w3TA5>iEymNL%%kW41E+68GUbj)|5Qk^hC zny6&9vO!=|yn&QeVJtCy@kM;==NzTMCfU*@$|SZ?UBdkcQ>kPGJ&M*2o%27_v&~J; z={g6S%Wg~iNje3Sk%KP_DSFJ46OcBjP`$Z=yi~c+#b>V2^6GtF4T(+1e{7n5P6?3t zI>gxqm9Z=uwF)YXyelAw&Biaui3@PLmGnPL>8JS!WNHUv8+t7yb26BV1MM!ehVJ>? z=u6&sLmZnY$!YHbx(qCAHVDcZ>G&8chY@J{TEX~W<~sglS?-?!##vX+CW!eNd_@OW zPFeMKIV0rlpU-cyglC7c?%9=Q%sJ;Q8(WKo;&^Y~U1c;#tS^V|FOcUrW`;?m8>#z; zrX(BZ()6D= zyJOs)G61Ir0{izv4hOq#)net+%B3*`5k`$o&bhU+j^yf9*t?2J)20WSZuUk_(%jZ) z7_k9S%rwe^WE11Nt4eXEA?E3{WO0#mE5>(a{w;`a*nC0_)s;xWaer%j$qoXst#jK42w6itA>ux z6}AR3wNpvCUa{O(_hg9ZbcuSA##plfbK#DKR(Pg~IUfOkOjh2DUM`HCr8zSoWr6Rk z2eL#Qs4dRePR=p?M&y1avcBDX*ire+xumoUTFEDH1a8NVM`9R#R&-@1gm?1+!sj=| zB} znriWAbZv=VRQEKx&-1G*gZu?oh%TdO;4IhsljcMGJ6$?UCJfr4=be5tfl~B977zR! zH%!%x{vlu3-MdNL)yZobZD)4X0KeHX)21U7AYufB_Nq*4ax5y0 zo75JNsA+MmDqzvj@+>Q$(~OnfouY_ZT@b5b(Twfl_a5Zj&~#$Z=@wi92&zi+0o2cB z3Ww@%M!h5~#W)F_k3+IF(}*3-+HV?}wZi zl`dcs6d1x&bT(KSde zVVB&68%z#1e3|WyiWePJu;4cXQ+t6N`QfvB;{wk1ewiXM`xFIAns<7L_wfWtb+JIf zDzHk`>SfTaBE1l>Mr20?$@fRg0!w?rmWMx2RNg!xy^<@t#fukD-JnfWY9~HxsO8B$ zt;YZ4Z6)})g;{e*P4-)}`AzXGueUA|vbRRshkpMP+Tyui#d2II{*Ghi?j^ zx}L2nEUFZjwglm_yxvyF8l8M5Uda}BC(4gC8N&U__b4v9zgx@=D*hZuvI}+tl9Q?! zbrz9bVI^&D9yrr!BHdU z&59TO+V)6%gkKf88)R0z%?_aROn1RdW!qJQm&Lex9bE&L#r$&ZW&j<9MShiR2OEX0 z+lMkp?K=DPL)HyE%Dp}L4)xFcoKNu)7S2_@g60GEY3(l$k;*MsUTnjrIUAZOEFL1> zf9#~0WGy`>?2LO5E5gHJ+~MQ}sxQlBcr19)9sMb;qa1{<(ATXtI7t9D%=2}+T}OM? zclx|R^pz>UQs^xPHc5U$IC@N4d!Moa*A+El_8(L|WukfO(#5D%nAH2|LPiRvsKX#G z@lx_SM->%VXvnenqk=-xCeMF=K`YxudWk@`5L4vc&fq9HCW zsLDM2#})dWmyMFKk}d`izNm@M;|kZn%yW5>zhxUped=VX;+S%_O#OZo=YYrun{L9L z*^}Zr(__e zhQ~%S?uJD=o!Rb`C-3=ld)Kbzd;0^ASZ24DEDqA4ODx1Ci|i)}lJMaDdi;~z|BrAg z04j{o1`Yy(`;|Atk@OoGlA*~K0dfNTe~rvQ5DUY@zB_&-i+9Q2T?|C3+ypEmLq4?jB?0|ca$^*<*I z0O~NFTFMLjCQKt9%x)5rLbI|~e+5Bj1X`!#1;Tz7GSiRLk4Pt{IHG)q zp!r4=T8kPj!a@=3U9?YENcmWZ9OEe6J~8Kj%{DKDEu5@wl~=Xa3aB>o)1dT_h1&t4PPr+ya3@d{BQb5IjFLp4Dn!O$%rO085)Eqx z2x6f%U$YGnASE!I)T;Y09$QH(GovaOJuj!uSICkxqp4e9RT#9H(b0Yfl?9zKm*V%E zc2jiWflVDNik!a-_OW3fmI;t11+;UjTigsylQ1_fwn$s)&O^c9{CGc1#XHXhO*{!u z4K!k2;Wl-S4c`Kl+vBj5+wpZETPqU2AGlKpdR6rUTv@~@Pq>GU)~;2B!LJwV@eE|)Xo?*}aonVyJ@6RN+f(sTj2SqpV%RD^l+BN>$=A`H@c<{gA*c;$l{+E* z%;98WTl+)dbVYKFg$wQv!z7f z)nvOT+qP}nwymi%@B8JP^BeY;z1P0hwRVXjLj8(XtIdnjy^3Y>oMU#)&%1kFLWFrw z`0_O}R@S=;exm+06*xv`zwq3}S_N+R&wst8u3SQ+#k zhJ|w9bUUyxR(@37L7;lMuM;iTjR=-oTDus{DzTjE3eYe)+T^aw^|6t>_%V`ny&}-2 zUU5oNVyUFgr`QhwRyOkp!C=Fv%7L3^(_dSuLSDS{f9o1gw-rdMA&9^CT;`YKh#t;* zBX}|DnTtI`wRugF^H&)EhVYuQ-^vFt9T)N*Gelng{yW#}E(!&mGpk63&w9M)!uify2-a2Z zRm%YBhcUnc1O=xmZ^cQp)l!+YfEnq$z8X^$J5eUdWzVFR4%)RKdB|4ycamZnYR5;u z+~BA?SbarFe#xkS#GuTsbub8b^7>75)$z=>!Eh6k_TaH}zhy=f8Om=$_1fLgFl@6|lgbruDGPA@?)982jI%yGt? z>zzF!haZK!b?C!r6wEbkORL#~I>AA0%5bW6KaRY6pWfohCQE|rzYC%}t<91zoqoM~83=LmHvcnB#WpMw}q()H4 zg!Uvs?=wfO0&pG!>?>5LwybZmhJC+Q?^=aF6JV2bsUlpw zz(00No!Q&c#GXH~?*;D*w>f$7%{8vpQwfNpS#1>ACMFz95&M(^2{NGnps~3}-XC=b z@Z%l@N_rWuNpcb5vMgO7r}V|~Zk*>+vee~4R^0ZIiaZm9`8(*VD({DV(x_!SW-8SI zC8#jYHNs0XA^Ubo!cTJ+MvUpduhEAZV}CSfC$)t*zKx zgNa*!hys0X&I#NmJhdL3AiFlURa#JiQ^KTft)zjpAtZh8;fV#^GRFx79)hw{T-~9E zq#_d`Sv^+P1oOdeSsr!qA+OU24$F10XV&-vdwVFPU)q>>?Z4Null)i(d`T2&{CPZ5 zdoEC;Jge)L&0BhkZs5r1$~;fck-@9Ao}{!KFXcfB5~Y`#YZr*(?K_g|ykzbGia=A) z{nvY6o+|MOuOOX}9vgC_X4T%9GfL1BDQ%k`&reYcI#aM%;%?zNN|R%HS`LYJm^PFV zqMChEhp(1GyX?@75(WM=XKA~mFcg&P=fBbsgA;m2Yn#zAN6OcOQvFjzrO7Ud)&nw! zSBT>58=b^CFnJssI=|G~hX!W@itS7d3K-}0siItpR6nI4?b{}=gkrUtTD-mM-fi?| z+sj>JC$As`X{B8|zmTxyrb$Lz{opYaF;!lzDXy$knF{EHqcp zT3qAt$4&S)rP{kIc0oTe<|00pwDm_7OQzD+Zt+Fva8ztedo77UTCV9hqE~Bq4M*Pw zX^2ntQli(P7@;wDiTfKHL+jUphhos z6drUDL(xCU5bg+$=33M^pem=26)B#}Y-Lcub4?t8S!u>&ve9U#)A-VU60}M4r?F0Y zpcK{KUqjQQ%xPFp?JDuxO=C99tt%8DIUA(IkUy?D&N$eWd2)XNo|A(03A^z8>x4bN zU~cVX<0yJ(4ax+5or8%raFaMZ;1}x$)x7qBo82h(kGui3UbR*VK!7gthS|h|cd}Wb z)mPO8^waEARk=q?0xEFx>!&_iuGnKx$A?3(j~Y&?)LH&$n+@3|;y_1_B9kt86f{=) z^3=ZVa$ytct(YHvF9{#^h=sm}{jq-md0_>U&Oc6pNw5Ugk9O+H*|<@hId$W(%uKSwJh0kj4BQUaPBOtwXT%qIKK@C%v=?bBk|# zes}tgk)N+>I^69H7RD*MFPnYG9@xc@Sog)hNq+Q@6XiF7!$PzBSZeM28dudAt`N<& zCvlE)QY0iez(VROr}0x03=7>`dNk%pRvJVF_AyNLb5aiu?YP!8(o4^6eOe_mBhZS7U?AaQS3c^vwbAuq?>#Jo@S6o#mbaSi@V>X8S;#TeV{BdVP1=am}`x zwcGKs@epaRYFL=4y1pXI`m67n#`q%X(h#(+)gcyWYR&d;_q_I^0zCT_YGo@ z`&|+d;1{+24%KCKt;1KTnBdW(%8IY+eqCd0mT0Is?(`7Q={LhV_xg_l1o!UYmod>| zOSp@68&Zw=a^a7&kxaYr|Y8X-9c|Kfzs3^KN| z%@PbdY(trF*;Ay#1kW1$RbJCOut9(m3U$5)bgrm;jxSRQ%fU~Z9HA(6zIF*!P8=}S zoR-AP@NcLVXF_4@ECmt_s0M6lJ-2>o!=6&H9y0eLS_aySp+1#xhMz+$TMC@PPY~n_Z{6fXBI%Z zPAN-)h7z+`(drM(c~64!7r-g_U)1w=zI!S71=#aT z1SgDjD1X9i=zj{0p2_^iRGT-$#67R1gzt+!^3jC#tytqD&p3XD5 z_c)1%_0rybmMJjk(Fw>*XZv)G7}Xa&n=9BFZ{1&ejJrDgeiOJ~4AJ`Mn6Q8%nm18O z7ft`MKY>eApB z{4=tJ#jfqiPyh6RYNSmP5BP`~!Po-a^uM=C`hCdBgWjJ*eBNo%W}40fk+%{jgq@(vgSO*uThP&Es_%%7nfX|GpVKi!T9HzZF2$T937!c^;E z=MWIB%4YLErH6q4O`f&tl=n)3)8XE}87J5}B=wbI%K{|Ml!l4DkBNvfC4?Nf}~g;Hq*E44WY%8 z6H&Xo8ZS*>_YBb%Ga-!&`7=q0jj9P9%ApW74A-9r)UTT~{vHJcQhbN1s+7A9m1r>@ z!k#*zF_|KLZh9RHf^cIcZ9C{l zH*@OX1Z+D7&)+L-3eU zsbA=Y4`J$+LCI$LP4$LE+Cb_?COPIozB{)nq>y|=1ItgRAIL<%=SuT!p`VBMCm(1W z57k}0v$!6-GlCO_Uc;+{7o!6;rR*GC=G&6|-~Zj^ov>uJ1|!?0TDo;BcPH8*8KxyM+A&Pz9q4!Km$ZpNwhX#C*HrJ`2YCJ&UoS{AcgtldLF})bd zaURekTaj+xKzvb71!j}^yLIbuo%79u6D%fEQJOQnR+uqVp^@Km*-`a|NOnhiRz+UW zhH@k}Q-28gDu(te6!Z(@WG$)LybQcthw~f&m>o$H^W13X3Z%)(=cKQYegN#l2b>9` zS$2~2n&Q9X{tDmVz%;;WdsmK;>6vB1O1rhevDFUiL*IYzuIPa{|M?ZI1F8CB3SqNB zigIL|KV`Q=`u5j8&W@hWpa@@VXCkoKU)uDGbda^%phhm4J1&0Jfx|jOK^}@m*mxWe zr%xNhtSRBa87W0GgJ%H4`u>w4N5ylIt#LUy{ODtEPwQ)DU5_)8hgac_=QWqlrQXoC zCyR$ykMmcLOcunc@aVuJ@}X@l#7y>WE%;2=x$E|OE{{%1cUwnzkH&x_l51T)#Hq13 zKJMSoF)%Gs3+(q_%xv8WlMi#?kK^`$4#uC5qgU(#McphGsMtU4zX-)EBpv~R(s-P_ z8VNVhK9j)^fMObSeFc>T{mGAeBWgl<*SP3yjm45^ymAxvh#NVDNtwiOopPt1Umj(} zfgrbmCt}P$zFZe@X%n%tZ7NvqfA+Yj|goYP*Lk`_*s7yGQo(qGyDyx8Tr z$RW8#b@MszXO54QU8ir?m0x&lf&?Hl4{{(X4tSsC+d)#aU*6wKzkj381(P~cFXS^l znl%2(=Q%?B&a;&d_f8VqDU3Lv_%;;XH}6i=H*c|b&Cm3}&GgVRm+~6V9Y00ITSu1V z9^l3pHwCE`KUEYrfBV>N*Y6(y{DLxjw>lxG&@Wsjs7UKXo-^|SG&V@zy9?VBR}`nV zYI8HMOmj2QVYuVITAnX{{bep2qgZO!!m-kLt+dd1y=ON_6?8Th=>$#>C%8j<+dPKI zj`o|RbDQ=N-80M|WKXel;K?Ul1U{qr-U;D%5Zo(cfH|#_Pj|28N#Cdgl%pz0)f!MM z{SE8Eyg9le?BZBh{;dL2!Vwg+USi@4ZhJ_}uqFMNuy`_?LX9viR9yjo;E0s&!tw*agKy`Js0 zm~M}f{xFskc!(!;*t&Hm5CQJfa_c{)7KmT(o1K|_H4>HKnt0lwJaLdXouup zo`(_jU)RMrPRkY%!vzGYwkYB>+A*VV9)fZ~jv)ktl;3|F$W9CM`DL=lRgpwiiQhTP ziIZi;gzXy?N_W#NOJ3SCQ12P!6udl897$u0K8#2Tj8>h;cz^^4yAqS22DXt2DdGCE zV#i;q77;^#pWBoa10Tt{ZjUUS0hio&QRa# z<#~=BmYZa}b6~q#^}^PgWD;E}NvC%DOWm}OhTVtt4qx(l<=H}_oWAS3#jVqT5<9Ke z;nF*DXBLuNNB?ar24YZ~<0 zDs-Cc?fmV^ZGJooK1tW^Nt|Xf+XaG4bZbhPLx&>0C~(QOG5h8G5kj9jO(b5EQIN0j9a*zpY0Br+7Sw0akoeCi|>( za&WmpQ8@^QO8t)jo3tE1#U;Y4j_cU*SJDafAmeN0&4?&HqEl3e7NzEIo|Za7?xP@g zs0j5UpRRx?9aiqn4uyiG(-0cs@*KlP@S;m6h=i1d8h(w~3b&alM)D$9C~YZmYXW$- zyydVR@UlB9wT$yyYl5T?(B1dEglhupCPTd#e^@F7XphvJSK1u@z}IU`V{2LJ<`7=X zZAA!Lh@}juv#p-^%6n)%^iQLUBzzV?%}$BTdwA$9At7;LKX7S|23jVj!fTwht23_7 zEp)r=X+IvMhjKf7XW0XV>df>}$pJ%a;=LnDrYw9p@5*gOax2Ekp)_nU$j84zkze*8MxcfKllwF-%+SfPq7l+;j9F%l*G*R|NTp zoEf2*O@B$OT+94nVvazOxY7%)xDjDan}dsX)vnpO|5KZNQ6_xO=_q6-Jp*)mxVSjz z@QwUZd&Fl&5z*Zqc~h*mH;SLs;7KIEVpEDhbzM#fWg8?2UaP}sw}(ZjW7lQoHO+-7 z#6oLc>(-+ik;!Af`MTbx>jF_mqtV9Jq8=9ONhDyo?T6V&j*{Z>1-aB%)NnZ}W>mL( ztz$|A{0R0>2pyY=gG(fv21|9og7hsN5iNyicy-iM5j;YlXEG#E*6?D=P1g$@aa0&}x^g zxx_6^xU_3lZ~3G~Q`qBF)rm$PG+806bu%hjwWLm}#OLTc!O38{J_iOcEJzX?v7e`Q zRL*^G99K85GzD;xFHPB{d8I`60BuF~Hr0kwgxWZJ*;xyIJ>D|4s!}%+V%+GU(hjcB zsbb|fEtbrp-LeS2&@GmmH4pzpx(rE8YZvViT~J^K#^V0q)hvd-*mE4oJO8B9U5$Q> ze{P0yLaJ86iclNd0bqq@p<3czv^OckIJx_T=;PnvPB@nOoXgW5tC&5Lf@hF>OUNh? z_7Chr`B(3`=^Fc&Mg{7 zExRjix{9k<9jJLJ??TseOSYe(!Xh793 ze5JXI(uU69tCZ);*+$77v$kw&_bt{N-}k&zjnaw&P}ut6fZ(11!uxj%{;QfVFPGv0LAoC-s@AePV|h-&{#w`Ce&r>e?CnlTx$cSHeXo8KU_v{^Z>F zXi3GRzjG?9^SuL|Lyu_$OkemWZc}TBIdz?(P?;oVB=`5$(Z)uZAfG1m*)u}Zdp4QbSROPt`rQORn){rBD8V`Y_6->hGV-t`wB~J z2Cnh+g=}TG*+seTTKX-*+K5&;4;BUsI?B|vKq^OpT;r^c(OU^WUO8l`(7F10V`{CQ zeLkaR+hM=AJyL8s=7$&4-Xhapw!S1k?@wCP%1@puJCA*gi2 ziFV@9YHd}8zgt<60FOweTVGt_fw1Yil!myeJ!|a3g*tjL(^XEBo$D`ZBMcU{R#QEst6QGtTZwU)+_Etws# zVZx|h7E8Ki7k0&acvpiO!xh;D(a=f4IeZY z2trm6#8^I!qXdc44wQ@7A!Yws!_+PS`W@xob|8R?p46}DXEm98qSc33fAlhYjO6)~ zCZhIae4tVUGt)Ada+M?Q!@C${$(#(Zwar6U-r+|r3lof!j)(mTc0`kknkWS_BQ)1? zPjhS!lS5slu`9Qa%fDXVR(%ONg%5mOXwDa1(j3YeJY!^9L9agPz7s_bedw#B^+fiI zP0i_3xgAxPX7wlUD=sUeYDLxj`Ad-Z2=>bA#P_2Jt%cJzeQpXl21a*Pf`M!E9D%;t zP_gG6fyI$rJVBx$a2ffX>{Y_?Z3n2Q%pR1UWCs_xXqw|ZoOf%?`x$D0kHfcLeXV8b zMv*wJGgN*I9?4FCutag2L-Pxl+pg!jarzF>zxdws4VOR%=7HQwrHp5!tT3Yn;jsJ112jL@U=IkkRP|$G(UIH)tIo%Rq`zsbi8aeQ>+v^)i z{t?$M@cEc=I4bZQ{+Ar*U80yxF|9{Iq`+q3)!Z_5YfY15wgT}Py~xSHwU|VaP%{bT$ua+3Od(et z7Uhns6r;*XOPmn!iN+cmNG_;MNf_*|btCO|?;OT}}Ed|g)LhfpwcL?kToK)+*Gk6m~HT zg#6gAtvycQk=-lmIBp^bjmpifDvR%luTFXSHj597J9ngXO#c1dRc*mWW%wWdOfcL(c{in(Q5>#8!)=|FccqxH&0rDUqprx?u*!)LSR4lNNln+p$=iLE z4_v3GL!^}w*TrY>&wtL|JjNd=2;D(LL+k`Y^(1^b?0e0N{zVP?e0f1Pq$N2 z-|z(rd~qlnuD0h^bv!bgdeB;jX z?GCbid5!@htA#kSa+sf%v}6OI(541BDQAs{J8jqsM+EZP*#|W4VG08p8{Mrv-*(h7 zvg6lAe3E1q9A{r;B(%eL$0f9SJ@Qer-iKyjeLyehOimx=iULcJ{tofw*LtAz>JdhZ z#-#`b{k;~_*^>$P#eKO?4Ek5kZmVgna%d%TiRF|b#{ph+^XoM1_FrY$c<+hJh-`@S_}TuKrSRv*;+K#^B76|KhFIyrI35$ho8OP9jLR}YboRl(M>*leYCnfH zlRmjc&^rQ~@47#iAap0n^birt!ZGqN_G5a&WSocRZWp%~VT1TXCJOueN5z<_dmP>Q zC0$#czlVQPqeo7ozGB(ps+pCv$Vk^louGfvofK zcF(KN&#tS_r;n?N@K4`&sV}a(Y>?z&4Jh;pe_V!k0Y`lvkrGL|6}$e1m=cR|ar!)q zA7^*&1hEW|g#IHsbt5S_r_wgCz9F%jhmYSBy&hbOh_Cw1{h4FyD^tC67G=#Q5^K29 z`K{=2FktF!7rYu27b6Bl&X$eIa3MxX#A|66=@mDW$%E)rj6>CzuG09kJB*^x_}F zsafvvYO03yzZ8&?glW0twlmzZOOda3ruo!}3SlGa-^Oc;E{4!mN54#F&(NeR=*qZ- ztWL1mab&D3ZxDm?$pjIRLWno@$GxuF=Bg-E0ZTmOXF}m9GrI)ZJQP_V@#qz_F__N3ZdPBak14OA*PnfyZ`n58C3i7T?joU`m@8Vvgl1A!;I%61f#k&`0hAEN;l zg4&KbGBCperD0 z#UX)Cks$x&If!yEFUmsdQ0HOIkA;^y4rOB61rOi-F%6v{@>DsIHVSaTkP8}{z zf!*_^hdZ(xk}5`4r{)+$v8+l~)}YOTq6gF8LGPHI$RkfpCCz%Bj7{b=Dij^(8jeDZ zud;oObZwg@yL+`WW0ez1f|rAo=<6bS$QxS87A#(?s*Y9b7^)Lq)~lHdz%EY7!<(uP zT$cH!SN*uQ%wh)KlU|l?H^;8>sJfwkjT-m5v<2+V15W@JS6fPGuWJwS!KKr;l$*wZ zZZ*z-n2cn=?&YwHSYv}VwauRL^|j%S4qZ#(If(|=B{M32>q(zUf|_te{6#RG2dcNc zSl52RAWPYbkCj1F>;x4TK*MC#ecNg~8$o_w2twgE8;r$`w1anJt3)_;O6rk~ zj*XNE1bI~tv8$XgCZBu+pfALWK%^T8iK!h9q3AM@zUPK9j=)GT{{Thc?+NgcGIeMo zKoa0Ua>AvLwCofUKJCyZs1bi_0B4-hIv5`lUv zrlRJO;X~t{uZCp^l$Ig!u+~=m!`&;?ghd&(B?+;I*`uR?G||Vf*Mzh{QD>$7WiK~; zpUgql`tV2jF5`2Vn7BQAAZ-z;Gj-Wtfp+%(!Pmj*+XsR7crznC6$$6L!R^6?u_fqa z=ndR&wgwxGIc8$j1r>I3)dik%XeK+s8)m*-5jd??%8P+hO|t&ZgL-`VSGn#=AxYB? z-SXQ34(r)~=pLVjh0hPBV4qjhHrFuJ0(3Q$y)!MbSwE$KFMwz&&A)j5&U~MNjNGq) z2pP7EMjS@XkIU+&d~;Bn|1W`B6qJ=w6&s&6hDpDs_Vk`MO`Zh3p6+&on$l~bg6y}H zE!#t^O4*hYySWQ;dP-EbNJAPs(R&0jl=E44hzcA)=Nhuk$BWr(5hAoKKRIZ5%ieSj zW#eNMPv`l9NoFugx+N1kcIlO|E>|UkwhOhKw#%fPR$*^3J|@fLouIFX9)CbAl-2r` zxv}t`X>cS9mYrL+lslL@9ZUth=!w(=+d^PmpPVbbONH*Os4r0Jo+)*)koebklahXy zv1l^@VZzoD~G88xsu}=*syBbn==G5oyjjAylNBPcQ8H)i+Gu z?^oNKBN04g1~!-WI-~Rb;)|rWv2i};gM(}BJpay6LglRAf7%gks1w8)axqkGqLN)j zlsr;I>(oqhE&MSYf_&tlXP9;85btxRbhM+c@7$cHAB#*>rWm!&VD%ag1}(o%-k}4y zCy$&YYtL1YY8ZZ{X3U2=%}K&uN0E z*W$z+3#CF^1|L7CZ02hCfz{(QA3u=&9gz`3rfGxG8 zHco`Xmr{t9ZpfE3#M`qwj75MLe$UZ3BYXe4qT8`(R5F{}XX)UjeC+N!G-T(Ly9n)g z!@d{4g(LXOIq4Ff)DcB__k?EO)F9WD_ex_r$yWFp9L+d8!`24FR>YWlVxK$Y+xs^` zk}Yt7A_-BEbvsT^^3@It1?LiPC$CC>&2nSB)3+hyKn3f_?4lr^sV+3_oNHk)de_ zmp^n%q+q|?UzA3{(cVe^XamKI?be>X<36-o(sH4GZ{6YKnnCH|uY`|w!>;RKw!<=^ z++l!oKag>;Z2wY4dIroXIR(g%%9mnnbnD0_eShK`5Q)-v<`{4UqaP9-DfHX10;QsD z^366hpjuUkp7K*Q4e*V5MdwYzDab!Z{c*CM-z0qDi(AXfQ;eQ(a{4w$i>|R{7Svlw zg%?w|`{%j&-ehBA+)z9c%U|cRmnzgZ13T-;KD;gS2x=S02>_xF_B@@u{lR=gp-YlW z+BAkg)O35N7}2Wgg1f{$yLxf`@I!9I{fC{A0Kst4%uiqA+!WD|xeUaHghqx&mYtji zDPxuceRIbpQ?V-7mhd<*&{ChqvM$vFYyP|fw*tY(7_ z#Y6e>#eq24$Q|sz15?P!Z)|+XO@TI0yD$LM{8xF@4Uu*j>WB@Ck-xn%KdczyWf7cV zD+zWp@nWDoqxF+D2$8jJt5=mHWr$w%YCOM7;6~qkB?C@k6#fRNT$*2tk z3oQC-v|c$LR=iL4cw83m+QL2^PqmPTFztM&!0#2kWJiuU8&DJ9uO|JzY6*L=GRQba)fruu z^PVB?J+{VSac%F( z`<^cjWwz!muQ9u>8m-@fT^{UzMD=-WL-JY5D(bQIh*$?4IsW__jb0rMmk1PE#!PYF zGo~OStR@}+^0WlqDUGjSo%s|cf)N(Qt=p*iu?GzvhW~V)9$3@xMw0*qvnhkAEMwn* z%PYBND7$75mAdoN!|)7Dy3~rlE4XYu8X)D|9kCi2>QX=dTQ(~61s|!E0vGv;+MY2d zG6TlgeDZE@EU%eTY$T#bn)VnwT;KX(@>1nl@oUH$l1F9*fMS5=171~pW)PV`c+ID& zb~+m+(XO@$_EGxlN*wUv@luiSyY*1C*aA?1vz^*P$!r=2d^5F9uuWgE324U;fs7$j zHuDea30w6iF&(|3yXC!()Tr6SOw@+jjQm6i1;%X)==~!P8*rN0a$0@OND{XVKBivb!<44Un(CzDDewmhnh z(66(JKz9g3Tgkfu&n;q{M^ji$dT#37?qNas*l=t4_hm+uzE(%7MGWO!{4s%qr?2e> z2kz`yK52)7>}g$jX-j3X*H9yGD13je1^i^csa5*Q#b{O&~!w&zUPUab#0z)~-ysn`zN1Db(Dmj)>9A<8%4qA^-XzfNT4B5z&}UK}NQWwZh))r)$e5!A zntL~Hv+qVC!}C$MgpmsJ@Y$FlNqm)lkIubuL-?AnUB^;*hZ}qug*Fias=*dX>wN~k335ia@V4_oN?d3j6Hi1 z2`e-dokuSVlsfiOXL3R;ZEweyA*zNgnpyIms3$xX*JkCTZI;PW>1_uq2C=a^6k^sb zmLN))2@lQpInmqr*MDQ0K~9lFVGIqA8s7agm7PB&wC}jiWH@M+mV5VTVr1Y{zRip# z*A7(B<%$HAyY2onxQSdkuv8-pj)&1)h1WJhl(~Dpq8`qR=gG)rF>rE2UiDy+(=de$ zyfxoz+?*7Gztc^;aLbxIEbYOkgcNB`b^R83hU*M1y0M$x>}P!j&t%VGA5#55^EEsB z?UGh|(DODccI7>MfOwJwF_jX+D{JXF;s})4G6@&@*J2hB8P;YPr=K{FrapTXxezoF zeE8+UhM7<>$k!<08^06`UWny>*wQr?uuRwyWuAh=t{i{w&$iG0p&aF^9W5zTEzW%SwZ*0hfa9~ z-70GS(gr5^^`-Sc2&bodN)4qC*VQuc3)WFt(-0^##bU<5Q31ZdAAzh?F6K~) zh?CihYFY+R7q?OcRkNS_CG4M72u^9;SF^2E|B4LT7dvv8K}{Zl&xG`UeIIQEvo`?A z)3s;j%^vUfN7{B;lN-{#2G@TB5-llm$|qhEq!{)b3%g6Ve{Qr%{fMJ){dUyJpN0;Y zXQf}fyZGd_V<;~;a4>g0b>tQkaRiUTTHGqX>cvx@-us4S`LG0D2C^TAN8P8hx;R%E zFJG`K@;t*o#QfEE-=F)0{(tw5rb-{EumA1PPHWo=+HkYxX#Z=vhOsAi|AJxywBdbJ z4-y3}%;QpJ?FnUJm~I_BDC4mTzcCAaM+WmJ;{*GhZHE(=Zu&hHmNgzmdO^Fqp&@q{ ztswwzCP};AJUh3z!I-GAp~B9!tzzx6)voEnt!<;R%qIO~%Kb`~q7NdVZA{@X^Ue1^ zA|ycuzq_APnZ&tHA2lF4*}65r*R110?<13t&n@Ah;SF4`htOa}$lGEi>2WgAU(&J) zWGj0&D0B}^;$_*rq_VgVe=#fKCT-z#agX>s>gD5KSqpXk>UMuD+hWAO|MXxxBVq(F zRk!3sg)T9fEkJ?q{9*jIvU_&2G)DuyQRjF4q8B8~ZJ4vUl}8H~YxoDSQrg4jbD_wF zT?!paVlr!T4+-e#aj4e#`S=ikp|J-ONFV#H_{$jXEN`l9XQpHd_0^~<)#JwBPC~$x zkqi1sUN>B3@vWE3@ClkHPzVCjU#RE%G||mdLd9cHTe^>LcFfG~72Gq)Ut$Nxffj6; zsg8&BM4D--D}-P#Ula-eQCqtJIv}>gmuGocAzO<8?&Xo=wGU;RQK6yJh0Zr^FnUIf zlV`21C$JOtN*2Qe6yYrr-r1&3kfMSoSoHjO#!q0#=364T9j(gzH+LIc>z{UpEky_p zk|RQ5?5J|yx7s8~XB(}d2WdmN623XCsTEuW#I zF)GDTkvbY(n~wR>*-MW^W0H$t+$x(Vl|VWj!j>jJ2dSN})L7%zB93bYTcu&CDpjpz zPS)H~rGUKf+ROB}KcLrnWUR*w_dq!cZQ3bPU6DPbV+srIY=I{G7G9Z|{K%R;&04oZ zIgc-mYMLhUmU;+~N0ddIghi#SC~U}05ig(;%h$>qVH*%WLrIhQ{St}WFilZd??6P7 zWaU@-rUKCtXvm;X;cmN79ND-N)#>+5GegpFBMRi@Z939I8+63LeN4k8L^h9naKQEo z?&SVFqdb4miT12P}Jc-%*A$%wL|7{G*J zOE*6A#`Kasl%Si7!S(G55zRWw^e=V7YipFRSYfpvg7=q!yi(x?$3P(6YeEAFiH=bx zO&{YX%wi^JT1egx^Qc2E++*4 z_k5+kHB{fWznqUUMv;SfWQA;Bfa%pQBn$#xLDq}{)90Rw+of7K;Lu8%>(P*A2Z4L)aQy!>?XfK{f^ zX?r!7IPYuMH#40b%(I8SzP{Y2cwA6!)py{01|q1GG`Pct+*U#6hewo^w(!NW(N#X{ zeJ)7<*IHyY+Y8ACZilEr5Q_KTubNfaIl`#ROM|J#>8>4l5?2q9+Hc@HkM@)uvKC^3 zA&{@F;#)B;alye2wBKpBW{=seSXf%`hg&Bns|g*a-M2OMSA6vHu3%e(C#<<=!UKTa zY$C%hQS@t(NI$ZdzLK!u(eFrJXjiUY(F~potXsmL-SrNgMWn?c{4jLUKOS@uRj1zL zUJ5ehsH`&`+^dPK*t3z3i4~egb6E-QqnnF{g#|@=tzZo0|0ul|)lg%CvUV9$3V$N~ zetUZjAcn1=Z+IZg3w)3qhQ9&)`HukaBsJLgs&BrXtVwn>{Kl&Wp;iDWVRvY}ky(e9 zGFH->y9Oj!(3l%i~?L_9zV=i-`b>{d&Q%x1d@#?)k(iE{$)eF z#N^yaU^i381cOtwx6w{$c~vqCVp1}Xnc`^qQM_`xw%o^6hKr;~?BzLldbt2DDZq%i zoLiH~Y_TfhWe~04!c>yYAtpPLA1n?Df<0cOlr`L4dN@=id&e2^*=#;#C2?W5ur7DR z9NxPQvMFzYxYv8tCw15#e9g>eim7d0#Xbt?jC{-bskG^tscF?ol}%j0J*yfB^EdMF zE7ES597Cx$GxT1;5uN_Ls;xAQIWxsx0rlUDcXX2?;#YZwIghUf<|)9~x(u!WL%=C2 zCGZDp#gllh9Nxr6cN}9F!eos<)yPWPgl`fDS#a27qtl_|p7W2UV;)l8%>4UKtfAXA zi)rw~0zG{kPLfUg58PIlJZI(Rve>Z7l{JiW4N~D|;$J+!^ZQ(J^`tWqDvuW2^n*@s zggaF%*M$iCB}JUW6}5pf$2_kpofWE&pf!gwl}soUVeP6LC}F35OpJ874}PzZ9()H5 zt9C7i*)-h7%5s(bG%(-VI<-9xS22}4uZB(Ia)W}KkOi;sa*O9aYeiAB!wbp$g62sg zl>gW3!*lY2|BiIXC}67+qUhF zZQJ(5HYRo^HYdi!*v$X$zx(f=J^N0d{_3jkd#}1r_p7>fFMhOTygDaVBM|?qOm?<^ z7Je6Q91cL%rCuhj3ZKV+6azp?)jrn$d*|m||Le7d>$RUP%I&l;W&DS&D;8D0 zjr0J0l{_(CqI!OVzCD^&YamOKPNMDQ;t9gDUceAxJOCnBZ%E09L|GC4^CGa z+H>d+@(Dl$z6Xx{fN5DK_N~>cS_7^=RmOX2{#uNYrAX4>Rlo3Idl2{RoY= zd&!l!HD_(igvPZF`31Db0cubAO5{$#Y@=-TAMnXzT44*6>shqK0ysB%$X5uhewQQ) z=lzsxQ~BQ4#k%E48&I&4_8C=KrX@_2A7?dCL2 z)#$51Du>fkGv9%AlTPOCY{q3yf!+hy>&7f&uWleh{A7G=qcv;<9=MOTrUsEAhEv!&M+!?hsR z6^+`-M|a_+FMSPQ41X-Mfc6DXwhJQ9M}DEQRrm7N(hLWp2jfB3z>qCB&_Jz5+rS7R z1jRt0;Fi*sqsHqB8i|Jm4kgM#M9?#%lPxdwEtoDsP{C%SE3lhn3izE960I{pQ2&;* zDa&#D^RuY4Pk*1^8cztaS=rwL>m~hbu3i(vPNeQD!dVK}MpYTD zqzCZJIg`1KYY`ckXJNss3+UmmrJU3OCCY} zl&EBEqK9N3q|16}c~$C|b#_W>P3rHsUQ+bL{)1J~dV!#t7wt$J1KejT)!0$SOn{-y zXlnIHVxnAR1+~hLb4nN3KAq>PWO!f?>W5ESFqj%pZei0_kNirqgIy|2E6LL)@FH_J z=nTb!F8oeIATR1Jd~)a869Yb5?Ga^xJn_x?USm@+$8XdUQbZ*`$4e9li{bVO5VAi{ zj3@OPE zS$UnD-jaQ(oNvC!@uJi%N!e*~SuAvN1}j`+U?^(h*+kA;47a1Ac=7RK6Gki3F9>7& z;>dZ9>&cmA*`=tqq6<<|w+kDm%IDA6%SrdZ`v{a`O~G{UbUR0QRSw;fl#Hh;P1*)X zOJzwvwd6v(EH56Jr9#eDGmC|lsQE?(?UGVArWkzG?TztC_apP1!eiR(*G3M0>F+#D zZ^8XCJwUC?nj7$ znb?m5gs`ZZd!hC*Z3Ujjr5oufFC*g9&P9${p;3(+nP-%e>>nz2iw7gE%A4V&MRw77 zukBPyvG2@&!C}6&?$`S<^FL%s9PRN0IEg(LT#CDe4!ThO2uE(kI3WwQLlxzV#6P^A zLKM@d?@kXZ^gA>-GJuer`$=`uDn|V6o?CzoqM#;K2wZ|4 z2APPO-wooOpc6a(K10at#yeJCpOsmbdUJap&*sNR_YX!IIbkpqDQdFjJ{Z0Q%Z{Wl+5J^hKDP#bDs)H zrdTCBq!6iS)yv7p7xzwd5&`=kKu&nlw~&wZYBW=iaV;E_gHB47b@JS#S`0#^)I~V( zg+#cq)by~t;EQ6#a_1Db@#mItU=uSsoAnaWlv|nX4|^$@`Mt6$+}N$Q?eT>>Tk%j} zHDv|c0o7J)fh-<))TqJSD5{fr&R7&WM4j@XTvJju#!L>C#$dU@ zsaXQc*+&V;{C*cDj9wny(~U}CkW}R$>sXk4F;6u10#Dbs6(t2$)x*MbpijaP`GN;U z!L`+mRDoklVk&2tv#eD!MRPW|Rhh>?S|U`gBvCpl<=2ZNf-)~{C;0~j^4kjpl-!}E zqp9ZJc%}5wS#$DuX0FnLbW-!Z*Z3gw(>z)jZ6acC!fkz+d1hE^rdPVny@H#4vN<#d z>$tOiai{p(QpN0+h?yslR**7jpDSY$;uqzO8#QVxS_7tp)%Y%n>pD%+(nj%mV%;-! z?X%{$6P<6OYm+OZxq8y>j7=5sugt zl{$1m`V{)pQBxd)R8M?|E6>7h=zsgVni=~87HiC@r`$HYzAC2!3qH4Fb_G%*y2-^b z^@Q{psDCi=QY#y=#ifb0|M5sv{fy7gnru_8+9y*>Cu<tj+HV546g-o{PS4+ z4Qy_zC-VS8pj5SX1}0I2h{- z`fM#PKTdjan*YPkWQ)y+MFh}`m%Ae9W`E)ced%)ifTWT zC)sxBvjqg@2xvKenMRpeH>^{_2*Yq|Kn09KRUpO)1EJ!&_2OO$qf}hQaEjF&bUB2A z&*d>T)lTqoe}7|nqPcR+i~C}GQ^@h0xCftW0pMg`Xf0|0h${5~`n}X!u?*3fnz!GS zWeD~J2+A!zOXS-ms!?>j-gVZ<9NzH#_BvbtS!PkLYs?lY_19-$NSCtewF z@a#FRD`5LL1_RKlq0=?y1a03kc|@PeWY??%?ANAW+Ay;`qNVSCGn^pUvTM)vP0jKM zJN$0f$^-KR?yfoQ)OtvH-r6vnzsi-}syO1cxhYAL zj{M_|t$%eYZiR}h16xX(*?jUibkxMPdF~6dap;}BMd(~jw>azqJu2mRJXA;)_A4?L zP;@PA0+rAE(2P!X0A}iAPGUJlQ_O=kW! zp5V7_S#+NGS!!K7sT5g=NP96)cSDR=S+aDeZ9v2$H6qxUH_?QTx)7pA>x;{gNM!1d zvx~Cl+?FIC{UY|btGHXRvZn%5u^UMszj8vUQNer;!i+uSM3{atzv^1I9rup-kmm07 z8`cLwlAE_yGGUzyT06ethfh^x{96<0U>%yIYCSc!`Ix!x2NusGBw87FdHj`#BbUV4 z^LMM^&bTX8C$$R6y3OPTLG@eACys;3qQ6!YtqQ~`m3yWoP8O$8=?@nxjf2O)9`HYu zlaa>RKI3$bNv29!vThlRPq`i$RUp?az||)6ukE&_-cg}XeabhnGgKCc`9;1A1*xT@ z-eWF*>mY^hGoI4sM6^`$WX=p{xp*LJe2nW8KD8I$blfoUv?^cD5+@FRu=HHhHGmN$ zk{M)EA!6Uz6XYevvD7EndMD=rV|odc3D=l*;)A}5&p!^ck>8~MpkdZqpvoD|Cb_G0 zFH0G<*tW^vX;t+?WQOfg4URqV)D};kX~MvMen`IQ@Ay9cBjc%v{f~Gf>D8+e$#)4M z9R>^|SrjALLYcttzT=u&wk@<%K`eC#w7=wov8yB#xW=~WV&{K|<9{1(1*Rjwe{u*8 z32IRj`=nMnF{9iM0mSZIXf)+%_QX%2*GcJ>{YwXWzJk)?=56>w`>={#-#B!K)NNnpL+4w<9zK|u}P}}X-IY7-=C-~d%F8FZs1LA-L zSTi?KK9ILvEQu7)e>Jp6fVyw^AK!WGFjLAGjdt0b?#|wL1pvm@C^9yrzZvk-O7xpJ z$s}$FLDuW#;oY5 z$DKcBkj-Ictd^$hh@2zp8F{=2AwUm_;;P?#Q8mLy2uRyE5D>w%{xnEppqQnNsid8WsfVPUi>Z^Fp^Y=yKbDfIxuw0`f9`2b zQJRqZ#)!sy$Ql?#)N|8r5RlFhd;;St%+ik}J5>Ke7+YFubP{f8+sRNy=Lys=`NoZ` zKLnTSl(*Y_`ogzZf15B51c#$3-oT<`1G_U(nbEO#3Z&ZW41abBL5|0vDSxxNq@bSF}53mWcBJV z;~cFD9KizNlr7m2pf57dZ4XK*@%CZmD469Nb_SCzgU)xI;k;)h{`OjaXdOe2(A!?n zGP7(rH*IU=CNxB0WUUqR@*kO*^HWLt;i2)xX^FA!kY<|9IzntC+5JXyGLbxD+;Qjw z-*Tqo^W#{Wli1O$uc*2c)A}G+WvVv^DKxqHj_X4%o<8MlC?-~7G`&QNxk3E?udog% z-x+$VJWoLbL{Ka4Fs_;?2i7nD-n6E_|VK4U3e{N zwH_A(C4S3yJ`Q|^wN+VvSae=DqrrwwvU6)vHE(f8Ei+Aq`Wt{1F$=YhSXUH~h+SZ^ z5A?;)&33tXYr)yEBr1;lS^0 z-6LJ%a1x64lk2=Q10}GfnTC^{L>B#Xkfwms&Pwhv3lEnknB*$N8})ie4jVmR&ppX7 z_DPn+fM<9S6vhV>=zfL1&}m=Z%b?yzC;X#za{zCUsLXfLnE3aPBM0b-_T)4WS`DzC z|4b;03Ig&Ki2tv~h*^r}#=}>{O&&=a;!jZgw0c$^!2jc$^q)gg^0g)WYc1>(IJrNa zvk{DxoYWzEi?7#BoaWrZyCF6-YecDf78%k4(Ilxygb z+Nd`wTzK;KP@|zh5`jQ%-WT;)gDg>cGotJh_hHi{^^j^+7`C%9Rcdrfdz3! zwJ-n)j4AD1HTQy~RHs6L#IpA5;Ee}ase+|_ggEM7-Upq^9{S#Z*PM!zFv$r{Bmx=g zr}KB=Q;TXpO(yKEpG=uj&S|UIn;3{?VTl`S3|qOPo?=zNfuNv-tyKI|i3tULeX@23 zXlSfWFBuHyt-~j1z+c~G(H7AP){rrXupcmGFJn>OUh=uRE)8~SJ^FXkRuey$3q3IY z4i(;5ctr#jmAZkYEN7m?q_d36zKn{A*~97>>wTcyL*}5qF6|Yw~izycF4_r zs+UZ@U&W~joLqy~<4~~Pr2`Lf%Yh)I19SF#b!0gn##0kV)k`1VE?+HLT@j|~Y3?N;B^y4)rMFp*A~yEQ`9 z!03%Per*{Ua-P8Jy+|?TtYOJM<_YO%@wO=#4x}~#k*r&11ENj?EBZB(Yc+VcA|^(h z@hEMj*U{Wd2YJ;M6*aXvy~7|OCF@Fygz1<8X&M?~azX~$Wb`P_m|N4aAh7T*FQj$&nJMws_)S_Nx_vk{|o^C ztz>FR^BskNXc&|XZbjK+e1u5m7!Nf4c z=H|qb*F_4vAm<0EMs|ZB3)0qf#$`{o1Eo$HP`liO&p4qC^pe*Yk0TtR%OxmIyA70E zvg-%U+sI~zrb^8T%?9;KAuf|$CI{&&r@!X<@XPPm{TWGiFp|D?C1$3x;g<82 zV%sv_&1y@Fl&3rwy~%eBjK+MuwguZ(N_UHlciS~k5_){6Eax~IX~u=a+u3~^=qT{u z;zH8=*-gnrReRPiI5P7$I;&p=XhgM)3yc=8jUWqp*9uYXe^6oAB!q?m39Jxd!as#~ zyMl`4`hd_oA0^1ScxD%*`_P3&+++AVaMGXzC@v|CdCciJIB)Q{S`)DVC>KlZC zB**9=o_OhP9ZY)n{Q0yy+? z1I=ly^0+U$1pKhRjGx&fcnc2I%YiBl^?C_%8<8T1B$?{BLiCejQP1)~nbBy&NsZ2J zyv7nK+!wiZ^iHn`&xxf zHF4VFpa9AfAwhizN)`Tgtu{{Q3EakdN(%fuh&L?TC@N1;{-pi$=KlN^74kWl6>P+k z8wq8DeWEoA%ek8Ei8e~c(oNs~_x|;Aj6|1vL_Z+nTPu0J(&Uk{{BW#;DNwhtO%$4g z1xL$P;;v=f*bo_G;+OR<`dg%?o@Oe|@>h7NjMt(_;q>At*&9N+WGYRPiS3CAew6nj z8Uz$XZ^_0VxRABX$JFj(w$y8bG6w_~m+NvTzf)LtZ`ruYd?G=^8kLfgK#j?)Vje5V zAD4&cOso@1jZ2zj=3CW5~wn~hXio3BXFtsPx+ zL&nngRHvST3M}>`7YG=aj9kAa>Ln-nE_yVp&DDRZ|6EjRjz;$0j0BoawAN~NC#qWf zJq^L(ZI36ZP?M%tr*+vBJ!$TaN_7_J=7O>FQ*!^6sikXC5mn6Y7VI5?1(bdDXkV%N zu2u!ttVHY}WtFN`qmh;ps(ePWyTy95g5a$;-hM-C6xR{_Dtd0flYsCb35U>tw^ql6 zeT$a$P!UqDEsxl##svg}IZzB%tnc=dbzsQYSroWl^D7X(fSZRuuhzFMmlmH&51ITU zq80Y?Ej=YqOwc7tqBe;iJt8FJuP10=38WHX!=f5BX-&Talju+>_nO@W4t zPtB-O_rQn@ylYtlEBXb4+`G8k*7T4<10_jdJr@ppVh;W2ub;pi35k}GfeMvigmZcs zl`Wt8qCC_7KpHWtXT>2{WiPAPZwx!T)zqvg$7KajYL}hq+o-V1($6OBRkSo-iGGd1 zW&4NGuOtgsu-BY5JDu6`4}sVhckyy2xjNMwaqLUDX%#(<(M*R z%n!wJFcw#hffK-qUmIPs#EZd`$iYHkjOGi(*}Q=-Z`XJT$IfjA;4uXq$k}Pf4it5W z+42a-w~??OC1R0F!AO~kaUq#i+eiHwAzHsYwIE0soCVYpzh5TkD*qWzHK}(&)cNz# z(a-|--HBym##6lCSF2+05F)wkfJ#1rDIQJz6tyz^hnoW?xyNPtD-5;dt8d}mL-c%y~rJlo8!02!QD%A_w^G`XJ(`jz%h0vtA9=WXHPqa z!nw0~yGY6(3_`%X|(4E}s%?ou4-hskZZkVY3T7rywui zl9B25o`VJM2?a^8V^s>j_7{WT*9qki;RMKk-k!pRVYD=L5|va^S%ZylxI7(h+-43>9dK zrGKArjcTy6EL!6nYJ(e8cZ)XErA~Ry_b0-kf3DfI#oh^(f*!iid|KBCEZ{Q^(ttp`1+QUEwP{sD(1 zrL?6kkZx{TCwD=VlP_b~g42%d8QSZfIu?&dJ5>G-L#{{mPv5-xs@uvpS&Nr>Nk+fXwA29+>JUM_7kYQ>W*M|kdVKPo*Os`g5R&7+z$r0QE+yoQkaaI0}Fm^ zy4C$i+;q=pQ;{40An6FF3=)4lJ*~?>4?b0Bj}9q&&k6Dgdssdzzdfs%jzjfeN7CyA zLbc;Um&xCpEj*#5@I}uwAisReC1?1Id4H_R=8e?8hO|>9Z=bvoK$#+*NOIq(J7!khSB9zKPkLhtDP-j-Q8J@y8iK`uGFD%Ccr}BUZNKVEZ8!z zE-kJZ)3b?xJ$MYx#}7y|0jpOQvI@ah^bOvdcs$%_7BOGMY{+39(I(kGh@oubxY}E+6*kLuje@!@&l$8zw?3Vg5IkwXr<9y3G zS|{q~PuC-j#V0`##+YQgX)~n-M4gOa;;y2J>J*_zy&X@tq}>lsqGj%CtZz7#EXM#}l{lHx1Deo)RU6Fb}+#9Rk ze|?v>gENSo?M2qUc-szB)+`@XgNmiJlV=cx2cZ8F5nL zPun;9JPkzv({{UNQDh~U-wAay_Jk~*NQw2HH^BJ>UFBZ}{gih*Ex!xH6GWA3Ui{9< zJP51u@#Ca+W`SORAZIb!qAo6e_uYA|Q~MZFbWMLrG$Ck_KVn}bH;RXE+=L+@KWmW3 znOYq_I|lM_=NH*A^>~F2(4`0KPlNMsBPTaG8wUMs`$T1YN?Bn_OfYdcJXbFAb{Qr6 zfk%psuZ;Rly;4uZp`*N7h818YRC|1+N4H|Bp)?metz0bZMbTzuLtdtAo!@H5k-c)Q zFQN1}@*{^dDXUPts=!yfxwO>R3b=}-rYIgwSld?z9~mlz6RTVVfPaI(5h%R}q?>1c zWrp53=n6CqT)(1_5N?`ren>TAMq_AZRSm?r${K%qpr55l(fNI8jsb(C-=7 zD3hL7_gK3iU^j^k&_BQ)i`p>U z+t0a;VySG6AX>KO0jW#qyX7cP%o|cMHFV@qga9csl47&T0$QUhZcSh?Sj)Wa<<{ti)O zE8hd@Vl7z-%LSDZB`B36OK6~6^~bsPGh6PZ&$<9OXthQ0vh5kBA}lsVs3==7E4Y-b zc--jBl$J>!EB2eAqtQ8W-=|i$(>{owd+>*x7%BZA1Bb z#7bI1e{4i(uV~u+j^#Rdz04^UQFV#>qT8GcVXh8Z7l~H~lSptlJTPk9EP1Yc`|6x3 zWymWpae9>4^z#ZFvrPxJS({Hi^#pvWEN@+f|9~ATESa8W{06zF?x140Qd1Y=?!Tvc zNVBo$zbpxWql5zidBsTUefgi0N@)meJP5!Xb#D)}CG<~OH@41I86ge95HcnICn^dK1)T)-Xv>77HZC?by$pa!GtZA3kRkdoG z5eb+0d{p0iJ_-oaKQ41tHzfjZS@nA|xF2)9FFAXir>{1AZm4TJbBV0v{hGg8hn5}iJ#w~&K zu3{wSX6WGBkx>)paLqERJ6A5Pv{ag#GN&#iro+E!6%NGH5?J@&G|0(*{KAHwz_4~k z_t-aEyHs)z(i4nwjmW6)P^x%Jtc?b)sH_=gL~tPTO>=UXYe$TPh_zHLN!%SYn5vo2 z*E?eq@S2tnH5Du)H$AQRwQP9Rl%Pux#iNz$<4ZB=xF4|DVY{Y`O^D|4!ZU1cv)OV z=0fu+J8L1j6bbT1WZth_4AL3ZtI(Q98fx-c8Fo=+Ltv(oMeB=M_Z=V^Q;IF5-_N;8)A zmAGQNs8z`}%Bbd*({K*}hL&iI&?PBE(Bbm0$c0UY24rICONe9z$D-r(gr|rk7mU}| zgLA{5ozw8E(fU)3MzKVTF!)CqK3$takcM5yhq?`E$e!X^|H(GAd9k70q{fI5h-;#L zS628r$!3(M(5rnyF!Bc~FD_G01{%VeRfmMCl*iVj;mAj_c_7XX2%jV2$L9kvIBrrs zowl5^5D5_yn!g;T#txg@fa7xw%aRq=Y(}tb*(Ou0Lyb#(;7G~h@l{WCe=)+&N!sV-_wt&Sw^;AYcQ!;6)Thqo=l~pwHiW! zP3k)QGOhBR7TYrg=zrw}&%VQ@MhT&`dD+15>P2E?s$FBFmRh_${yjlfVdq*G{&2R~ z|KPNku3DSbN5A}5bJdeB>#xuRBFjeLnFvp1$S*Va)}pYvnbK9kF!RIRdgvXwjl)3< zE+rjn*7NW1+K%kqr!6PNtv&6UD%ah?X8lf%TXC%Sdz5ftK;aDltt9@EZG32FiEnaOJ|N2Sv_a6X7QWw z{0Dt%D4^RbP7n~)qAOTeMP7YsuVsxq z*UU9SkqIwB!6}DkEgKmiTK?$;XM;~#2BRzg2U#r~5S!q`T+7__{fQP#ehBq!IH0Z` z?T<@P4u@dE?-LU8k7;@@-yDf2cGfpxB!Rvfx7_2=-ZnkVNG4%!Te)BBrr(Vj;-g||~G ziC4&%K%W#^@qx0`eGZy_Dn3uw)bb5_LKUBzaO5N1PjT)AP)jlmEv(O9{nesU)=nMVgVoX*YqDgBb2j%LFAz-a`zLsAmRWR6b4LF78e%Sk~KuX8QXA3K$L9>ZZg{oynp>XU7tJB)21)OZd2qPJH8AN2bJx#LGa*Lx`m; z0ye!gUHivB2;LA60h5324uK|o8`d=ENEp=m?ov1rvm;cAVxS)KYA6}?n&1ImfFFVc zdGMkFX_Luv;UV5*poOk9&jMko^T&!;+mHFTPE7{s5`AF@MunU+xUQ>E#Kl*NVMZh> zkz1+Cq6KR&1st8`BD2QeG|PLh z1BWKs&#LA-5H#^}g`sHBkD<@eeBc{(ix3<;)2`$UlYgaeK6yo%d;P$GhP5ta4pe8W zXwK3^j`fta`XRQoCFz4tYWbBVu70A6&Zp&S%VXO6&b- z7RC~E=TFs*Q1@sBf97aBHkRgB>x&nOkxMZ3#Xjp&MrRa;muU! zgjDc))Fbw;l+zR~t<`{SIGRZpl$^V2h1QF+r0U=9YG|E^!*~7!e#!y&cFl!0Lc}xr z+<&zOo%mpI*XLEmH4uJ=Rsqr};!wvZ!?%^wgUaTj&Fisd8iw*nv3cUE9FSU_F1~m5 zabHJedrB7esE8fsM|pucEk0uX%#n#kbWjnBVZ202W=Kk=HY!wlC0X_F;xW#(5hzK{ zkihjj6SF}Xe=7o%FVX^Wr%>P2(;dQ)-Eb7HNx`KdJvcDYZ#-N`+;5~VhMG-yT$8H5 z>R%x9%&jRj3qIT$2wzJgx7M$PWqC6SHIVW1zQKt<@Zy~ak1!Wu&WKx4GTvr63i&CF z30_NK6V6dHHIqkq&(VtOtOk`~PB^N>bPjdd)7DNQE_v@72bKe6l7!Imru5-*-SSB6O16dg3zd)w(w-Xq$^Iuk zRm$`7Xacq6T2(SoF(~{U+e1@qW3U_BYH`EOf&Ld?bF}3%@kf0Xb=0&91>>Q{?Fedaf-B9#^QYDWGRspgmzFp^a}%H;qIbh;Ly9B&kC>- zq(gVzB)JIKgd}Nr6b*UTp|rJ%c|^qqvk`26%w$PYrZHj8O1syd+Oi50zSe1j~I~F z+B`^Mz<<>)eJq9C`UVQsx?BNS0f6{g-A?Cg4gLw4i14rNt=)>-2Wbe3^wl1aP~m6T z`O&@$4+8R%7Tpi|1Gq`u;{3pJEN%RFS+D>I4&z$ zuf7x_|69cZ=jytC4tAlDr2V@4r17v01It-n$uqq7Z<(7|6?D-&KZ2w7NDpJ%6)6*{yy z$cP5L5MuHx=S(uCo=+!|=83lr(_IqPm|PboV(N`dxiVg-LR5Lk;<_~=Hs|(~L&PrT zoEiN?$F}+QQKNiw3k#g}Cy^?~NMq{nD*}9cPumv1ij2-kviE+iM~k0^)Q-E}Yjk*E z(8eL$0$cHtE9^|rIE)MFU_XZ@nh|hJ-3{}PDC@Sn@PO}lBgJYuVP9Uw+>uADYfwp5 zV!2;3w$+WUU$x-+t1|jLc;%3m4R_PHx~zxy{mnsPhnT>x;#KD3Y062jY2)lds)dxs zSZi`ZEt=1$W^-3X{XuA;4qn66x|{aAAN0$9|Bm)KBz{xx#?2+`kK|>+MZNswn-G8% z3}ajN2jWBAKen{nv3YMZ+0JPVe1C2EZDSSXXTh}p;xy#f``Ln(Lj5Q>2(~PLwP{$) zV!#wJ(1%@W%V1E)TK%KYA_zocAtuQk{DO%+977CZ6wVF(n@p01YpD;iSQRL)IdNR< zYDjqMfnQw4!OwGvYnp{t42{rF&^f(FmrZt~fFk!h1?WV0f&GK; z`xS;8ta6d*MYY^KFy=S@rn@$^Eb*z;LZ^O&pV&kz{f0iE1RHX$xF2=CZ{i(Hq{=Ve zo&DS>3)!rcM$=ZT`@>VMb z_$$$PXNb}tuu|GyZ9z8(IFAO2&>!A_ZD7Pxy*PJz_C&7kE_wREJ7^*Y+)Hvj#O}P9 zkq-LI^RLjmEAjKp#5K^@l%P)kuElWyC>(-TUR_3DE_sv)1*~EmovYhf%;X|DiP1{A5b{FBbxN0xKc-6G)gL#; z0Q@|e)%kveQ|5KSd<<2fyJDOWhjloAEw$IDBW#vP&HV&QZV+>=M??_ljo9ZbV)Vlh z-9BuX1!B1!^5dNMO~#7o=72}NAMnMJ@*U59fSeB5e{gZTpLt>2+=NawGSJ2JG{%l8 zT$T7mx+QoBf7`z**nLb(>S3`#x4l6!erOFtVX5p%Yfqz{EgO@{Sb`4~(vZUg53zc3 zHzg}uRHjH|jAjTEkr^5GnJ*gr=)zp+hJl2(@8(;O6O+_Jb`zZ2E-_ESs(`gw3&26r zIUC8GQQ!abS8LmTCB?8GU^JdYZrG7|Vn&#YWwsqUPF6aSgc8Z-KH-uQd0z{SvZIvq zD&b*wp)T!q1X2PRW6oHNk!2H0_8y&YI2BX^vY;WhChV3h@4 zYDB;!cx3`XlT)?Zk2iC6lJ>h%v0^8)0)v2%6%K(edO>@*wElBFqGU&J)gg(?d0*-PbwW(q}L z0(x9XGXo=4-f^`F2b&9$1K*WyT)B8Nj06~#WTsOhg(gO)B;oLVrZPC-leWn{7AvhE zr>A8@N)rLdf%@m_*DY%9ycX+l1KUDL;*absNyxL9I~_0{V1?|xa>O1oUiBU}p0ayw zR%`^}(6mRJw_j*kF5b%D;~U;qi{c)(@o}`ZibD{pgdzM`FQ;e!$!egS^#&?$~S5wu0|8NouNjZSQA4pnGRwm?14`Si8R6OQsi`t;;Q0tJO%yFZwx4@%5 zWaLk+WwIf-+OA>oT>dJ=*uN;IyHZ!uP)04CFcWg8OJOEjAZcAwd9P-I9DXA5fc$C# zq(*aG7Zm*czQSa|+s|Z^oJ}woq#VYu0lN{|)XfQp$xOkV2g8SePq#pl(5ybB920+4 z;=3MPGpii(gyim#yUaEF>}bdB2?h%2M~O}bv&CdcB|%YpcX0Hh(+8*D9Ckwr53nRV=bvDniV~&;`ie`N3ZID@50`I7ay-#Br(s>rc80vJMfOI| zhHZ_CwPTq};+ z-r9l_s3R0!vS!^0J_xy&cbw;;VC^3rgAW54i{v=s4UI0 zsp3B;6pHz$7^~$I{XX9vJyeFu%nsr~Ndj5Km8FKXL-&7@w`RoJ6ALC+3mBEwl{YJn zQ?q{uNp%_*Hj!X&aBt*ki}};j;QR_Y)AK8%S!K%xaz@jR4Rb`R1BMurC-PTc9f~Q> z-ye=mMix8K=7F0=vc8!nGE>5uzzy`vKO?nK?m-}0C>_K@J1LFo*uCB*fR1wab zgiuQ$Jny(~@>!1|b)q?V%z(>^Y=-dVOk(ncMw2Hn4bh+dfs+_DZN_iB0|MugKXfN0 zQNIm@{L<=j!>n7uiYYBQqfJQ>?f$gG13mX0%?YCuH}XDQ9#h6Id=?EVOKPV28=Ujk z7e~{xu~V7vLVpH;s8hVxiD9R{mreb8`0K}68IK8TRjZ22&} z!rT)I*yL+%ay4D?Qx%?0S7()~hx_&yi5uI-gs#U#T;>7h7{1+^%(Xx#L1v%-X>rk< zVO}ElML}dL8RKtkWblG@(P~hZ(LSHchP%t^sFU+04<0{Lfz>KpA7B6r<^3G!Pfn2d zs(J+xTs4A+pZ+G)sFp*%M6d|r`#3Kl!B336;N|O2Ovm~7t0ZHQO-9oTXb29V)>9Db zIS}&e5Y3J8Gl}V1wHd!Ez;}4((9AP5Y#A526$UdsGNX!{!}B1?j|hEYUpSqkR~G6oY@9yv9l0rCT_by~B&b3+(FO8zP19erW8z4h16M zPe4kd~Q!tLtxqY&kj84jKwQ=74h1@o1Vx>0!YYilJ`c-H=<=Pcw z9yOiP*TB4mB9U1Nw_YZG#F^&Rdfu1p5(I0U7XH8u0?xaQ~eZ$R+M3}oUlBS0z}tN_foJzR8Ytx!?b zZLSrt#`)}pRc2vEmT~_9yiLI@S-ZBoy_$9Pe8%T-Th}h?`!AGf-+O2bScM_h`ohdX z#O4|-;!`W-DrOU-UZtW*V4mLve17|{CqEF7VJ~AAkpFf-ezmP!?JSX8e*ABx$uFg} z_8CZ002Bzwe-QgKkW^^@llX7eAV_J%!T?l+|I1Wh7Lx3LnOe_6lK&&nytP@<(qBN`&md5P^h#kP9BUj`Z-9QuQnqW4@PVL9Aq8n*W;K^UO*&y}SZ0O_BpmpI_& zAKFL)ivP)x6#!-b=ypXwKl~SZ>jP?#zmVDyaQY=LL-hds6Qg+oX#aT>@B=XZJ0mB5 zz`q-k#u^AX{UQm20P+9W^&x=if9k+6fZ|tgX>-Ydi7zrI1t9TH`zRIg{zV?70sh^% zw1f=6?icBt2_XMRW@G{8zQ9lFYb4zd0SIaJQaq@wh1r0FFKH`FE};D%UgiPX0bf{N z28jQMoOOWQf38=40{s46sCqM?SopT>Gy8qALZ})KS$$Rd(=f-<8bDpya#fvU* zT0$I1%oWZXK&xHlSg|?n8s}e>u78u0hq&@KkO6Y97om|hj0-V{ywFk<*>gAdUki_?K{S2U4U)MR%db6*qCvtsHcOAmR zN!@dt$U;VrcGV)1gvj#It|lxbh*T{k3Q}jZ#ND;LkPuTB=$-Qz*JlE>HqA92y^5XX zngjaxEpk1>2dk~S0?+*{S5G<6ocFk1#U9_@>pBrV?7rW%HSOU+*Zl|w9C5ub0iinG zL8#{iS31`+0U?j`TsHwBAOGIml*eawUUH=aG3>JIViXl#b;W@r`H3Jp>$>X}L}GdX z((;yTJWjAx1+ELQ@g6i1>o6gofDRt%TI9M6TElw+tGL(|OA~Xo#FfsjK5t#Sqew_E zAl`lNx)Bg&>L=F*sP8aUbmLTqsp!_(bl0Xbqdhy^WTIQ@Asbd&x9zSAb+!8dw(2 zq?U;&9gy?0x#4KObR~Bda?Dzd0cPUYaEnkdZXI_PCe!)-+=r-e*Fm-`4UcepBYoR3 z?w<$;9_MyOHRmpK=cAf#SGgwu^}fcvg<|$MxHFM$_AM^X5~k=5_W;68`P_OaR#?IY zuy`n>%dEHD9nfZd$9;mbo*%dbc@KT!_DAURm3tiF^Y7d_C_6&J!{`#BoChY!WjXI4 zw9ZlR0&$)m)bNTy3z;wyG-oS#N^)SN)txES@#>+Ha09O)=EDeo-a|kW0(cM5W^51- z)0ep%!s|lW!g&V~7DVuVMVpNq@Mbz-{@{GJUaV`FNF-@|H_|;b}2Z zR!!#>0?5q(746r7ElvkP5Xqmx>j$mk-@r=unLJwc!)NoBpoTMZd3_!DWs{}m;p4qX z*jKIxiQU9=X1dMiVMQPVCxW105pSpz8F4yZ(uKAlxu1CMWWrUh3(LKIJ=<&5Cf-R< zd~Xx)4GQm?1j5g^^7=!jk?)A~&w;T7jdIzG8%;8%J{J2x&>+RI8kHA0{*q{$1Vx&@%ikkEU7I zd>$?Ur@?Ph1N<_}`6;-k7=DtE8)1^T6o{iw^C#h6X%`}H7x=W^Y|i65 zgE+JEG9N1flW>(k14sDjZGL;iWA5=MApO__J_G$F_m_k4>~}P|n8zRZ=a6alXZ{l0 zx-@eY=x|1feg~#;S^*udx*h^{Ju?U}BN?MvunQpRz7m+8c?rZAh>tY{xNMM#wFJj7 z9yp|XUCz~5OSTlkaX%@+wy;HoZLPqmTR8wG~}ZMsSD8jVD65!^>@12;l` zp4lekGjZDl15r8$TA06g2%4ixpp`j}_Ya(S5kTuwz+?u7L=gf9YABwofTkeE#3lrk!FF2 z$+{r;70rIl6Ffk8@3P=`oDpN62;`WM^PUTy0vP#1fE%Z!Y=zy^=br#M7oXm0+<^tN1E6hi7s6a?l-z*lwG-E^(;cnEHD-|Zfp@r<& z2_Du~3)^EBX8H&x;SNF>C8RruPZl9f_1Q7Po~WxNPDnTZyXpyBg2{cq2x*ec?LaNI z?kIeWs%v%@(slEEg0L@2MRpU?%JCslxU6Ey$ta$cLh_Sf$$6C^^yOh(ClS;8nx$&$^&`+#2FBBTSb zXPc0gq((c13CNbcTgV_x*eh&}hPIy+Vxc4V55YjUJT1h1dDvmlD>}m}C^{>=f&n^l zUP$+c&GUpb4>tZI+>L^lj)35)dO|TX;fl~5R1(8c5a0fUxOV+T(L+mSpMN%k&qwq`~hY<)w^d!1ZD57c6R3gGXGP6@AI*c$yDVl(R zc;zFac`?>sgvFyyfCw{?sSaP7i?Ca$=mtPC?=SGSRW(s7Y+O}cB$uNLxE~Ss{Buek z$+{-ek?z$*PR!j#qQ^Kf=9~aukK7VTX)`HmCOQJG`PkZf8MNk{vbvD8lWehr6}`C# z$CkO-Qbec1q1K`_v=!J+)C|*ncps4*%lga#q5=T-PJusjl0{17$xapBMwl^BM3eZ% zV38ln$ft@VPUt7@CgX*B+HWMrOxR7rcXhasRmmb}5y}+gYN1aNJ|M zE)iixVB(jF{1J{?F7gHaB<(C1-T%7?Q%5BB;xxS}qOyM_?5(b!Y zv=>LC!a9!P1axVooA?e!Ym7>aiyXtzis{)1J~ZdXjPVd-Ul?B_e47Qp0pVg=06#{E zG3^dl7h^dh=~o~EwLidq?3kO_nOv}lMMT^Rayma%1Obf{)73hct`;e*5`EJ;g8i0RGE)KTK!fNSwB@W?n; z9DpUM*;Mg;04t%51V+J8J#>ax!gQS`-hp8f%@E@fL1J!$$hKMH7(keUIbym-ubU^X zi#|VFBc^3)-W|{}ZJl@v2;7IinWpQ-?Cx-*m{x!3W-&diUAzm#!&}9zQJZLom=^Pk zyTr3lTf(1Wyb&OUCuq=jofOl}U;f|Xzma{@S!#@lxF9Y-+;~Zxi+TLup;&`6`r8vR zZvHMl0AEUH`;JFPF@NslY67m%PKPz3B$YcWj^LDS;e?qqeVPIZl;}&p=zpJZYJ>I7GX< zM7xl%rE(`SZkNP`2@jGKbIK1|G0oBnNtaBCH%WVLHIew`GADA^AQ3RXRhO)ylLX7( zDcXSInrD506DVyOLkW{5fE|aY<&`XMONm- z#r_gEQhZhlY^jnaX!yk-Nj)?iGFtKq#XZJLiV=z?N}eJ8!&J$yNN<}dDTQ{jvKXRc zSy>*eTg6rbfewk79SbF$P>{QXddRF@A*qFex3k%(-`XhIkMxCGCCgB8=q^b^qz^kF zxsL6vjz~5k{FKAe7oLEZz2L_9_wy2DUwKJ#5XtbFhTo}QH{e@k(VzFw_ihg)xLT4@ zMEoC1vLJM%>jyycUrL^%*qI_$eDWK~3J@omAAwlwqof+>*gHT<^P<0#^Z@dDxk{;r z#{|;m=%GL<#f6J`t&+Y4NV5`Ob@bz^N!kmUECEt`#G^x4B_G12)Xznc(k4j1 z7sDF4TUWXdl`U$>8VPGAwIV&GElW@Cz{>mlDqTsJD9k0l=#=bFBx9T0$P{;znvh@C zS9%Sd>1H48wl7&a8+Fb}lg>vUOhcrlnDC}C(vujLR}Rr`%=dB96_`=WCrfEYEuAi< ziKI%{t7ZOTMadC_j9VKd0D zowucG%vCNkEnnIU1@nN$ym%zlp$lPrKs*Yiba*)>Y}9h!N^y9ZuAiihfN8^edl{k| zoMm)m6S*=vvR{QV_-=1kCdy5Q0b=acGJJYLL}Ji+*dRNCZjbVmtwyNxmC-V?Jy7-n zqq;Og_66G))s$hkNOx(u$2m1+)MHn&>57EUtg*=Ip!G+wvbt!^x3TOs3Tm3mN>H#^ z27=e#N>wCN4tC??W$TgsLpxb}^u1*l>U*n18679rBpF7JS=~=o1GQZnDEo-o@`lPP z=le3}qrcJl?nPR;MZ;F&icH4jkCfF$NzFJJo$6hu$S~hYtUK7}{tcndUjrp<_7s_h zt)ktJ$b`&+8L}>@-+c}nv)%J$Ex_)+By_n<$=1&; zCu9O9dxPvFhVAzqvQwChi5lqZ=-smA81SQr1Rsz+aV#&cFZn55NN9Tr{BATl^+gly z&Q?uusvr7uCWnnf&l9pb7>9M|WtfFzmku1DYeL?6nU0l#<)A&vcwLcYVg&ZyfMp9H zb6-Y_-TKFLB$y@7Wwh93ypnZyE%)R~Va5?G^JSjwFhrY3)-$oE%!hrE^+m_xjL^p&-(|m{2h|+qjnRYAd^z2wrJ2C%!;c|i5>DSSZ$d#omHZM2l9guACNarBVZV6Za+*=!{Ny{3X^| z`B!9G8YO>&en`B*lyX(MsaD?69;CUyT*y3&k;kKS?Rr$2ku{golIt1IYIkcV--7hx z9p$G`d#W#J&q|cj$-O*Dj_U=9@hcBMbVNsr_euFAPcG*&Py5MRqI6`c{FZBZ)ST3x z>=`mIQtm@`_(!W3Kala5-oxdGQB%4V_q}r^YWco zU(Z}-4b2S$C3mmM*Py!@w^$)fzMM|p_-ArD1&!6v{Y-FV!ZUf<9tQHJ3G<{tZbU&& zF)Nt$md%^zUun4a-c;aH#9VYz(2{$YtLTo-ddL)XRcKuUjPuFA^IL*z0dQ+*V^6_mYIDbaokEh+U;$SSm0{S{hS(uk^$ z?x}WxpTa^1+ogk?ttA-o6WOf-3bz-@{ zA{?I}B2x7UqK>Q<7!9Npcp-~cT3~EF0u`X&`HF3*=-nd4E}S>|2Ecq|nW8Vw1E19j zdfMByR?!tbmuyl@K#%8bQ7k~Z_ioxfromptT=e+L0YwKfz~Ix0&d5LQg5o0bb-bb| zLfH7W;$Lj{c%Xa_nreO5wGld6wkO>HBG+!lI+%#GYmu-p#D6;OgVii(j zngQ#t9~8$?ZXO`y)Ig<_oTSZP6!=V=Dfz0ng>IfNRm?)Xv%RuA;N*~_@?StRos>Cf zy|t^79$UZil#9^Be2J2-vF8*@_Mt{gFg3|1Ng!DrjU|!osvV&b%+))Q#vT|c9#k3 ztV}@PXLnWZL4~V3g2Il8%6HJXxD&|raaF2FdZLoYOzF+~`k^n|5w`)#3>2~Nj4?`4 z;%g4fu7S!m(72)95G4lvmyt?LQpRzN(jKAnIOS245hZ}k%?ZleI8w|cHoje^(%=(A z7a*!dI3+fB4u+_w4_AYPXo|F;6@k2IubX$4O+w7xpT4fC9_guCJO26HsRZ63n-NVmTb zDr>Xwrp0Xm#ws=1O^WRkV%@GH>`>QD29Sgd02e0IcWZ);XBxUKwgOC?=r$FNJ)h!s z4hLf2Ot(RRlS|3K96Q%-D+H1lQo!F15;qB1nHud%G)vqz0_&ek+>*f_$ptFYaGBc# zMD$>txwG7DQwDHM^>FWl*W^V3?st{|JpZS=GkTJm229USxPL`=pPX{Xh%+nCxckA7 zlk{}daM67eBF7N<{fhfS^s(9v_b2G1`mXzBF2KU4?ldq{U$|F8`oTi?XiS=quidjy z^U}BOcc7e+^ueHH_80dzAiOt2H4)n+L!eE|QEkEgbwlKiScUIPksXM9k*Ed(W${q> zd$L+J538cPQRRg7U+1d|MK_uUs_0(rW2mYMX%HD4ufbpH&A&n;##VM7|ht= zpeEmAC> z{2tyy^&BM=Td7tfbZpJSb!}AH*n@$i!Cd!ts=cVzt%E8KgVUk23U30LeF?0&f4iy* z5kH=&qW5{~UMjj22KQD8vE9;F)du05{wliqEl+0Q##9y77pBWVR?q0cs`ZGQhpBd= zGna>}zF{;jja7xBUHc3b-Cda{sXim0$5fRZvpRgfsu1}MOH?N@nom}+{;pW1nu6_~ z>r`|Pcw?iAUWT9B!uoq~yGn|B&+SsBp`3M}Y7oL52URA7myW7lqMakTsx1in{l)qx zIi*?)I5{>If^+7ysxcTLl4*eWUsR1k&$IJXc{p0R&`4(ftAZEuU`15X;#GJ}bros( z(?Q_oEtMC#P!m|zP^*--6qP~*)U6XmR?SN$_MYWe`{Ay>T|`Ns>@kMKpQUK;fm)YILd zrjJP?O=@3E(5`;!!9XYB^TF9?)zuOZ+VH8S8f8C6savD&=Fw^+!jxF`D})Jk)h-Bo z)>lgq4r!AnD#{pzjMSg(m;F*L&pS=OA`TKx{S zbZDm@h+0l}RMW_u=&b&L?MJ$(gAu;$rlxaYP!Dw|;%j=TuV4>GB&#oC5;Pv9eu>Fa zeVp1G+dEHI2jIqK@g6brnWr8|eYvWpr7I>2LIOlHaShQ3Ny#ns2H;wHTYVhuCEQbY zMJKwg2CjA=)Ox1qfqEhKancL5BNnA;uhpdhCaeJ)E8nPBp*Jxf)H_i6)hBg4;<4Y< zO4KqiJKAmU0L@<*>o8|cH-wp7jVHoALQMwx6)D$fp=y%V>p@{(orYG0O$N;e(42GwST8}- z0mGcQ8KkepYQ+p3OB^s#Inct~=%K-bIP3%pqKHA7 zwj#i%PS9jwyWeC@9kkYess`V7Vt$>jp>LqInW>@kb?$c1U3;!3t~L-y{mm+AdPdU~ zg*RQ$48|qMp9r?Gs z(VPLe_z$o(>w|`F<~3h5SlY;TXe2c!LwCD>*L*;$9UZmZ(bWS^+94<%;;O|5)l6N! z)(Tol=3WfY0F9E&5^Le52B8*{ZbPD2izC%Tp{3bvaMM-;ZRGww95IbH45z|UZ!LX7 zKbI*tn&qpd3+eCv+8bzmc!)L($8=+LZB2yvQQCZL&yCgg!(}tDiIy%rKFzc|NHiwA zt@avrmD^t1ADtf3K}$o^r;~O(y7RQFmR^H$`)KjBK;|9>cSa>^IXJU^8>YRBLWv`^ zIHqLI5g;}nrNtcw(`}6QBErz|+Ors_xkth0j(n|n0~|h)V4AGOi#HN?45XS)*WycD zOz&A*4)!#aXxE}@*-|Zi#udL@`z!Wi>nbhXf^J`}osW2(_1b$>*(U95bZN&Q+OsG( zdarga;`|)#5H-LFH?-@qZ2CUY;=Y~9c&4Q*MaoOgZ;k8=#|=eqFHcJW9lz1*Yj?x|N8mLL??acMv02 ziio(D?mmi_MCtZpW9~U<%!$?EqYuWfo~}8DV`zQdE@bj;q}z({-zK^+YVI($LqU(Vcw=UJ~L`liNkPdI3>Ey(C1@`3$|H9sK?eDrR zz|v}^ZUq`ISgjk6Tl?g_I{Id_4?_b*?5~3U^aHwIFd@4la{H(bPvPV!AmkUhPC_Oe z(5acPIXWyBM1KuNcU8sc&=#5!U^p+m8GtjvgKe#R88t*w5WUj|JF?hEk83 zI4q{y;KM?t$9fPV;{hRnc#%`LVIiI5;ZX#-Nj~z-FnZKR|LWWUVDT7& zP}kbyhza1$K^}bkc*2v}9vL10`)=_FL&c%@qTM#^*zJK;Z|5CnR0L)wM^)E4= zqKANNchp-jATONtiP+KRT>U`Qa8#m?#=w1b(_^6|#gBkFOQXk^(MhYvfNa+3>mp~N zr=D&{Vtw?5V1o$`)W1ee#!!8G?9}k)`dirEwzd8cS`xR@(~J9k9rYUY@lqH4Q&d{| z43u8%roV(rSNGBH1xSuPhsKmNeK`7HdI9mSS*#JW*Hhz$>LtvLL3;X1RlA}3Ua0-R zaQzyzmM}(-=M<*LIQ>2JrtWWgHCjuUs-K51PrV{~F3{m&AQ%uW(?7!SbO(fRSLrXp z-#1q2KLFpQEIr-3{Yt&e79bkTbb@x_8R? zOMeZSmi(h1jZBJv_3r@XUD4C6jQXbj4xk@y>W84K$#?X0yQ|9A(^JOO2YP%w&%A!5 zPe$17seUlR%g^=nGG$zWo>m&aB0W|)GVmREf3>ehyzjL>0~vpNtH%mPLf?aL%|GZD zAuQ^Ct*WNG_VSgus6&!{qK?ZN=H90RfJOXsL#4s9z8KN*; zhv(0P<72?9xraUmTJ=l)4FxDw5NN22Y@wkBTE)MF8Ac#Jq`Kh@hQat1T=*r@upVX_ z`5I$5541C}h7fd5{0(U0x`utoc(R`1C4ght_IG{50wXXcBpH6i%t-$ZjTe#)botH! zgiL8|R50pvLlW8=KiEKT8D&X-vMJ1GYmq^leEc(Xyo5E%|I`i z+Rrq=tsWCO%Ww{wNmqv$m|d9$9;{|$Cn8bH4Q;WLb5ow zy{%hocnmBvvkfxTy>f#g7K_T=9R^>>K{DPM^yu~)rlOwgeTLmw9y^?&vdq`B2D)Ts zoi}X37_{WXK!x35(2;Q$4SB%WD$h_2$LP0z3_MgY`?8@nGUmF1);s?iW}>NncMSWH zXufB-fUqRr5RYEQ@qnr0L&JXbOZwD6kFBB44Rqt&;Dx~xm^bu$X+VWi0a%_;V1PGb zn6yGe9c@8(IPvuK-8BHt5i?#cvJukoV?0!*>8hBH%50Zy19- z$sY{@%KOQHHkhTK4U-V+zZy=U?4$1ntZK|gN8=HkuD!WN>@2w`1z&dXjqq9yGe=-_ zM)wzrjd&6vr81yp3ycAzg*?W@6v~Xfkle=2xD;UloRSd2kBHJYqweXAN73X;qwxqp z60ZbtXAh$fv&3w~n!v2_GSc(zpT0&tNRl)+=)uPTV|DZ_IMnzC3xO%hn1PXATi2+= z_MC>sKan|B1S|<=&!eaD z8T#gu#Cjs>W5g=9SO+?T`WYXhfkZ@nQjFIyKHmo!N1)*F;l>Ozz)wq!Bd~qYD&u}M z7`Vpx1P#g78=oTnd86@-qd#26ClCH3s_JFD6Cgd)QmTBW5IKn3Hlq*T-T%n0A{j@G z+A0qvDEG*1#+vvvfFjH`V-&didYkbZ7%tv!9E}bn?=sp$NhEnDhwWOtdDB0TrdXVGc@%J@5(J|phMGx^~77y`f*W-Ihe z5*PzpRH11eZiVG)(;f7yL}Q}oBvQBW>V&u8lg^8C?IF&nxwcLytBl#9ea=mjl_36^gs|6BV*33Fs;E3 zrmZn?QD$5ikms&7!Eut*%Qh7NDQumIgOas2nixQfH=5|~aP?LbuAxh}nefRP$*Ts& zVs@G4q33yfOdAo7+;94Z_55B05NGF@0@23<$4$6}O#0Kb8F?h9On-w1B&-JT6rD2- zKsP1;QWodP7$=fbBZkM23#RILJ~(&NG!erQ|IkzfXxt+cp0SzsPfaHf>Ry^!!-$g9 z64P-2;jc}Ppw;b-X(?KnR|^!M{Ahwg$Nc_@N^I!)&4gj@Rce}s3JM*}^p5+nlNs}h zWJZBwt{k&F5{*1Ft#QG8a~I6fnKH8;E5>G>8EY|VWd-KBdNZAv!_4LYbY-=l`3Qh+ z{^k;3Z5?2yN8HZA<`N8j^9b`wgfDBESE7j-Rx{1tbus2iXdky7>X7+;`K{Dz9@klE(ee&|5tyzN3bgK)E`d`f9SViJHnd1=7>q3dlnyzMF zFh}(Dfhj-HETga3;OvfCE+2l7h{u{kNPa7`6A9~Wh6i^Hw~x6$no4f~0y74f>0^l5 zX=W2TybTb!bWERUZipV`O*QvK znd&pljc|;@o5C2mdw6=1{9R`F#7D~*DVftWhC`aQGmA+6T=OkOhsMmzeRDQl|R~vjRgj^mj9Tr?Ip-s0E4IB%wu&lu6Dq)0>0$Yt3)b`pfm^ zff$eUmcX{5i@DOegxRv$`~^468+Mt0MPA1}X51GrbM~2=BD}HR{1i>*9x>zDle~&& zO?i>X)-iIXagLdOMAhTC*?{cPf0_3nJA7LKAK~|N<{3Cgr(ZQ+M)6i{LA+0ZSw-@? zn)%G)8)kYnx*eLC_}gZ>%4FVSP2ak2K0r;j1LBrP=5RD{=ZSeLHs=2VjiaBNA7hXb z3(YvinE|iNO^_Jg0f=Xd&1ZLBf!6k&c_7jfI|A+22Q%J=Fsjey{&0;%T6KcP zr2G9eoYFnRjpT5|%C&Sl6cL}r*g>C^dgL`&vg3ai+KI_o-{?(A3f=9>E2JC zxI<+Uzj)I7SEuiu&k=uB>S;m#00%F6cW}eeD-m&vix=LVFzK#dcM-PYd(me+#R4x8 z;-w<5h5*UD{@_G{)ax2HR#$kf#>SWd&{*K+Mem$7YOh*IY^(K3L5sOMui*emRx&WP z(tFWQdpH=qXrfm)d(nf>(G(!&dwF3rm=Qi+xX>`!eqJ*Xh6Q@ji%C)~GX*l;Pr>l4bIt>M)U-LW4C$|7rd(a(XG*7n+B0pgaSUZDX1Z>{ngPTgDM^$y)y zJqVb)WqZ+yJ$^maL|P7p#G%mP3HJs=4=W*bI;?MEdse zUV#&=1QG2h@9*f1W{mepgbT-d)9bZ=#(N(IeCpf`Z)R?W&ta6_F$@X?45^Hyjq&A? zqG2)4%)}KwP2k{0zE`E`Nc`{^7ves`cM*`f4+k;mAQjUwB_n))fqW#n*xY4|FCN(f z$NC<_wwDtSC3i=FXBRVk@x}_SQho6dJ!d4ec}(%0h`GFay6>wY05iY)#&!dk*Ts)s zT5Rp+cNx8&km$!uNc7)`HzfYU{d?j|^0R9M;NFL+S2JKX!b`ORe#iLdjR8NN)(S8& z>n#C$kn(GEKwYF{j4gM3`xsacj*gAdFi&Fx=ymhmIstW%r_MNVE3$q-SKL|7Lu5;f z00-PSZ0{8C5H}9 zksTSJEA>?XF6~6}8z2K-2OKqnM1V5zl{+-%Vq<7mP60A9;Z=Y$X*My2 z%dDv%$j2$Uy+dFY?i*Y63>*(0F&75}-b6*4=LAZzQXJkASQpR3rDp?`xMfs63FKhA zhhxxZs34?xG8hWc1?|9-Yn>^8y!8#5gMFKa$kMQ&8$rN6xJ%GP*l98!rUX5~dy%^& zC_xHLm!^ViZ%L4fbek5#VbV7Rb@Br8ne#!pXl2&Rppl*cUBtmI=#?-$coB-PtrPqe zpYCL}4wlpdxNBkXVSGlNdMQ|m8|B!Yl*D@@`2lWoE8A4E>FgoOKXeV># zKpzY0g?Qn!4Qcz37pUxOQV1V!Gt%b*QJ)?n#052IWJn=u)JzUZ$6eaYxgm?e5?PfA zOtpRw>5jyu8$;%z2LJsb?=VdaCqkU@`G)u9kVwQ6?uAT6_Z}65yg>eoA47IyH-7sT zG9TShI)(lPAZG#SEOrV#1YIRtIiaV4WteLyE{dc*KlE=v4T4bm#Q(i0lzu#|g)a18 z46HdgG)n>S&pM%x04K*5gQ}x#LX&Zh>j~zODa@T@ZVcs+?gV!215-lR;{zSlq|n{y z;@QklZ*;k2WhlMToV+IV4o0l!#?U?(!LvI;uV6e@9|*lnBbpof32-uD8R&k}iPbHH z4>yE%#mVe?F_eCOz512VRjBsghoN0CLe<`cuELoA;~aJkC-P=(m?I9p2i>pBRV{(`f znO#PN1!C$|TNY-**R0y@3roQEUAbXm>~QY+Fq)#4lCUipxu?$ICS2pXt^p+mb@*+( z3IAF%`~nD*B4{KFehpVKpW21v+Xc+nU&HA)oz(0ZUK_;+4iCSB^Rm^U@&I_N8 zu#kj5#5i91J$wtqjkI0|>KCjFcl3uwav^*IF6XMJ;bV|_QDL|b_(;aD2XplV)ig}O z$M85*8!V{S3B8}{UM&>E&*-b+`;_F^2H>1j4LG&F)!HCOYPD*iK$*HSsv5I0Dk7*F zw4F_kSTqY@;-?6Dw_*Geu>kASp05!(^+8MCCU9zIo$Bsh(6~0KI$ghG`&6IY8SoRi z)oaAU!;&vG_BID}SBo068UT!0P=l_cB@1iNXL`AdS$xQn8pC1DA{V!UPKO&cZdV89 zd&0=USdH3-L|%iql3m-t?b%t8ViMmXat#pPw1}k7k^YR2#3wV%>DG~>>08W$I>cL? z{r#trjI2m4nXn^9L3UBfkRrZbf7U)?fG)Dx}7)j{FCyA-h4+FFVo?XGQCSk$CE6obw_-1p+Mn z64?xw?q)tUY2fbp*31qCd{T#+{yrcaw+Dp(8e5ZYT-Hvg>4I}$^Te91Kr{1sa?Mf- zr_~hFxiPC|C&YWqscFKwFe$6%a-1W^9W@6A1Dt=mrrsOiF8f-Spw==I<+ZwD+CMbb zlC%W)HnrBIdH_f6t~EXe;HTnRb^XAJ*aHyMQ}(qaWbgr);X~B5Ga>WHTxe#rhT2!q z;gXiM{{~*>;PBdaux>1wR$G8RtXo$5I%dzf^|k4I)$grMH)Cy?+VrZr$-&y6v0~Lf zR=X$yVD&e(Q-a{;=?LiFZ-^R+5p0D>V{?=T=6i47sCt-9o0~=LMgv{CM&Ti!DM*Zx zVFH)-jk=COxIQ3i1e5G(&{)cE$?q>fHv+=u$;%h z?wD`cfr0L}AHMDZn8L4E&H?)UiiO@*g!~2Z)ZMUb0a6D1&D_0Vx$octhl*C|Y2?Ih zOARS}=&@ybT5M8}CFi=qVWgiO;oq@@*8~>)HQ}mf6~_uhVpmw_Fkn3s;ml(1pm5~D z&ks`pvNge~b+}_kGVfTFcz8i2Wa}M^#^ek?7!3j`J^S=aP6p7!M%I3NDmha-TI&#Q zzd-7&CLVV!T=cW={Qe~9u0`w6+kQd#Nu#)`J)~{R2ih8YVk)wj7sRh|B8NZ_4FfTj zoV{z&I6R`%z@Q&QZTjY8)3-neu;urYE$T;_1Nyh1xP3ub)~NPaX& zk{^RJ1yr2ed28YSByW6d(b@-*dG{Tf7^sTg!yiU+DBt2ojE5Y!|8K~GDugf{ zlAXxFKOI6y^n*&TNQ(#1<9$vLFf+L)fp2ff1ghtM*t8A*CzMd@lhgXN9g;X?a9jB8 z_y6b%M#~}E8Cl&@(uVZw{bO%qNfz24;Y;4+77#&7qoWK5aUCO2>lxTLo9r4 z`dH{pT0gW@D-SxE@etHkbD8rnmHu4AQS0G&(U6j5^a-Uc<)-vfP zsY8BNfT_AYL42DF3R2-d-`0Gzkor3PiNy_lMdt6ErNs@L?d&X2p!{rnohC-+1>bp6 zQt$+m($((5fRxymSqZ~_akI0#7z`7^MnJAa0d=GFLI3R+Y5EkhVSOO=Vf@odA9glU z4p{(SO+QF&XXk07@>&RGzVy_h#4o)Ehe^RxNP;a^>RWOx5ea*SBgN8Up8-u2LunUl zyOUW!iq8p$9mjtp8D6shb|k7H*i6WiHU z{i4V0j6L1aLhyfRLJL1@!yzTjo)oeO)G8vbS z1s4((BQBHLt^9wER6|vZfRQ<6E>Vlv> z%Aee9`61+Z0tBTP`q<3I&9vhv8A!UmvcPA6%Ho^(3UpRK#Zs=lviO#JKjZa3@5X}n znULRcHhLDGL+)nfj7jmGy0iqwN5EL$hB55|G6s=DMHbI;?vrbqnl1y^7?7|{977(l zFSgXL@T_?;c-G_+gtjr+SzIMZIY^j!83;9qw8WyQkO(RP!m%qr2qnWyDyxX_=)?_& zASC_ZSNN)a#wa`EpX#s2WjF+-!5>)YmcrRh3;( z*$VgzjGzD|s;n036Tez^gV zipOEr!9rB=FFRvhy$U%#ae5CYgmO>x@4*vI!wRxH8SuVJ!i`3+(iWFM0M!D1>` zHnfwpRwB%tJm{dcqHDMV&?m6m#!s;KjsIxTmWxDe#NLqEwv%mCuL?0KN%&Yf z)V}Ro?E4TxlQ5VNfeN${g$I8-2F#<+`z zC&!9^4N*RV0Jo6K^@jIvk4+fJ3+iM}!Yb+tfb1&GwM@hNN$D>CvodjsuX z#el5-3a6LTi@(FEjxEmi|l=Dms{Zm8g)wR(#ZYk;uB}-<53mC8RZ1J3R zK(`0_7X%|%^<nzgm5VVK}p% z)v)TjMOLn1X@2;YDPlW2NAr(SQf@Q(QxR_ap1m6< zrng8;r^8nfDnluxOY2jtTFp*-IkB{?tQ(G8`Kx^}N7A8qMcM=_{cjY}Eizi+J%ZB8 z$;exjF5VBE637Xtj}`y2Ge(^&mr$2QU{Nd_?MIz2Z+0Tl`=DEM@>u(mMy15QTb;SY z9_;Uc0fVKd;$L=#=wH&>$!cD7&sq_-w+Z3R2f`4Az$6Q+V8uGn{u*T_s$=#No4HwS zY)Y(toTY|4c)J|_vn}V!yR6u?6A%{dnaWL!{&Md&C6Ew2$ji!8D_Ra9g$R@(LFCKL6{F#YzxU32MF0DjFqF0%SgjxBHa1F1O-o5?JTxUS^*^`tWR4SbATfy5ovCwh zCSlHSO1we5Z?2U?Y4F1PCZ+>pEp|% z1>0{l0*^<5$F`N#)ToMVZH^TV`0Oxu;y?(FwPL9O)f@yr4cz{MuCaW~a~SU$HZzLT zh826wvC>T<4)f~E-;^~Wi#I|R+s=xlb}V;ZB=q=q@0iM{40MI4Jn2?Jb|HIRt@3gY zFSvq-1AA7X&TimP^_q~>gP;P8@CXz7~BC3k=*@6fDOEXzYE54OS7V#>RY%>oWYq+2a z^$pMJUfw$iAE@gVRZwXlF_o^G8_%8KVP`k6?vI7+3t_qMjC7FLlo!13xrF*o??nzz za&57LkmvynuD(5hWcXewfwf}WozVY4dNxET7*<%@F5ftW+)?EY-xq*^Gr6pW zn`>Y!Lxom)n*d&u7$H!XpF^sdgq(43C&#uqxR7XwaJhm_$fCPhg(c4RQjm^ktnsQWWAeAJOV@xHF1xm3@PMl_2#3X$UXC(x;bnO{yUp%DPEzJIDG^)i zmj}usfk3VMify6f*c}Jn&-=X95*W@5F3C{Aw|?Q>Dqe_IoKe=Ntom(>1DtALTgg@QV%z1k)X9PAbT-Jc^*G&%jGX@A)N&{XZeHMyN=aJ(rbCSK^+ z9`-ULsHVC<`t;v=xQ`YwUJ&D4xFN6&j(07phh)jDT(|!gCY_~_pR;2t7{z3z)G8@o zV7YC0y@b%)O^^k)J{HueBJv4DDr&7n1{&+vuR@;v)qz8P1t%(a1|W~PQ3a2o++b7w z(oG{Ev^kK;wrLmNq=H*SxN>$yr`LG~IVh91swAsA^!y*a9fhp@TC=PY1$+VICa$9! zIV-Q?+b!hT)3t)fU}hKM*q4z7T^wPz(;WhByW5yeCaA0$LKU8Oca)L=36;B0Q@%XDXk&pBjGXYK=&?? zR`ta{T6#9V(h~U;;s~#Ums!DsnieRr>t5h55|hrkWUq9OI+e;A{lP;+G;w#QF{>5&W>8)s9Os_jB){iZpHm*yOzXqjbwg`s_iLRWPa%FsQB5eBp=S3OICyi9$sX!pe^H?SalW^?iQHZ! zUu~_twEUumz#Mpn6i*VRu~ri^|1k1K(pN54jH_=!tu6Y>u4~rnUOuxZqlNvE=I?+aY$(Aa>1dVz*FK zDUqg!Zv(%hWhWPk)fqm(4nv*+VhwD>wn~2x>!4%ZN3p@{s%4~rSO*YmXd|{%@q<|L z5l1<>qhm)IW&hqb3+N}@6yOd%*`Wh zcxrN5Vy~Z*l9uc+J&fK~xFoFNYDBq}58K3_Izs23nSPvADV=R3)Z|#9qsvd7JGbW3 zyb@UDPxt+?bDnS|SH+@|M1Oaz`mo#8E_G*W?9tk9q#vNZj`I644F)bRmzR>_NGCV4 z=_~t0C$V2jY}Zu9mOr7i-iHHxOPe$}yWK|2+s0$>-%?^5k6QTTDD2x7!$G>Sjp*n7 zm7-xL*g}49>}1l0`mSCW0c69F=7vnLjhW+Ch=a^Sad-1k0in&J@*bx>2hJD`NCw&*;c~<)8PDEl^TE z!v4^9Y&u-v95v@6us)X$b>b(?<8KdKKIX^2LkYG@%HQdPCn@H)YAq zy{+_41u4_NT1TITzTr8-cDoYRmgU~!%@%s(o>NqF+YDHbe}RFuy?Lw)J17k6sC*}Q ztEcRWg_;ib0n^4FEV-{wWj}sDzA3CH^g|0PkZnJ%z)lI3UG%Zir*Wv~VgF8R10hB` zz`X4(`0V5gZUt%VYn9=74Ma$HUpTKx(yCB*0=0ZpKMtLC!~hD{{G@Q+z$zkl-Z*h> z4)v%x%nvlX4q@5i-aE<3ct7?)jnp|vl?`R7XZ$LIJ@I|QO9hyF2jdKLzv5qZ#?r|w zx4%Ejt>~W?%NX;ng~HHmg=ucP0s0Cr8De~<`LhQ?jL(KnvB$UVW)-M z5B%9JCvq=_ndA@!`=|hG0BIOdsU~>;*SDEaTmFHHXuE5CwG_D<;Vup}kc+6|`dU_n zGO$v`vN};Wp24Q|B+Pu<-2VcbDl|AMke%?Tr@UJ-{=il(_m%T46L^Ge&$}5JFh1AjhA%j4vK`1u{isH@+)uyY3q0Y z^Q_2~h|0=W7y&W3e~TrY?cyS#69IKKO-+jp&K>ji8uVWeSHiZsTHa$tR#vZ!e*Dhr z)3vZ7r^DXCwrj&)BDWv;Qr)U5ues_PVENHkB>0jpHLUJlU^K6Da_oTG5vw~xpJ#v$ zf15#kX~$V`$yG&mf+%uh{@l2+{!5ZQyrlpYt&R=%EL80@tXf%bMtoCdp~BuhwM88T?ohLp*9)b&iXhbBGoGz_!u9 zk}lX5{40lyscF@qzsDQ5iw(;UJ#Zgp@Mn0K3KgN^Uv|cV1P(a}A{Lkq|Mi={EGxcD zn%$p6e*qZ3MY!_yV*0DU(ppf3k>~IJ91?;EXrq*4kTVR(8g!WDZ#!1qX?g zC@X&227boa4MvUqmz|N6O3CmE4|29#M3YY2RHRcgs~CqH`=2-Nl4T5(&6%)LLxxuT z%g#s~${~q2UEE2$#fl$o>1-)8EgcPSo94UdiM6GbTdwEq%I0Jy%)Xs4`{2@{;$L>g z{0X4PYK1Suwop&uhm_eabo#A~5M;ID2fE-#m8*7P{S->Zj}^?YmWiZHD-%(-qq^XC zm#c~lnNddW1LShgE(E)Os0ChqftqjYS%IOK3U+D-WBX4bteb2wyPv=!|i zEN@HAER%Q_P3^-^I#;z{V}2QV)i0IyoyIjAItwC`{ZnKb+E_I&UpO?=^kqhFYoTEv;|N5f~h&MuKHCB z12T?cIAoL#G~A7)8lIJF@HVR?s0JAe*ouhJryh;%Qzi?gE+K>yRu#UAlu7jA=#%iC=`+^>Ps^x2= z>-J@2gdeb~Yo#w4BlpJR{Vyy5u4C|@$%eb}RkF0MHK;ta$#3Ed7XbslZ~^x`75}m` z4A(d$qX`G@8|zx(GiLY`+w9;JTq9xitT+wfhqm2FdOfQPoxYxT%LHoG;|R&BdNj71 zK#Eew-7llwuSW~|r*gvF2b7>CHS5zU)1-cxM8+dZFp=48cEHbeS4~Wxmt_hs*0;Kq zcPBC_@6=?d8EG&p3>BThh+inEWOt7EdAY@?NhZ~)n+A*G?_NJfE%P&6&35CMNnHEN z`ViZKzi=&N)l4vGyGw|HpXZAi$O#}98_+1+Xi(NCKRJB&QUjWofep*r{1xy~oQ78T zoDG>!-u8>ymHaY}6GSdHWEGSXrfNY!Lu(i?2RABH(9{EJWh0uH@T25aV^m#FDGK-; zr|paH1b(%=D&q+w{8D)%D}1Po!0(t>r7ScVQ&d$Xzo9mee5ztv~7pfNWa2q8*-$deqgErClU`RN(Rj@ZvxF@46 zWBpX@^~Wf2+h}~xL_dqJH@&$|IdQsebTw+$k4Mh0vA1WBUN6v`KOx?E8JcxL-EzXa zU9?G+Tz#ZM>s62#14*MlXV=4pldJ6l^j!|WyDq(RW_^4x-j1vAf{L|m$XI}w{)?@h zUJF0RuFq`g#w_R0V-U(Rw7p{VaI_`zK$9+RlmH_x!jG^RDJFs|7;s1@Dr*Qevtc_beeh=x`7dr0fc*g13h*;LC zEN*t-KK-PFlGXCv@LgZW=wK0fz|Kz8?95JwpOM#>^ImtBX`%MDo$U+p`7bux5Ie&R zS!>gqC4W}+-PS{o8!50;XpQ+cUdpQAh+9|rKnr_s{ z|Fs{1;#EhL7ze+}1ImfXXY5R(dUXp8@A!Rt*}P#x&_sXOdGPPti%Ok2(BNn8^~sbO zW|c8P*>c4|cOA{Nw-|Z_(XH_nGbM1VvywpTB!%sF)y_eL#B%*5UPE|{iB-bmoG(}e zztwQf&Q^?)A`@d31HHy6Y|UuvFnfG*gv~xw*(+3;F^X54poo|4Vims|GxEf4h?hp? z7{!ApDdOF9;%Otwc`bzaj~`ffnj*#BKZuj!=v_mtT%M{JF1l-H_rdtLb5Gmt8Au`L zgA~?JXT30ew@SEgFg<>zVmj>;o=L3#M5(Gta}=UL62&Spd7eVl>dG2Tx+)qE7bwIu zNu;R6=Zo;sY}aUfgDP&S*!d+a>@Sr0Sha47l_jYPK{GLEOjL;;=?bCExl~tm-}=e( z1*m`nsDKbd7YX@N(b*ibQpsy4H4;}f ze#*%_QppXcb>xwny&CXEc4Iyj>+v{j4J&_Kc>UZQo1LoQ#>tq|7!4Fbf8WM zo|pz`e>Nr;-6QE>;nc1iE-L87Wy|ka&RZPqrA#jG7fOkTy?9Vd?pjViq04wiEX^x( zdl`r;FU8E>JT&g=t>kRoJCWC$TjtpSq~Ov=2_mIGvghTSi0l)MuXRQ2;BxvHHV|ic zXmuy1^^tR;kP-81wD@jCRu7b;JN_Dn#Qvr2MgJk?3`640K8o8Efh_66edGOTzv9Da9xEaU(`INxt zu?*tksqx(E{l7ODAJaqAaS=u&)kw4-&c4AQYCQ2<45%-YzUB!1CfT zs%4l5X!#6a$MFo(G%xE3TEJ1D1@_Lj7dfA?mlu#wn?)?-E#iu-xsCc9jLTws(PaQ~ z8$7|;PUPN^F^+2Km-a3#ViDi!VTx2i>wq+gf4oG|2`X{0|I32K(6j; z1HpW;Rbl2k+iMLG>SC|&^YylgwF9{ypM;9d(`_PR5CYD-ft-NzAYgWT2&;-r^rFXq za_G@dxcS)QdS}_XIYdm|ON6g@JcxCZD;N^WI3g`?uYf|1p3J%II2dXZj*4SLq65UD z!93|%Ls*Yv5_6cs*ki(O{V1_MQ@Gh%3<37>Nr`o+Zttn)C5wp1r-`6p^9encT&G7* zu`!k1w9!yt3vNnm^w4O!7-u12ZElGjL%B9C(_@nYQ9KL|u=WCXHP==++Sq7Vw1Z8> zyOM}$WiMM92F4D9#(_U1F)iO&Ys!m6m>v*8Em>_i9t#TTF;J|z$aOW5Fw4J$d5PV_ zIg_UdyZBaO?}u|HeMX>J)85&Ow9o7-u}i|>Py!E^Fc>U~wsT%b4Yt?E-Ptw{q!TRS z?{Wn283`SSvIVkAEgp$Z@)C1IhzE(jxtzZfB)-_6L2vPXBxlfj6u=EZ z3fOrR=WqytOM)H5lG*kqwMio9!0_2oFx=AQAfiU2;L{d6V3LQh;+QWn z)huf#*+t^(ah@4WMb&WZ2I&0oBXkCu9YkmnNA;LZ0Q)AP<-5n=W~gWnF(oP5mx2?8 zgScVE4>pLI*P^{_e(x&jk%#S_G7i8wlp^LFgn_qY;AD5JgmB)ES%^~B24A4P`}9=U zFL)zrIbp|p>oo2hwf4HCjQIZkuZF6^&6qzuF`%wH< zDi)%m&DbPKoF2oTaDNQaZS;l2LdHeAi1;h)R;8{(qw`p3c#qYw_z`KX981SScf)uc zn?II)uIQE_yJaS6BGgaYkAvEgDH5AGjxB-Jua>4Pm9Q|9UvMh=Pf1 zG{w^azY#qVt?>13sT({=zFQ=B^muyr+P9d2-}+z%p0`g*yH1X_6Qd?^Y@Im?iR9vE zB+09pI5dfi6lFpp#gmXotAiSgYg9z|WGd2663eRU;H8H1$wVZbU?O8mw4+F=hCxuN z_9itP^y5yqQ>qw^;ek`Aq-RvfQS_a{E)x}nJS?3;c{pz+J65;$fy<;>Do-UH#LXe{ zq!<0-?-v=i5gkwqlQB+*8OGdxcUVItw!~cqG-gs?p}AsoUNe&@H6MND;qOx9O>1k^ z`DrRr+x3SejhOp4?9 z4SB0}++7YQxuZc(C;Ez`o_>FnbDlE>^lhR~;^*$I+R7N?pNvee3{{vt^H90UWF~8& zA?JH5vJCAo9)phShDwe+v6oZ#ZX$UhudrNa!Crj0qljPZ5UzS*)KY_~R#libi6--` z1NlBuEU4xv3Q`>+c~~IlpvJ6!==cE9WkR%f={7YL#eVlVQYFoXj$kHVBKV+jxh9&jxR1!*Y%Qzj92lzELSZ`1;ifCthMJo;2Q~K+%%V9wHsp|G zVQWWGxYHp-zf5>f#LzyDB6KdhNaS%tnOpW_CP<{t<&eH^F2t|JE8v~E-0?zAA;sb8 zNU`o`3PZ~TRQcUAU`EXY^S=Si1d6qpJW1Fx4~80!XV6O&%wt!4Md0a)3K%h;%hP5) zz$KF$h5G`Wq$#fBszr&oJBGZDnWD((2bgE1?L3_snozZ0fai%b70Ft1=-5tR^lSz( z>*?l#9W6OmeM8Lo<;?hCkHksq#2puc`3%bhWXnfPdTWiu>%iDlSt*V!24Q&zf}$F| z`5?FG-Qi3(+A($epovSq(o}F!Zg-SZ8D>V6a`YCx7op$JUZWGKF@bA;-6ACSTc(c4 zigVH?+wX~8wq9fH#BN(hTXn$cv>5E}ZzMY(`9%$d`Xeq#*D+r zF9n$8qQq+4WNav5`S}u?dXvMETLVY^h*Rko$;LEr)Yb~7RH)s)LIlkhN7AF;pY-S@ zYIbncB2Zcxi2!lM)ym+myd7MgODYi5hzx14VQa_rALXnmRcN z)3-3DbjgrI_;;g~NV0AZC((K(?=&T@gk;a&P9o_ly!^l&Zl>=D4DP3Z>rOiUU#}Nw ztSWJFJG~Bl`A@twi8OfBk_4-Ekm@`MU%~F=0anIdtXRc;Ifodpp$b!Vh&<^+Nvs<* zYR+*a@gtH5HzaX%xFX(Y6^DyGzo2|0SD}0-Mk!3ns%V;kZdwKFNl6S=7DcPLj$RY^ zc7h@qxtg8LQebG*u9Fqo?Utjx7+rva_>wdyvE*;W zL<)b%M%6U{|6S%JI;`P+yJ_@%YK4Lj_H5Aa=%E>tdx7O$ zos5@~)eqElHlQl1ypn`6Jf{#g`!!)S|9C`?(QoN7P*@hT!!-CBrATvj7C9SGw~1d{ zvswA?qYmQIaAY&YO`(r`&Dp$0I==(__iLWug>J;NaCa6hHuB4c!SuY%$62@~BggAD z^1wH9x|4oTUE!zDk2mt1+kO*t+yWU46oJ@wQ}-hqA8qkC6Yh8fe~o*XmBXDy<;6}F z)n(oD#fBtoD?5wz%bfhholP8Fo>LOPR#CuOn;C4f8DP)q3OMa1_eMeB`kD;ZR6XH2 zf$bw1^b<9=aP^tD0Ib~9S@hq+VSEuiZ)ol;c5UHJyuHVU{~g1H2qYNvq8nCrI72#v$(g7Gv>M-&v!m|#?Jyeg{ei1BXHdSXOX;}C*-^7`P?99 zk$w@mmm78&JAkDRWlZZOeF$_P;Vj%QImwj>GE?viGIMbUGV^j2iwEN8_MB|t6&#i9 zc0y-aGBaMH$xa?>(jP)+Bl6cBoXOZ&ADOk;L^>4Y1kALH1FvYvN;YVeBi1$x2kKcd}LN1HadS)wZ<{w!>K8(;SCUZmI2@9sU_ zghGaB|j98(VAV9(gb<(j1R8;bXGkjjSZk~m_qZb(0mG34C#CW%la z%V~~`?D+;6IeD9~K;>mmMt9)v3HK@{+zS&C_N&w$Ab4K9@h?e?`j*|QOHUAMzlFx2 z7oy`{tn!o%#OvQ8Qyu=6n0}b3z8A#ruZWua~#q`gjU8y2y zkWDULGO}xiK%d{@;vzcjjm8RfU$hU=w5PMJmF2=5(S`WN?h1c&ALsMeeQ>8z<<4?MRwo&^$8{V#6>&TTe~puj zNs%sM(hBEbb#XWHdl-t0auI3YM^`{u_R@3D`nWSQ1D$-t8fQmkc9U@cM4N^R(KplC zN9{8`i8+ z=$ye?nZYh1A>TPrgq&AiAVTpRDc%^OFuOV^uL;2f90rpxoEdF+97o{ck%~Ai{~&pZ zz|o@_3=pnIc+4~%0k|ufK~M4N5iY-#dyAZgwOc79mR9-e14Yi zj?^{@iLSr6DcU3qZBh$?~+Ck zSZf1=_&u?u)>U?-gNwJQ!LNxnDRdtP7j5$CdJNq^VzY=k#&d!`^jNqVzY#~#V6kclQ7tSsjC9f#bkJ%j~`OfPI5<=*45 z^;tMHXa;Ii`Z%%PBKk+`Fw;0=>wLL&yzD2*F8PszOw(#mqgP5cpPmq>ex$em;^~h_ zqwQ&t@DpOAMef8k`3czAvxLzOO*%bxx*$cANq$xx7yaP0H;>q0bwY1G0gcnY6Xqjs zw?-^f95aQ8>klMR)ZSXBcZfLtClMYZ_Z04QNIZ%4y?#rs&Pj+AJv55YLVp1f`A>`l zdd32t>^&ehrpUxR2jRs&A~flF~Jx@3$C zrYfhS-PLW1k+Y{A5uNfer#^re-As?_?y9}hNlrrjuRe`n|Zk z4Z{_g*u~arcU^)UJv)OORjlkP-kf2Fu5=dVv#;VR($B)POiST+%2Iz6F=J{oL!17w zt6cQac3@paXUZxU8Ew^0+;H~wayRiR=pE)>#!j4y-(VT zJkNu*F_TU4ulvH})c^Bx(;laWv&y;hG44Pyd!>aM=aQ zq|!@$pWYJvi4NSam{0;*{x8VSl9k1wBuo zbDjd0xx&pAbOmL)lqc3-MQkmqgFv8E`e-7)yCezij1g_)(=77Z#47&581XXES#Se_ z_Ge@ACzqwl=!UM^ySb37uyy#VBx;W1#mNl9lCKlSA9lpL>VviSVl>}$7XT9%w>PqBBS4B@rH zb?XS|wWn)G5i8Klu1*TQ>pGX@ND@SEUPs!$fO&X5x{4a2+BJWD{1G+-C*aF12yJ##hpFscLSbRH|!)8tq;Z zc(4Y80b<8_>$|@ziT>@|!=uK2Ft2@QW^qq3=@$EO`YqU)AESV)H@RxFp1Z`fZR#f4 zZg$0i8hN~{=52t@n!Abhw=qSD->URR zIw4b`ox;TIcGX@r<>i20OZ2@COb2Fsh`Ym%kaH3im)?QJlVD=*qK7Ik6YiV_bBCC}KUWy#EK|^3q;q5tgS0lBOwVnG zs_OFaQphE!7u|(=-4V+Q*9?bhc zT|?A{4*Ub)f!S^%{SS;m`e;6im}PUB;hz80&Tdm^FnNt?QwJq5or&3uAB4#v25&ijqnx454jtb@_^bCQuEH1 zY@~-$RcYD_uvu+#q%#LRMLFPpVT zBwbt_O+U#MQyD4EEJRA(k4qx<9dCN(6~fl@lZ3^P%Te^Uv`>60Vu}$&>*o;>AQB() z@U`k8gzNvJVSY+MG9CecdR4=H#LR!VfCGq_|C>g5sYN(R#OXgIv7^14n^@;!rjIBq zJqD5fKqI{Q!##Ce>2bt1ek;b55*$Z1esCZCRH__(%wusOnQ8P~!+g{#@9+df@k@>H zQnQ*(#G=<4;h|>LxQ9X7e7VC?kj5)2^Ree zxgBqifg!khQEvdPA*;yz3~UKDH1(|3)S1LK_m*r-5&DXfu3Zs`kNq`*!qOaJG$~QxFdU==JtZj7(9=iQ4JoH=KN26sQA|^Fkm;Xk>qedH9t<@U+53CbzjnV6B zt(Hk_+%z3)c@?d7=MrM8%rvs}T}Iq15C!uz0zXXmmLt_kCKl<4J+C=Z)%XV*t5P%~ zRE?8qMAXM!GD`mZ3^nF8WNq+f)R+rutg+UX!=q(XW7jy&_ORb5cnS&Pn4`0n}@c8eQ65B6nDxvB}*98{jhemDwWmErz+|zpN6n_$z9daXR&XR#;#7D&~GK|`k+<$QE<=WWcPsHSH&$jduF9Z{pSnR;o5(jfMP z=!n2&?jBr=swX5&k24KG|Ehy%Y3vi(75}TJxR!4jSRGvDzpT=tQ;029)5!X%u5^>w z=(>`P&2&d~U?5hV#q@sFna{*sUpPynhMG(THO_4>3+2C}H3C6L4#Faa^KUn+C4I)l=)DcuTwNeJQZC)S{DAMq`tZ|-lxQk9jDe+c2*xLT7Mri%U zsT>IBc!eMjnqhDDQoHUrV#|I0UskE)o5UU&^j}sa}nMYS5eN4&efRjhYI zHLe*WW%k@e*_F}X!2!yvCTK*cTEC-+h@GMl_<<~0zxhNwoS_j2lyWK?=?Eflfh3;1 zL>fx>Sjof^9ijA$V=oQvd^J@Q(FTOL*9MU>%QX?%u4(|dT-T?A$n`LL+x)ObioA0& z(?qJeGjerkorVRf(Ko@qyuQzk->8ZB8|GRk{<)d_6cHjB5u&zX&~e|Usragu(9H$O zb}WS7#HoHb_EE!nozXcTBQM4| zU+NK^isCX?$yU^H9iezjcJ=b!qN5w4(T^uJmbTtA+@O(tQDV9E%WE$3mWb^?YlP+^ z{oO(Izp4?^Mb!M>N}JK;1@bx+_4cu$-d^9(RHXB$D$B~%_+vGS=!EgTkwGQ;52>=$ z!|bQ_nMLK1d;~V>DJC|l6A5Fx1brmZLyjtSL?ES*S+>M7%9o5ocSVWo{e;@5=)}}VEC+ABeV#zj)>2jX@ra$ zYF%BM9#wIYxbz5h^-Vh|llBd2Ls`Nm_(OSeM~%>CBS(m+(M_{jM!fMilRs4q08tg+ zD{1WmLR+#fAhw{7k=53%kBFVv-^gl<*LHzm4-S!R{)O@t)mdf^5i3S$g!E6fgdMYE z%UPs1G#stTXbw3k2+C*i)e`6Tdih{A8r>k`{8WV?Uz`~XKc;a*b^TJcfApdLYC?ZC zUNQH>Orsu6mA41O;I27FLUWAdXNK;Uu}~+s=UMqc&XyYOuRKjYEO#|trR(H&o6Pjh z)D067dbPqxsQVitCIoCYzDd#=2x{5_X(O?%GmWe^|FR7Qo3@$Qy6Om;NZ5w$gtb88 z(>*-krJ?ZBFY$>|sJXT}ZZ-~socUYAqSVnMlQ7pqgawIORXtD}az<%i0fZ@6N06JV z_1=2Mrd_=dat>j0*jVr7@%@ov3)>5F@98?(I(}Lbw`+RHszg>T6+x`NB#F+Is1juA zTZzcNq9c?}=2i&=&dzWe(4b>F;|c3@Tf?-UafAg z3~ISYl-g1b)nRUXbp=#fX*7We5e(K>z2Gu|Z)!6bq2@beJ)E^e4KsF!aUL_hrZw~H zuxKh>J4OMO%$KVP@M&`eR5Cw`z{PDCj6v<^?lTnTN-QTCAlzzk=WJXH;`!Yauy~(` z)-jU~BQsbhIokA8*gLh@!|WoUV(MpA(Oj&{S*Xm6K>h~}ROq%B@G3-!-e*a%)(|Tr zcOa3;Dv@9cMk&mNNcg@oAxk=RcPhT8?>s zH0XqDMq9f7Fazfa>znbT zy10T^Q~Bwb=vm(!s7X($k82lS9P`9WFh3u$yS_9;c@t^4IK!s$aq$~ONS?u&bWLNo z)2#VDsAE|Ho`wq^*)U(S@euUgfI#E@kJfYp ze)JSke*}8D>h8GYGE*sRI{Ya5H#CQ8S=I706Kd{0!8nG~P*+dq8loiMg1Q(V?3>X@ zGF(xdl|nE{Q-lA1S@Hh~WxN$^EC}a?tF8S{Vs|55wXw;G&`BC-^3vTz^r0JAUxQK` zZdX*qQQX6(yK|H4gnq2O^zV>ZUp^#Af5bGiF}gzF333ik5mC+4UF>OQrmq%?h;Tef zL|r_#j>cn$Gg2t1%2~4fK zuMw_ddozAxb()AqPbKlLiJ4yPjp}Z&H@jG4acTK7Ph0gmyg4z@HUBNy$iAqa_}-p= zqVqPcqbI}$ztUK2@yW(%+7yJ>dr4IO+%r(Mcl&cgx-S%wCWAdah2H?rfDEbc&+4%!*IO$03TrAW^QRnfAql zTLL^(*Gn{Q$-X{;o*U!z@`zR#|3|F%bWjgi<$P_((MgLUQ@a%#a@z*?if@IXs8S1N z+Q54H4uIzf+}w)6in#ZX%jeV@=``x-B_dk$0=pkQ_m0IV+FH)Js4eQ&dih5w{N}(H zxCPQUwSCl$CBuZ%K~G;{YQrv5wGC`E8^BDc7}GMsA5X()f^E%obIy4@ z+a%2tT|Pyx$}5CRX5Ghqkgo20fn7v9u!m=BY?v4!i8Laf&({bS@#$Lmb03tQzgk^+ z-W^W(3FgGc%Ww`Zl`7}|LQ2Xdc9!0m8qplGw-&=86>LC^J`H*Q*+YUhy2BX8a^Aag8R8yT(d+;pBs? zqz7axKa*I0_YZ#TOr(|Z($6_IWwD@0>}aN0dzW$scF@<7oq;=##qEyV2!$PC-+i+r za@~+{q@$NR|7(Gr&g?A&38xs4iiLvNYi1fK7$%EavE#g}k%#FoQ_EN6XTEL3}yK!NU zwlauZzAHtFTX`9OAA;KG!Q<93K4_!(4+f5p?@6^zZM|GXes{KfqdPK`hF|sl)Etbf zMZFyL6&3L*$cKeQ(#Tya7L~N-Q&dv($D&jZDD;mt)@%VOYP&@g&FZ{p_=T$RTqJxt&iVT$IG z`CjN~vIm*^f$ADTCbXIE_-590xi z6=p7;pcA_ZI)_MSV2r{{--I~#RO-ZTA(Q=yZl=;(;QeprvB~!RVX|#YZ*iEk(f=fK7kE2N&wTQA2Z$}F9Fw4O-qoy`zPdJCxFsp zN=HH*7fMC}sxzRKPgf$2V#xIVF#wg;Gks=aH`5ZaeGM+)|zYpj+ zauz>(iQob3pA82996Fal50N;)Om`|RC2-_I1)TZJtGrlKk=wk0n0AY-j3Z9;@eWGs zfnatmQJCE47*dxG_A+WUnjEEg>4zB&d{GuE$<}2OHY(ck<#GnZ( z7zXUv^QLWUh;1%37dOjEKBi4;zN4P}KmTI`xCDl7rxXj{wnnogtQnkh1QRu>bMsj0~Bq@z}Ro*`OYzL15d9$7*dynFL4kK*M_Y&JYihJ`>!pfW^j8^16iOA6J zalWEaBKOu&^jz5ASEMJJBWYzs8x*I0DL6A8jlUj^U(wL`Z$9%CZ%+AC(&r@eEJA;^ z(df+I4)hf_^L((bJnQ2jYW&RIY4m8MaB~n zYXyBozc0AbMiP^^fEjwfa*&>#Q+&m(FU)iv*k=r$vr~OV_Di1{>Y`yZflD$N3>IFi z+0rq9HW@fVHP-C0VymxVYUnl=oIifc6r8AGhvVZ)O~ey%b2||fuy@nrzdOXGvE)w~ zGpYo2y&T7MPoJw25YWzT$hnPN&8(9#B*VK%iAIDMePr?%La(V*f0>^W0wipl0 z`JBW|k-iT4s_$IaS0671sVdiY-E(iW~bn3;U+Vdb-rip#I>Nh?-=sDD0d28YbjLEev91 z?ntr9Eqx6$HrGkW+b6#hNliJN9+U3L9HqAPHN=6Ri0uDBlGEE8(+ijka!DbP!J^M( zZis}=hCGaaM69P+*x47a|2_+kPmgG0a+ zq;&A9Brbg7ic)n_zj{2kWheucdDPY3A~nGx?U&`(E{Fuzn3^11P(YppLkRuv#TriY9S;8fT>t zMCoRw{6y>!-*6E%oo50qrvr>F&0wV7bjmPaeenLeEQ?eSh0}SS`i}JW*x~E48D^T2 z_L_m`!}f~Mol)@7oijMu{lt9Xq%d!1nE7WnsL+W|x=;QLg|>XKx=q7pYiVPAtBAOn zY-7kw*r@N$HvGk=nY?5_O<-Yp1uUD)bGl2@4B37F4?%JLGp74Ginr1uN+laayaSO| zGHHHpI6Jn6EX z1MpdM1|vk!HeYrHCZ55T^xn@rqrKr|ZhTF|B!GVWio5y&f z=xzsy=lIqUYYW-<^0~0lrHh~FvfuY(-j_g~>^^8H=}+A`36rQhk89`B5d;08mqIU_ zCs$tcVDjg_3ixCmhvBmG0Zxxsz_ma5me-fW?Ucb{rOy<(P4mq(xjstDX9h4BoUwpI zjN1ZWRfciRdWiG+zOJHOp0Bgs>If1!Fx)EQrEVh~17ou&Q8dj!pStrD?Pa^rOq;Ae z3qgz=ts_#e_+pb$cJwL4+KiFxo`viccL=*aUc-Ear2u(}Uc_^klG}zr=sDRahaF!z z>p!>%R)VH$tdFo=%*FIx3?h1_BsiyW_tEcCgmnX{e8|qqdk{qF7fp?OdJSgj^m_m4 z>m=H*)UXvkLWIZ|%)61;Yc1IItGi0Wv(1gYoF(^z`-_7!0l7ig9ni9`4{N)4Zf zlFPasMMlQrrBz$Q{HXj=O{JRX^cQYypr3?}O zf9v?UYlAQai?oJ^&K^OCPDO~WWN0!M+}uYh-!GGE1v2A$l`vo7`XysEz66%=D`BA` zQDRF7n{`8Cd0%qOctlvzEs51!&f61hmjjzvAh9LOdCIhtu!VOeR7%t!t0E8r`Z)smC7AaVp2g>=8`Q>Tp39Dt<%}Bym%@JQwE5p zPl%wgZZbWND3WsTd-^$w(;3{OZxC_&g+^$W8we0L{+7fP!L_rJu%G{t*l&VM{fe-U z-bgH@KeDED%I+&c%zsBji0Cg8WBg$X&$ndN9!GdG|+xu0^;)i#<@S9i0m61<xw*lEycJ?O{*g3{ubl&6_p)Ln!Zb!d*v>M~^ikAN3)oOEfb%gumYz};=TPrKg0UtD3Kj{N;2*++-uD@T%E;74ZW*u zA1Tr)6I*D(xo{nN(F0!@v=#;e`f4mLR(~U3xzHT+*A;J)#=nX}FF(o9`DOZf0?Q9+0k2lEzY2EbD+B!?;KG zV%Fm}g5g`)-d99qPtXyUZ^HZDu47ZS>%k^r7*y2gCt2bN%b!MAh$#Bq&sFSM&%y_Z zz;8;cguN)cfoG=v8$hhZfU?ybCYpS0b{B~oc>JEpM2be3#H|;optY|2(#-P8(4bwH zX>zm#diI(s;%ihy=@lALMPz)#J+a9s%&QS4K{;l zpRFU7Z02fOPsHHwh`=wDnEcC#M^fWoB935?Q3zMzwt;KB=N1qPj!EME7PFJswuRF; zK*Xt^G=jEkO=vHh-%k_P7>~KN4CT0ThKTB-q?W&7cg7TD5UYbbW)(4fc)E>8!(Q89 zxEC&(-D<8TI>WbMeIKyW>396;@QdNc5uLY#==q8X+@#dk-*Dqs0)uK&ghL*dsGY!Lv7rL(TwcJNGB4o26KMp7C;L6J# zf^#=1JK!Q$O6x>^*ujN5HXQ6DVz1h2EX{5!??h>FQ8OFuzmtc{DZom#5jp$Nxd7RV z!#g?C7f5zZfF@f;IP7ADO1nUy`>00B2)Asg^`Gl+*omA zB8dV#U@z=jK#P}Y5CZ5ce_PS%TiK_HjmXegXOa0W=Omkm*snAKbDV?LqXM6J_`Y{T zrxHYp$&BHA#uFnKv!wKzy*wqVRSxgC^7f*_ntv^c zk&XeLVr0313S!C`xsae?v;O>m?~0N1MHn?+3esbP1sGCpGe%#GtgAR>f{hcZv^hrj z_^+XE_%2M4%`qVHu35k1L{X ze1JU<@anX3>X!+xR>A8!eGv79yHy7Zki%>(VIr!p(_zd{$K%jDQVbPS53}E;97a0r zy%?m|Uhz`{^jmFX@g{(eX$BnOXb~|3hB_R9p%#d9bV^f+MqMAoPID~Y({?2Nm=HE93TDNv*zH-C&a_~|(ElNQU*p(3Jh zpe_G6Dj44%!G!c-##eOxkt=W}y^Wv#BP~A0D$K6y0fvJBhe$6vnKR%iVjl#!i#Yt; zDl8@!8gh{_J5WUZBoCp{Nb`N^wpE&AL12sP&y2gwv*xj6q*|P-KOqUF?v6`ni-Z$|G#6JBg zM3`>bl1Mwn9o5YnL@p7P@G&D4YQsetL4&Tgy>lbH#C==VY&Fq$dBnP@-r46 zHJ-r>=-9KopE=|#G=fh^Z1-7ifYXE>#4Uo9n#(yJDJuaaC9QL}NNVT<(Gd1&Dz5|fS>IBJAnAj7vMHu?hB z_fo{kz007rFX-UIg~=9$}S4)S~sFlwZ+#nysi06_>{WK6K zmnE^12xq)MIfG@2zFR&e;wvw){w5B{?wTCvWq7ATvjnGsRlAmfTmFC-Je>@KYlKOv zjs6+$C@qsSAZqQYM9smb?kwnz(-peb*3nggZvuj zF^xa5YKv!Acq6scRhUierXxCD<=`Gi#BaTH#P+NFmg)!*KljlQZ*l_-Z()P4L84ro zPGZ)3drV^Go zL1N$jYBs4ew5r2G(k5Ys))AlV8(&;TO_$OR*LnE9U1-SKi`kO3dl+a^Cpt^6Bay%6 zX@rlsah=`h5fN)ri0~Do-XNsazF`hlv+(qB_h$Vtgp9=%$8`*@-y=g)G5pBE^U^`M zPw)o1tZkN*cy@!|J-Ips>BoSITZpaje>np6xw=zZw26Te48l~uo+05=xCQ`<;-Pbp zmpJqr``pRjP>4P|iKr;1y9XK0B!=Du`Q;uW!^KeaayaA+!eYNC%!0=|^tkW`X?RCS zP?2cB@O-wFe8+(qTin9tZXE@Q@O1rp! z93NS0;+O%k+Q%~@?QD>KRNgdDCoXE-;l5(NgRJ+MDK7nvSL@U6aExy9CsN41gGTH& zOC#*XHo1;|P8z=RCGpwU9I4yf1?I3&Vhiqa1?2s0Xz_b#lD&18$A-8!NW=Meq%kH# z5@~OOJk&)_x8GrJ)JlzT6j{HszwRbt#~LE&xRA>qc-)mqk2OT!Ke$9A2`gDgSaosm z4^}%%Si(kWuh=cvPRz9ruAr_DEcd|vf>R$<2Vk8=rw8m)y5Jr z^tQ+Jz-~u8l77@`3`EOH*$8#^}F-XiWaB=&w*+VtF@xIgBlQ8@mhK-@8N04H8>a5`Ms-aJxko7Py^Mk9Y_uoDf z!TOF@y0%F~J;fZM=n402m#5HiuFD|J0Q%E&xkmi#FV;TgF144yX8`FdHSy#r9{|3z z&S3aKV-tU+CZ2vB>>|pohl5TnmLG=DaO&y%Sz9w+UgY7^U*rDbb&FLIzyAOoWaHE` zq}Zpe!X#z~2eCCUm5RW$>tJP^#MxqQ&xyqB?4>ZdM}iIeu=&JB_fgo`TomNOb51L~ z7)(K5h3Qw!r^Ir9g4?VqhTB|;SD3@aJmb7U;=4vNi0k_=2iuFl=iItApM$BIXl1ZH zDnC}6M~rE-!W@0hbIof6)*8oPu<-qhZB+dWpwoB;D~qpAF*t|7C39LDV zL6hikC)i!wIw>ohhOP473>$+NZy$|>{}^ABubHMKdGPmOTuFI~D`D*mShJg{Ft=au zv_Jk&RD$zMRKnO<3e(^vFQ2=;gm~c`1+;w>?5k#L`V;6}Ae}GgEA&4v&3>v&H)xd{ zGyvIh!)(GhtuilC#QPNCbM41+nUnN4(kWh|FfMWA>^3ia z#iL0k{%!MO4PkT~_isFwo{^~CH zeX11{p0=}jbxb#|VY*@5hdi~zs$&vUVtFU9mM6~44||R3ZoXTgm%QdC+WH#l1Rhhs zJFj^-eFM-8A);YDjj+W{Y!*K0zs8W;uj}hXVoF%6?YN0zBc+F)Uh)iEcT-JEDj+4_F$(^G_vkuxV?oAxlJXu{^b9%7bN@qj-g1l zQ~xU$;lNg69AKr#bR$dm1G@idFc3b=DB||vv8afL28|YTbQ-H2QLOfk+}wQ}k!0_= zMwVW3>?dOSJRPy-iOEqslvh1@^b z&9)GmvRudRcy01jS6XG=z-F(Itl5n_aqwG1HPsf9ZS&TI8xG{Ka*)(se?^2zym7No z<@mV+JDe%8-%5v+SHEW-PsEgslK9F##4w@BBQkq4k+kBhRUVIDZ=uH;;-ZsLb~%yT zwu?{?OI>lxHN>#BR6^wXT||BgLlZslI2hj-c~}~VOz#le4>lf0l-R`GX{y zcrg*{1p`TkCALapI|%#kC}EX9>`rC8!Tx$&qPEiSLOR`IjUX^$771uKuiXv^R`K_`~Ssza`tt-$DmhCgXoLlW}`D72%-2 zg}ze1M%c&iB^DEC!5PWVL-3g)I~&(KE_D^X>Z?KwXD6PL)%lGSwq~$}zT@f@3@`?7 zHrL~(6VY#jQDrZ&m77{wd+{!q9pBjm#X4=&El`?h*Af`ruHX9`xo8cvOsEx;x=LB z40qmeMiN3`^rRw^9~WlWm)scwXRvF_DT?D11pA!UVs7`s`GFtAg4 z|Cb#fW}%OrQ;298 zBlcE++9RA8B%-*2C6soasa$py>s`x6gF0dB!dNakBS_MbSJ4Y&Eu4B{MVKj%6MqWf zB)+T2bJ7b$tem1G`~X=oRpN*jSqa3dsXF4!-H-q=Bb@DRA@(qi%5#26?a3b@?kejU z4t7hjWMjiQ<}M`c_-swXOB5F(uH2FK9ub?qP-C6Mib`DNEh>X3SuBZ}l`V7RL&BQkBQ+k%U>YB(|-p#aC?=+d+N8Z(u~a@xh4HaFdjN4^<>* zss;=Fw-UyORH#XR8Cv7UQp`R}V8FP@uCkW|!$e!ZP{YBx=l-EM&Q=XIVY^>S_*Q4P zsaqZ9mwqp??4VEwG5i?U&Qc&(`44=PqHBy(AV?lMOeW5~?4=KJ}Jt;*?OsxbHs0AQtW~#hT0r zEidwGa<+?-QPAR=C}^1=C5c&~hFM6hT1bCOB}wKkGNv${$XVeUSw>_n4mF&~%Oi;q zRi%Vu1ow^52w-QcNvvN47jrCOf7O=Qo(P_8Akwo04P)`wX zR{kI`xzyO#nb)xb$;Q2>9h*y;BT?+u#aj)PUbcl~T|Q>h>vzJm`D2*g-&zuRJ40Pm zCz|pxd^lTliLnHT@1#OLARBWBDf@nJsIBOmV|1GQy@mwuch+PLJEDtgQ?+!JvH8?CgScmji4oZ>>sGI#`RET1E))3`XBi4I;%3L z5p&OwjqWp~$dP(nz^nCOZ}=>U&G{2*GwNH1u=jS)YCB=$ZM54*|2ftdHBYJ}*5?>D z>N&EIRUcWHy+k9*inH}O(@}pJve0U&6p06{#?D3!kdNvqlDPd(D1Hz11)5Tc9qC|2 zqxP0S0m`sb^n~=jSuXX?y$&^uPujBmt6O(V>1gTzAY!VG5uLL9>Mai{JkP0Ud=Y|KsDwlVaZZ6+c> z6g0MA_--us0;t;*|2*!utC+Ig!)smRyOVXhG!+VyZ^+D1ywXz4;1x_@n5}+yqjNMQ zQVx(rq&iA?H2Wpx9!7{l%u9?%dKVuiL4R=@|AG1SRdCIStC_mtkCM1!w%{zCnadDg z)lkf_KWQRPqJ9M)#k*94frDgV&VYtRqS++@hXMKxAE$L~Il8w<8EUcwHyFrwLoRKtz+fI--B8 zFoTyx#z5lQADTpYF+GMIB%vKjnHhspj=wLGn^|g!-1cF(1uur{V0wE)HGPR6olry! zN7$2_!up{{8d+XUX>D;;tEyIn-I{#}$le&AE!qs1e~PDwR2qs-%{aCAW-t>}OxPzB z6?!3SkD4KC178x>K-6l^wVB?}kgLuA5L;Cg#NkKUZubkb6YH9D*|s-_z3*OYtV#X4 zHn;_dg0~vs%K1f-3y3IJN+k8N;CJylhm{eLgTinMQ`&n;Y_N@ybrYRi^0Z_${%5n$ z*2v<=Fo%cPiu{(Gp&KN!!9gc-HZjamM7CmfH(iVq+Y06V$yq1zrQ^fG)inKEBhAjZJd%n8qMVpADa==87ZdB}t0b2bR#tSL zj_9E@zx#CL$hi%26z*pf@e_U9aHid7Kx8V5VD7=y##Uy88Fsb{+8~_;!A1!$5!03j zn*8}NGO{hwxgVw%p{xWgKn)#k%ZZ(83oC6gQL&~oJ~D<1nu=itJtkZ_+QZ#s&|)RUK__lFq8y0S`7ZeMF z9TXcPcBLb>SR?9-@!9i#n%H7bqOtI1=iBe{?e(4W=bU>y&TnRRc6N8Re!E{4IJH)X z*4Kn7_2{c81lr=bM4xPSl++3JWgQmU z>XsN4%lX5+W+R_#G%9$CV;8-G@zK=v)>NW9*F~c{uMSY5b(I*Uc(Z!SWLw}Ehsg%U zNOnp~NiY69_dG_Oez^T6Znyj4=!bEyjiCT~BnCC~PLOFIW5~~Ty^&@zOs099ak5sD z`* zP^o8MRJwJ#3=Hl|Gq!nsk@@)ynRWsHgUnAEtq{jSEDJy0bHaC3VEPq?BK6$_Z zuhlXz@NcK)&^(n^ccWqv$lcC>u1cAEyh)gtisgL*TU3+~9m{qyMmM-)=Io&usIzod z4|j5Kh{9P+VD>=CShfSviY`ei)#Q!=1;IgCut8qmi9+Yxp<_UD&!rkGc)S z<%^@khpQiK)8L?s|FP90%L-@Bh_Oj2Nh6a|goP?YF+9Po5(}*W-$cnuwHl|PFEN_ak4a$+lN=a`&DNJMKFmg|H{Rjx;+3K>7oIE!wDTQ^u(390e&0^LPM z)L(mjs1kO?OZXj&MpKJ`;u!X?nCbsi!ozsU&gK7QY+25heO=v28opVzGN(T~P1sAHFg=4dxL%HI_ILIHM$fVCIN}jkM^$|)&^Zl5 zLvA|TLF5_dMymE^s&UEc82Z!f75BbuiErkt*JvCBxtJ*=CtR&%F`Vb_$|9b-#Fx%Z zU{HvQ86*xT-;GvSdMvGHaZnt1PC?r(t4Etavk}yZbsWLf$7ZY1Q6pFjz)vmndxcxx zDECgluJ$4R#P8X?SCULis2m~DwK`XO21@Sz`hPwgSzqB#oIagh#o2GYbrx1PlHLg} zpPa+_JU?RKO&cj$GE)NfUmuU<3Gs!Ve>jb@@t&@3Hw|2oR z{L|!k&eZ0o&#X*EwPvW+Nl+WP0is9Yk&>~=5?xNqxL?JWtZI*(?DJb!f&1x3nY$uc zqI;f>#0E(BCza27in`e~7NR?%^BH$LNH%uC6?!xWD_@$ai@MXAyM?i^{lloH0x)cw zENj=?#T05sQ8T|ginrKCJM^H1QVZ(L1bM4MJxf!VF`DM3*`v8U9*HKy&X1=3&qoUV zn>~;?2B+a}V`!`+jp6){_c1!#LR_Pev|rZ!9;XS1AMS;qF+f+3yv4n{qw989spH z(pZrkG>%JtQ>o`LfpJdt#Zjej+^J?9bSYB`7g}1wNFAZuILVR?3>KushF|;hMMXFk zbR<>{ajm-aIOBGP8y)Ml`*;YM`|1>9!7aj68ge>%324x1J=zFHr&1QVa^T@$26u9K?f)mm-$(SPYuRn6vr=>}LN=Z%dompstdAW!`@#M$d z+In_e>IZoAu3dUId1Zfko?^rXQ#-h7WmU_FREkHL} zvI>a)qO14lq{&J3TXAv_2x;I`>WfA>qLalc^gN6tA!CAM2Eh{~c5P1g30!S*XFbS< z&G`yFK2lFN#@hqQv9q^fp3EmoZ0=T|Th&$O&`zY;L$8TkV-~*Li-v;NBndxj-r*uI z39`CS#2Wtd>ewB4Y;uxM1A{BX&t$6P9!#WeIM3Qu-S7_`(z7sQdrjg=Zv2hWTS59r z*JkEJ$0kjjSnnOkQ@BPpfJ>Z9SX_UhFj%*e8N8>v;td%SsHt`Tj;En*qX+SZ^Y7%^ z%|cnxgOKa5vE5dp^F4*Yx3jHY!~{;vbk)bR4~o*?tQiO1!amzo-!mml<|!7~GO#`5 zO(tLLK|0mTq~H#awBF4LGBU`-j|%XAmL0)yio`y`NSwl%kF_M44_x0XHAkRE0pi*a zpbhvRR|&K#N(gi6Zd=%e~KXEm+heJo$H%;mQc{);& zHI1vVbtHftWX)gLK|M-2?9vfx5;|Ie-E@lO%RdV6)-@a+HJxVOwSzmVrSHEX*c$4P z4z<(C+1ID@RBQew)K_Wb8A-_0E1k!V)$h6L)O5L@{xC2=YTI|uLXRqqCg!rK#s>4J z{1xaVOLy7O(sXL|=hAs9n`=36IMBTvC8Y$SU(&mp{u8U+8T^U;?SRW%CT<2b+e;zt zc#M0764kD_%j3-A84^4ERXc+xHRozQU=F8oe1T}knH){K#?knhG)vPdz=7-aVkS^E zQ({+Z-|p(J_T;OZYM%D9DB-TNxaz<=9PRd{yU;MRk%K}_;RCp&!;n(h)7=6*Jg6Xw zW8KxE;rMGkOEVwC_}IAUOi;|wn*&y($II~Nrg$FzEzoWMz(nmJdw*vu7N+KvRs^m2 z`PN=`$G<72FM2oQqs+a2sCyH*C?8Ud8i5{uHk*5Rn2`r0J#lvfQ#{+pitD?$04cVr z$a*ezeiPpr88nSeJvn=N2G#3D8Qd>pbp&|BjSO1Ld!hgXGC9y$M?;*RIxdkkpMXI1 z!q*-J9Ji1_ADB9oB0-Dqxzy;T1lZzTinLuaN&zDG)&ot=M5jWH!sUG$pOOF4!|s#7 z+ai-Epu0mH!T>%VK`PthFCimSVvRRysE4{lhkay#8+^zlb1#nZP*xHeD(>n|al&;tWj0|(uXpTi2PFV8i5xcW6 z>kLZ@lx)4HPdV+V(cGvtoUPgvl8F5$!DRQ~0O^ zAvrH@A+P2oHyNE>)R7nCrLN|Op1`Sv5__Zag~EGE3pLB7mT+yAmvB91$|XTEbF9p^ z>zY?n$h+hv6vvW9JmEDH^<3sqyGZWD6!5on88lx^Yw7Nbxljk4Oix%WZ=gQ$!i%;R z^MT*i`7&6%SU!hfwlO(8d+zHcb8vfR74A}sv*EUj^xRE8@#V!hKgspa;{#rqX*B(@ zxrn{uzq4e~$FIDEGa%0!c&k0wXrs)wVu?H`yOLWH(W!RbAXnU}+||yoT+4RJ+&=c+ zO??f}^an{#d9UIgzslVWDPXp<=_+{}t%++$s6iuw*$6TQ)7{b_IuUpyvi}+!R<& zzJH{k#b0pLIh*G0;n`eF>cfFa*|f#g{-n1$xCJC?p*UM&k3QW{M8Ze{ZE0B39x7iY6zo`6@Pk%MxV*NS^3%SV zR&eKbk&oWaV5+D1ZJ5>)3;Yef^Cvc`riFTuW>A7!S=E}>eQ@~S`Iir@z{s48eSkP{ z8*)T1!p`tn=kS~bpO)$Hw5zr$#OIKO;}rDT&3aT=U#&(C8o*Tk4!`8kYLq>2QWv~V zW+Sd)E#ILvfkdED_zVT~!tdEQ*c?LE%f|+SW_OTo{xSLcJDA%Ok%pym7spR}5!+m{ z@Wn>0ItUu$B%iaG!O_iFMn0RgjbLW3yq;%Uy*|9lJ(uW}jWil-JgDM@g{Axz=+@#Q zfL^#ZSE5&i9wgi%jY6^q2n5=-rua@`M=v;584?HK=Mlk zyASu(vo?pswUQ--t)cw2UCWg%iW525hesI-IA??k+<8tb945M@aM_Jkak;Le$zJF> zuK$}f8BBPE{+fnVHV44Z|0RbBcTQNEYcyT(42QSkEc~}X_i&adVg@N7HD_|H);=gN z_%IN|=P(?KV1U#A73jXs(#u(Zw4R*SZ9TW+w>%lFYvj`c@B1Whg926`)=M@8k4;*9 z;08+;JyGoFqpk+WD^z@)H&8H)-oR6+JgEZjy88&u+Up}II(bgyH3pA$au(CNxkMyo z1l6Ts7kSvqaLwcIe=)gE5WSH~F<~Q5``}$h4?>Ge6w;ojjMNblH{qs?zP2JqZLDC* zeLM`q`DjUT-}edO54}%}Scct{3Hn(q8(aJ$U=w~uadZ$6V@VX4kWusLB9^HN3G$GTs99CbW}_|Y6_nDbi8n1jgLCSor+WQ zw9cjFh?)zS*4}(RZM>T73zhk_mUc6rn-#sp7ouPI2z$?Vo4HHh=RxFV;*VEI_6H$j zv*gBt;hMzE(1!oYECn1^^x5VSDm!OQ{-d&9B5 zItfl_;O_^&Zt%4Shg{!A&@+~H)-G)jO!+m0c%0NzotUk>cvf`rgQ~5V!6jQMgXxWwqhv+aYe23vNBa@lht)D~_fM z7ZAIxVN{*Hjhl37r5{{+<11XId|m-R-%g+nG}%tWj<(x*@vh$wS=(_Pe(-k6$5@4a zzLe3~b$HbZNplQ-*-rD76hA+8=*hgm09)w3gLH2U^J@($J9y>`Z7=V_I=u78Mf{yS z;lP{#7`&5)YKwMqADw{!*u9gMv1*Y+xbHB)uTdg zS&Q|7(84It6>R0(;r8_mRHtz20l^M1dl$(`D+RcZcM$B|MGg1Gsz6+k+Qpq<>=Oj< zchMq?ZxK)GsxQ&n!Lyve7GRbl59ruV-mzHeMwc!c&89rVyf5?*0;7MU=A%VaPcACT zj3yCa4b65_uXLw*klOCoJA$A~&mbExHw!X`CF|t0x+Mj7UOFgi?K&LGTW>b3>B(ng z8FO!ODggU-^Bi<}Kpc1u68FTJ)9)u2h5Q_86R@~}Ml%IldZ%{A!T2R>)qI6lq` zYNU*wd!kyZT~03;=*P5Zh6H!k4jo?9*FmGn{oF7d{QU|N_M$C!_mC~mQ2`FW3Uo{G z;q}D5xOM2gmpq@8D>!(vKf$z1bvC9bb>v)Q==4`2NTc=9QMgE z_t4m)kXOZPUBALjfH#;6F%UHgsb@EVhv-sP0&n)khEF`Zf7AbV(US0b8DmJz4{9uU zD>6R_>+C*WXZuDNFgK@J2Zkc={oLdZk--qNpXz%|8Oko-&t*q^O8`CtQ5Mt?%J0V}ilDHb{6H-24Df$qItO?*J{|3{Yr^HE5t)uoI-r zkn4PhV0AcreGJYVklb03Tkifc{QaU+y`JKP#G<{C!Q6GtOUC#~MDKEtr+vI_2vlbV zw}teBR3{cA04;`gZunV=4M%D-f}23?K{DgIB3Erss53YmBEbC+S7`1S0_O9A?O_Z8 ztkl|I2!1xnmXbfdO6Q< z?QczZ2rN7-u}QGQo?vyH3G5yMUPmNHc!!D*ZgPZYdHWOs*g=+kMj`5AI<+2V9U)ys z$V`njw-|mrLOsSiMgC2B2*k7t#ka>&Lz+TF32Fal2^VPG1d>W|djdh+8Z04iuT{XT zo-$aA1Wf*Sg>JS)Pd78{%_B>><`}$Gvy`k`rjV@5U|%VnG=0%4RGky!M+~q5hp5no zU|S{^tzXgokYSU1KG}d%;v!s0#VLvYuRwRvG7K(G33Y*SoL$rBc94ntE8PVwr~3m9 zi*>g%vA2iPGP$|?qV~oCYY*n)u*Rm9q4u&sVs8hZmD6x{*0xam9JZXd(Un~}TC^>+ z3B;F^*X$1=%PfV(KROH+mrJbF%F203CiliqQp-YZ;cYn;f@uW@;=d)p0sdJbXZ=E- z0IB>pQ{O_|b%{+g4A-#(!{EmX$pzL|ke*$No^P@U@Wo5ID|sm#DTDEqc!Z!!C9y^+ z^sn&)j7nPZ*`Scpk1>)JWOWD=wz7U#7^;8OGc*P7D(Z(bfYq!4u!2d8yH~(YFj1_TJ%FEsT3t4Vhb}X!+p} znf|;+qTRybsO7qS`;T+6YnQNqM9Xfx*K>bjSky7@nzfB0Ap3n-OV~X+T-Yo5B_mv2 zq<`Af^WlT#$Efn`SrOhk=QwXvC%hsc<^)biT#r*K*ZQHhOblL7Qx?cDF#y#i0^(!JWGID0D%-ADy z&HXz63H&qS9T;9w1{4ei=pc94yi(kVo{`A}4~aO(trjVXE8;UnHO0 z7Ub*QfH!AIDBrTV|^^3*~@0Q((X$_n4eBHSbSof4KBsWGvB@Gs)R%7jm zTuj-g+~SI4QUiB7!@AP0s_*JxM9a1l5W7@+jty^f(1}pCp((6~uSTU#5bfGVLvo5T zP9?ZrSl`U{Q}GKS3;1_a`90C+g!rbXz1o?LUzmS66UO(i$R|7}@Gp%2bZiIV{;yNw8wl4w zavwNFs$e27#Q%)hpG5IzOv)4=UPQS6E|>qrXayDi7b*K+F))!Lkq34NF16{s+{o97Q596lF>+7|j1zZT_U^ z0YdDbj)IVae`Ei3?gghzWh?`SK=?Niz{P(u_y$7p*ADwXQT`?w7>es}PZSH#L_sJP zO=XCytvANvy5$N#) zbqx1KgbmIX6lpO(ZM-FT>)+N7hUqB6MKBVYC>g+;^ z`U0+1xC!CQ<(pl@kQmJ(;}7b$_^q9b`^>iI5@Jd3fPIrtiP03Y=U-e_>B7iz=X zQk-n1U9S=0BId=5#NhVyGrRb~qUj|jWwgZlhT^}g1PW;Q&p!JNnSLt<4g|ylliJS% zjF-wE#EaZGgD4A*_Ge*OW>5-Hvu;`-WjueL?4)mzf@&vlo#Y(-oPGW}xh>|%^$iF= ze}T@{l@-&=2W<4z@a;*0$ywL|G&nmbFrb(1&*LtNn2xUCuMn5Th3EO2b-151^rXn` zex;wujXaf8eg5J4!s2cb)T9RI=*f4GDrn_5xKEmxsR z%r}J|wdM#VnX3sthOU2=?U()Gkctiqr!9G7xR75yXOy9pB1AE^h>9WA9!6EVJ`U+Z z!yFIL0hGWff~%dWa;<|}&*WKG1O%El@zj%KUGl}D;0YU-3r!+P9mb^1^EIIHJN$?^ zg3uFwy!~#R$CB^PI8AM1JyDjw$xEq3hG8~gFB97F)ExX~hxna)aQ^Lxcqkd`QBx%K zEZA}~Jx@>wh9lC+Ltt|CC-a$NPZg?Jr9ETMBGuNsA-$Fypi zomArXS(;pfg8O<6D0SvYl^wJgyvX&p!6?x6@nFM3K?ulGDIxu)DouYf8jzpN zriBcM68)$)ZjlwbF7)UZVk7SFg!hVLM7qleyT29Wf_XaMO}&9e43UVRdg37>Yx_arE4WYgRzUlP0(eoNZJ;#N zFJ&lwQBWZgtQ}_pMD&=?t)kxsShk9$#PtBXK$`Zq9uj`ws4Q;=t8B7sA&BrG2C8_q z(bOCU`%S0IHBs{=M?_#KmG(hVWE6}M>|Iyy*~3zBlk$HJ<`K*z?v^?ldwZ?mrT(mP zETG%^7OR=ob$E5;o*V`+2Fxj(S=6a%&}N7l60JmLZ{m21+_C=}0cP1SGlmsrfx(}= z#L#3}Oo1RC9niBHE6+2)dBAzACQZPB6iyiWz5uc?akH0cy?!SE@yYOsT*{R-Fc$q7 z1Trmv^8k4G7M^e#7o3>xmIHF`p7V(lbN1kYTOQF~V!5zoG6L@<0$6*06S)gsCx!IR zvf3G9mT8Tt_cWkP=SLRYtW$C8Z&uWuxr~Op%%{e6Rr;lagx)0e^muXk1I`Ag?dPap z(X}3Bz3!f0M9^<`C4MVnkgh#He>}K^%A#Ikc7Nf=%APQ2+!QgTXQ&y5heaQ@)p@ z3F~pM77sPIH;w$jF$*K<0x37PL6r{wKK)I%+Zu)4&MB6LQ8g7#pOCq0ga$ZdtBk073EU3)>w?V9(--V_?G~zpYJSP-`36;Dy)M@Y*G6Gz z+`v`<1QJ)>e2y5@%d{ub(>)s-p| zI4spDuxDbJSlJ~c&DCgNKb$Y`Xi;`1H$t1+D;Da!gZ1JcP|awvzPS-36FkFVR;J`W zG$^%1=>vcT8UG$ybqDlws#WT+$7X_W(0qxg^b4t1C#B0m*~<9Jz-*d;{Y74F13z8~ zODO!behJ*lOUv*^J(ZPa#;pfO!kIhR4oi}o@lqej#dLJc4qzKoUrYtE+;!3!L|z%K%_EF<5d!Sy~6y%qN>SBnu#rG?P1^ z94nSb$BuNvOc(UZ{y1tA45k+hGA0-NQ}}y{kOT6oBf+!LP+YBb(|v9)A*1j}Y56WL zzb-3kFL0BnOMO#tptb7Ql(a*&7)>T0`XH;fai#{mwho*Pu;({uMCuYB?_c7QWMD4h zV{KtG)%c-9}sj4<)WkN0&H{CIt?zGyF!3YziX&!d#biB^uXm6n;=zI>6oi!k|v|H z444|+f@7kNj>ozYAv%Zc^1ED4^UA7m==`W8MH)d5GTjBB$(q1Cc*jZWu-l9Bm=-F6 zs(hRJ!QvHr4dN3*ZudDiO^N}F#0$BJu)~*J3!zK*iu&PRoXz16nyi^~P9glO=x9KA z5_${5P18_T(}*uQ-2<&zD12TAvii^HTblx}j)}c}^rlUBC%TZND2)n+RZR502H1Hq za6?#k=rQTwGxQ90;cXQY4;)Ip3nkD}*~`(xTRH zjaA`~nnP_EL!vL6;#LOljF0e2sx*Kdz17IRC=SaIZT)E^ys~FBYoWLn?{ItjxgqzE z47;vPLtff$Jh^^6^{*MCj>=v82O52{Y)GPGq-~q^HX#}=AufeQ-;fO?K2Aw3_ve@P zruJ`ro1&eRsWo8;H==7hf=7&wh_XCD?G7PiVy(?7OIF+Vjc$oMQeK!DC(3|m{Co)^ z4yZ-G&`0dDtCG?BnL=GxCaZ%bA^q&FDQ+^-XsGNFC#H!arElW%>BWPV3c8pm@^T@; zzM44SVI|j+W4F9`1f$X9h7QxI6xnvekx1mOSbc{|>d`9AW^@km^W$=|8{XMR?HPP= zkL%n4QyW+Ofr8`0mbNrrIsAZmq`C9%phLdPqdbn&tKl!P($O!>hnCg%%?sa0FU8f4 zpq`z)-quqo+QrczuenmUQb-@nIe8U*eyLI(z3kl=(beN}L{)vo^HLlA11cb|IVd@w<_wVf z)n{il=2>8ZF@SYL=N7Gq7z&LZe9F>N7NNZS?wx6TQ*wr}`jr4+Ua7Gpq>Y+K?y#@o z$Snt)MCC#$2jTr)AacK>NhFzuSEAY25b{lSJWR3^0Dm{G#lJ zK>Ct~H9W1X4q^rLPNY|TEraY4y&WU48GN3<#8r}mEC}zYBtdG-0w*OAPaQ83E^Lm* zM+iu#E>hrXbtL@G)|beHMEhj-g)ErPmj=7@AhM;(dg^wKNTAX3>XBzSd%z}a0>k#n z^)n<{_Ua^_q{q%D$RXV3lgwaG@S*GT+5pyEf5@fw*Qfx%^d~pVpMIqMo-FZzUxOW) zsq&Bf^8L|3L!)}(l$$R8^c|9XwkUTKeNpx;B*UOw-355;H#7LmmjAGF=8pay3>G?g zD&l;tJk(WW;7|B@0$qhNei*Wwu*E`vb&XH|uQng`jtj>)WId&iY+6475sfudc&q+3)8)f$Xx z3GPWNWg)p3Y=4qQW(a#{aRQsn7zlX9pYX-1UZnDU%II6FYLBD!hZ`oChFo|ZwOu&& zxDgZvgE2{%ABeyg9K0I$`x%rpM z$(}M8{qU~x{Zso~YrVFUlbwVu;YV9=<}%hW5kg7cgB5*q--B(4F1#cB=3ZCUo}Xfh zHuuD@I}agmYrnLgOj9zK+l9I%HCQZ|Dsn&}$5^&M!$aGThpAYj$SvtDYf*&Rlh3{Z zlGWF>KQZ=Izk>Fg99@>7RuPo@uGMnXmTjQl6FOzeMVfiepqFzB49cETUy{{VrUmVP zxGhX@TbX4yHOOmE2I(B7ejAiI8uLU~ok(s4q*V{sx{j9;!M8}pixD$6>kVpxY|sEWilo!(cW4SKEL*c>|VX* z16AzQR8Gor6PL7{j1TmcLd>KCHhaKFg^E4BS1&n1sP8TSHmC6eKyQXbkvITY=6feu8h=%`-o!mBtzNoH(@JMPtZR*VI5NA= zn4`*48ULA(@mUe?Ss8Q#9=8NzN2afAPp(nZoTfnOfnJ+HfDD)dLZ&aN3OeO$k=avUx85gme#Zjd*n zgBbeQX5|cf)JSqc{lEgTX+K&@1tbx+2Fic9YyWV+mDgF25E5};)o9`H)Gu2-jBPKQQ_)^kW*X|O zsG?|xX$OpYMqRva&Ym%g}f!g?K$A|p-7JYm5g@|SafiC^a-kIa>Xk(v?AU&BY<6sk`*by8l7qN`)xzwb5{n@PPEZx;_GWqm6feIgC|z zsFmaAI491@d?XeL5CCR9x~*zuD9+IqONdHXRZ z4^lm@=hg^2p8d4k<`%>2C|R0esak{DAis;MOoLjlTlp+jgIX)oujEG@Px5!;r^zJy z%$Xiq^mD34J}6Y}o6NTUn_Na+&{&SUMW9Fpk_~=P2Z5kp0G8lz+!AX1{HWK)ri|QT zIbLbVSFSf80fo`JYo^~>%)|6DdYfpc>xi2C+%*;7bh*!hdj@J|cZz8`S@kmJk za=f*ruf#mnity{|-0gFRs0Wnmsr!c(Te22gf3^!XKR|})Dk{ud+6(LBfq+0>2l%GgyUG^_U1_PWz&YD&)K zdh7o8xY~yk#ltnc6>jL9teP1shF01^*g8SOi)@;#dZ5Rns%P&nU!ed01$YaRX8z5e zkAWTFe?NRQZqk5)BK^7g9``{Npn+H`DS-z9ib=etrw6ENWdEM&x}G|O2&EE{k~|eu zxlfC4(!$oJl#~RG&Xy%jjF(qBGd(6xXHJLy1*Cn2f$p}gr{k7etv#fDhioV9AwrvU z&flcI*>Ta+dOfJ;)>>0}-STc3@RIG~Kn_YOd6MdV(|&RX@cX*S$~)D}*)2oqnW#7?ZV;jB)s)Y)$5X^$gwV4Gd! z*#g*H9!0rLlE}B9pe+xMqgbuGW2sL;WJu1ckm+I&ab5$497m9DC7j1a#kod3Di_@{ z@26pTxLvoP2;9&{7os2B5@^hm1FGd?=Z#D}s1l^U-JpAVEFSF{$c8(2cQbD}m|Ook zitKv$#K2o_OU7qqyMgzv$I@ZlBA@eF4g|Q_4(njf>hdSgkTX*s7{Tj-H2;EmmsvOV z+B)URVkM6}(^Us0`hHamVaHI+LPc^Tj(B0hn2Db|hIt^~Vp$X@?Qok{&b)Ry?0%V4 z|5MDA0cqfv&v``0Z7To@H6<;Lv4M)b!?{9B1$Jnd*`yIiNj-Jpk)6uwnGEFEl@35H zSQCAQ*+(c-kj4TDp29(w7G<|~Htf7rP7>P*RuS2LT`$^0ldG_0Q&PGa(+XP!Wr2vo zTfPg7Dk4No?I3=by)fYm;wHDPaZfFT$qtcuyy5^o(Ja-@PPQ6a+vF~~`*z&23$%wX zdlW6)Qja-x==>g({@_QF29;65)&XD=PsIr$Y5(vnI34C2vs`1SGu3x?DBvtH!PO!* z=XFYRT}DIT2s48sH#J8b%jw?!!Pk{i&MKaA2;y^u%Y`&a0WR!NK)6(~342KEEmYH7 zYPy*q9XBT>gyC|?Om)1Kz=m)~w9sX#>Q{lMlIB|V|qF+|h;rV&5W?7$EcjrpBqxz?P;u9paifj>Ho zl~)pqj#pt+0!_IXVLX@_onY}!r(sbD{Xlu(=JZAH1K)(gV!N{9lYafHE>?mRl{+zA zw?E$Uckt9c1-QC;1~+5MEFR!TfJuZk=Lo#fOp2pHIvyA=bpg-94|}^1qks%Crt!R@ z#^b5vC~*HL7lC~^IumAImGM?VQ{bblDypX7^iiFR8W2TT3wIQQm0Pla=3ZU@SPIkV z)M)JC%@|?kT=megTY>Te(i~%6nBu)aF@k^)YE`FKg7dTyZ@MJOU)ccZ2*JT+PoW8^ zZyh6lj!MP4<#kZCJ7m?|g$Gzxj8xP=cuWsFzt0fb!;A#{NRY#B$b+*+mx3-4WpCDt znH{G=Urc}Ub3Ym3;PCLiqj>2E70Bj6RE<7=fygmoZy4D#*36;9ec-(u`4t>QoHw9Z zlOzS+I3h$sfU82nlVc9D+@#2lga9}kO^{#3}sfU}G{grn>aI|w? zFrd_UkxNF{GmDa9+8#d!NG9g-lwtYG>0-*^8MAAplsf+D`hf)aqIbLid7?Ai0ok|u z2}MJfftlKzl!#z8-b1Q^q;l!*U?cLk53F%lpIVFVZ zvcnOIBFIq@zwI3$PM`lFHj9y2jLy_vUWQqtD_bh2hD_JHlFz7Fh-w zx;RyBVY0|gpVd?RDJS&pK^YIgx@9;8h-$$K`eHIw8Iwp7lP_5%^nj%SK3h0p5yTMTo z*gtI@f1w5tK-;d4xp3|Nl74=@K3G%Uc_kWZMRYFyxLTayJa9KyHQd^}z>ntf<$*k) z|Jk>za{FG9(|_N#-Ajz&@uD%ZFAuPzy}n-^-p>HI(Oy46nmFZ7_NI7D|Lp7B?2Yw^ z>y7ZJ?In8xplRo-iZ|c&h^FhrXt(T0fo?8CShv9n!{R5Qf;yJE){c6lXY+b z_XfiIcsa6gPY<{FUKal>U|M?9l1|I$ADLriZ}tuNWv>{SC)*}J9GdE|xq_;BqBH1y z%i1!mVB)<6c&EDDZK&(@!1i4{6$s&*RaU;pBF$?D2(M`CR~{a1uAFfOi2uZXZPV1g zAX@;5eN!!E+4T{{UT2QUm#e{EU1yn*%jjP81-Zl4=<5zp;RTMniBT{y(OT1oQ3+b~ z4EwT&f%7b3y5v|>#)zA_D4Ped_wHi}a_w_^9)*bO5bKj88?_)1m`+-;ERD}prXRbF zEpw~_5c*qV^=Y%_;}caKYa($VTjDtKxtrj{?O!Msy@$;QdgP-c8uIN-7U3RLjn4PW zy_?_LQ1k@Gcw)5fatZ&8rK?JfDaf(FPVa#vLlYyprySF4P!p93O{@;GT`yaY6_IVv zRzA`0Y?nx@9z1tO?m|q`8TIU9(TQ8k@pd8r1UUX^*b#HkZ6c5IXe{1?Lutz`wAX%8 z|De{vmj>>xSP7Ir;aZAlrcd?BA~DhPHyA|zeOIm1B_IC%7Jov+5Vf*=d%U@unWgl$ zM{*J__Nm1Wl>Vz$f$5+Z%T1D$Hg(#{hZS3U_7D!w4a)uFiKJ2B_~2O~kDEHN^EVnl z;5RMHnn3Zn2UagM!I6El7T+|Q+nBU?TA@;kB7uYli`D0lGhB@9lvfLfF`@?C9$g!1 zXPrifcdBuSzK?WB=Fd>hScSz9YlvqipU^k=dH6=PLF(^Wzb?y2N(3fvIVmTDP+xH0 z@fF8Baz>ruH`#n%x;`X^ocA(|X){Z_07R_3UI_TH&g&<4;FT3TRJs_^@^yfDCV2H@ z0$Dxft@6nI6(<;$iniXsQaY?SrxwyG`3cVHjEJ??EJ2Yn`@t12gMG}J>SwYzBdus0D-)HK>;?`L=NG8!;q)G)||0*zZQ)lz)rMb zE4~{yVXfXtjIO7mj#5M!{Qdht7-f0uydl05ZP3qI=!JASx((LfYo@VCQ$KNGtDDox z$5+|TE^;2OvL4`NrA1$QsiIy`3U5R}1GXf0TXw^#yK$VFh;1`(>3lL3tWqE>f?@Xh*UV`hj-OA?Yx zh(=`m+Q=1bDeCKQEE{9n=?_F6ggDhtm3is;#r=k8(#+-#PBc-x9KS6xq}GwU{*${3$90KQg#VaR|OOjw|wkL zG!s0jLUgp;tZ=CvxDaH8!Ev0)_jknt<(o3Rr@5o?Bkup-MLAhUj6r#?~#7hU2C;iA17Tl0@_o}rQZeQRAv?D zSy*pq`#<4`K9^xuJ0@$kL%$kyH#Oa>3oo^o)!NBbe*g4bcD_=p0mO^7e6i}2gNte$Vv@TzoK>))EBERn}zSdUfoRGD7 z2E}>!anLy1EDyWt1GQ37#y|Vsy5<)^-z8b&8KHCPF?Pb>9Q3;eW>9@mbfcDC;ZlQj z_y&g5Nw*Cv6sIm|89+u;6VZ61f$Im`Zmw$=Np7@GPgu0aMr3ac_L6KQwo7(#=?1v^ zmVEn;HWWZ#EGbTKC}l`d3))rCg>Z+xt9Hm{$%ne~QN#vV%E)a#{#IQ{ntRAI?sdq| zZ0OqHo>L~L*%b}fyYKwMfA$Kgdk@3$mg{KpWhDl_Q@a2k1u)TdAT((#yRk4k^viOJ zf0(l-(g;*n+FelewfTeyQTky>;}Ml>fS{BJFenvsP(0z1=AG?v;l@OusPJvQ@{;k^O859U;`VSkAFfjla8=&pw^?$6W7`2}oIAWOH zBJ8q1?CIc1lOh)?s`oT7gaq>yH^*wz1i^=do4KJAP0!3Z?Agn336f9vpU1ylxV$5k zWgXnD3rxICLktTWXSy^rvi#)mxb`@ixy%K8KJoY~T{<^0>CV6wW-(->h{?^A9m;i{ zY7VSzY#spSFuC1_Y8YvazQrVO#ZGy{U2({6KA2;HMEr(_pgXdigaU!OfGWt|6CPptDoQj8-`VT`L+9i>~=#=U=9Vuqp7;1UTqO&23R?Z3cHHHa@jUYw8Oz~UX3m#G$YPcv@8Jxw`%)kcM2@Dq z+My^SS8H)zq_Qrm7tWYtia@lqsa%k0SN3F9bIxuM=~+V2iOHhETWk}WNE<~UKXpzN%N_hh5RD(`iUp@dv0vUw z{GFf+<(H4N1yjOrfz+x!mRL7w8zHA{TA2zL zkyq`?lq`p2LJv1v<0yG4Db>`^jfewvOoLti(Nu<_GCAfe3^cFb!5?P!bG7N#SgSTp#2|sOaAhCasn?DQY zGg5~y-y+TE1+<*%cj3r>KfeCxmVz-e=3;?Nvc##%3)2;n6?iV$i)URdy7mD8snYuC z1THRzfN!`vCBh9#O|P%kexR0AI2R00;nAGfsFLxjIP-M@-2i(&ja7!Xh~Olp0| z|HVu-jNpZB?8-g``zKaq0V(u7 zPt5lIII)8MBW{spwV1`q!CX74W`!`mhJV-z5u=fux#F@Pql zr|MCwV?QGHdd6~k#giDlvw=LvGVUst z%tqU*)h_DB-gPIV(^v|YzrQQ)JEyyu1MNJ9OB`$&`e7Ug_XQ|EMIz63UIF=wi3jxuOR*D~5sVd;u9LnkKHo(4q|3MMiW< z=Q5s-N{!M*_qtdNIu2M&Zc;Q!TK-YxjeT55LR39F3^MW(z!-~$J(v*Ff&{T8R@(Lj zxlWOqk|+6nFwI|zdfYS;puTe&Pu*xlWNB1kcztF_9L?gGg2Ja0uX8<@Wls`Z7fWrc zS`c|XQ}zQzG{argL(Il&@oolNq<|_%ngPZH!kTsp4Gq50)&#>mQK+dq8*Bm(CVO5~ zVtS*RhjOzZQZ8_y#FKbsYqOaxrHitoIdKeoacLXlY70qjT zfvwk%FEziFonsP4&B_6%W)Gnp5o4zp_lGv6$(~zS$u{M;8y1I6g{qJs4vCSA>=3k| zoln@Q5v6Z-!lI`*GmXwtsb695uew*!`rzDF9id|{SmO={DDqu2B=l^H!+nFYCFzD7 z7i2D>_7lb8N9Vj}e$fZ0Wg1oeGWYRXnHh5Tli}WIagl2pmE)E{56<;B9ww*bhQ`?@ zbgWXP-L~~YH+Zdxgg*~1ib1o4t|Up($B-M&nozARbzg6T!HyWemd~zPMMZUNjuGfD z%|bLg)Z|99XD-ifyx=}D7j-X_gMv$79_L*+c8AP1 z;=R6DF&_l4wP-E?uftCWgYRmayxSLhCB`tE+A3Ns&YNnJTtu<|#x*Fo#G^j4J?CZK zI@-h7GT&40=PI4&U4MbGFQwYhTsXL@FZ#Bj@!=!_cz@AtlkyI%-6$LWV0_wRpxYWB zcp9Jh5mUQI&&^#tE`)?<)sAW9X0_D!J2d8#v(IzB?`dnc&Y-yRd96(&QC(02XVa}> zcI1=$NP!CE^tpyI-_A{J(QdPu>PMTshfrqYcXSU%6({|>>}b0K>j}chT3pSS7DKf9 zMt4gd0RLCg>xC)&>;587x)g2-d8ByE+{JBL%+0=&Io|}w82j)05pfE|uaS{N&&Z62 z2X3McX`7ZpLObN>#JWXAbd?HQp0(4NM1!+B*jUyLGhUkmE8fJgle=e(o>}?Zp=oyg zKE#J7v`smx5pyVnYwEMewiy$WUgVp{(4LsL$PaOr8f+rhmu`oZ* zfX#~ntbhrnsorV>MRKg$`U}HH7Sx4$a63n=r^ht62x)e-SSLog9Kh%RzH8j;+|;%e zz>hc#(yF3q|3%Pcw^uy{Zbr$5(>qjq>P}*2vEL>WDN|q^1aiO1zSHl}ma1`Q9>j;i z$jzsaU87_=aLV^FGw{B|BV#7V+Pyu(ap%pEg3Xo^yY>R~C1G7tipE=sshG(M!{6it z2TE=hKmBk&Vni=Ln7?DgutI(iw3A^1(6F+p#+ouEUKg^zOqr%PD{UK(8LXbJLj5 zi=f$*m0@sn^ShQqSE3jlrC8tu`o(Iko`HrsfgEYaqS?l13K z-dvWIOiZXO!FpnoSY|v%JI?^3vacQbp+jUD3*~T zhuC6Of6zPM6%j|MSvqZNk)P!;HF#Q|@hAsP4uzLQ8QI_DTe0;hzpnN_>ts$ey zvdq$=qMoE*y=cd^aj+ynPH1o0wxlu*vcJl`gpLs#wO|^$P)l*8p$GxOR+0X2%J6H> zj%Rx|=q(^rtKWx8mf}%m4BA<7cL^GAC{Ts$X%7c#di5k{Md`VqE?{jyOK4p-vpV9M zell1&-Ni`7j-il3SjNK*0N3gco{zl{r(!0naFzrboId4qW^Y%ftkdnx2e%7e$YMBy zf%)c^NryK88`w;jO&gOA9K)0?O#uRM52EEACMq!2j#JP9J4#cKST# z;0ds@B6lNoB|?6gGD1O{^S)4Ytq(3LyF4#gY^kHq$cPB%FTxTv{z&ID3AYOo-oz-b zk8IhVw+jiXrVMmrY6%MyY-*(->a4+K<}k=(#O(pr0$=MJjFY+iR%XG2Paela{%os0? zdO3wUmHIA715E*u20q0fHRalx(YU_y*N}F#u@n{6+iTCY6YIR`v<-D-#e02A^P5tW z;siV-V{Y8^^tE$#?a&ENI1RN$(#w{e-E6+1N9xOq3s64*F)R`#{9u;;fTQY7e=DMa)zgoBL@Uf(TE8x8UM zHzyZ8eg!mvFs8RM>Tv*^!OSq;8tUE80K=i3u2yZ5?-1Vv;GOOD&mC#Yg$^bv5c<8v*nEEDHfDCXWX|>WI6^;dRH0Z)Qt5DLllWD zwsiquq4dT+;(1CK>%NQP>Ld}^t3ez>>VEgUC~3mr<%Igve%Ri4Fj}I{^Ov$JKD>kiaKm(4r_neb2NHP{z^n6Un zR8u`s#lLML72*S3f_(5cn8!o?es0D&)}C;K4jb;4=y;>J23B9 zFVaybDxwp8oYz*#x9{YmI(Oi_vOTX$M)*A=?I1yK(%_W|_W2ZgK@`~qJ6!91iII=f zm~L>z#L8(NXl5Arukm&vaPj6n;7%a^mAXiGXN}2H9hOr{w1*{(!W6c~KDa`Jo?r)n zK+|_h1TUpUhlZcZiGFFX=$VLK-{}p>Zx}IJvf*n&zQLtC8Mot4@->9XK4lE5$sI@7 zGMbajpNJLAie)Mg^E06MqLTCsa`FNkMKM^+bHiUkcb7RZYx&P`JoktYi(M#5>T=23 z!|u0a06#~Cof*tgpEG1d>>P`p?sbTR$YXmf?maQD=rf4v zLw!aNo-^l-Q>eru0E#^dp6=3ZI~odcfnOrg9(Y%U0M0MgoV&}H))79Z8S0pMdAYaF zl8VvWth{FGiwY1GFMKjlL}s+N7;J=t`5J7Yz^AYm$RFB_rgr(QPjL|74Etn&xX;PZ zPaIXz#;bGbw>t?NRsvo`z%?lDmIuYig$Q1k*teH19hZ>UO4nin0sESzR;-Wq&GgrA z2JDHljMF2FAHJc10y2}nxgfv&*-pa5Bc#KcPokD<7(jfu6T1B$A4^MCXfYcA`K`VE zo4tdZwdm%hR`$-Z1@ZOD6LMbwdC&0qR|I?2DEZ3EQ9#w%V4Jnc{Y`%JnNgRto59T} zz~veM{tmG-COl&WufPAx4CH`p;eDRaK-Og$Qv`_5c1iO;^GN}Qn%n`}PMnM212pNQ+W@Ia1lnI} z7O?d@gWSs5*S|5^C=V?V!XY3C<9iNpS;NQjMTL?4{sd)s+B@Fb@Wp)A=%W zszWj+a1^Lr3#W*XM3|wITuc6&sL9k-m|W-(_9g12 zw{SL-GU{c$93<0+^(tJ>z`1P~g zOt^M294Ql`GwV~E{ZJh@PA8%8Pna_Kbd4}5UfcIxlf_}@!WQj zmUIGk#EaU3$oPs=!T;Z zL)QU>UT}IR0VCGgly|b?DLtji(4*YfJl@x;mkWoOqI4E+QoMDx$t-Hq;&{@%gycwv zaMdtXcZj}$5vNbw7~W8Vdxgsb0GbH~zspdtE7Fi`!Z|7GVAQWvg_2nfDo*@|B#qWW5pYj}MV$u^wmUv&h6{^3<{b9i5-uGYo1nKTp>+ zp12|Ju~C9kRmT%`dO8qXBb_vxVtfpmur)_(tjobU&@9cBqi&%duV&PA%r}D|n$~sYC20>pV>$JuGsj>N^52o9b zXP_x!fMsHMv;;PY>N?b>D;x4y)yDkfX!A_;0iD7yTMWLj?T_>aC<%>K9{6){0;Xn2 zMfZZL6w0xaZ*H}SH2rW|c=jdS$0Mw}QrIh~#AKaK zt#cUplxuzRSFHoC*&ZOK_V378yb9+Qw&S)mj zavAp>$-JYgv?;a1(WK=B+f4O7nKuiwbjRw)?=#?*i!Y%65tv#j*iEo^gE0Rk=x#&1 z85F)ZigUyLhaqfwf42wwFYDIk1PpZlAK~O5a;?o83J4qNAF<@05#n`-X$0htA1CnN z&;BEpB<%vjruAY2kpQ$^kR(MBBeP)|(1_XWw_nNf83|#+H6hgfHmP zZ_cZ3&WFqQaYy0g2kTyOBpGxrW%^mfpk{;Wz6C3v7Uge~qeMUkeXNO6Zq!!rTZ0_g4UP3|ZX01G!bX#C2Jhr3k z1{Sl78qJRzg@H)1LXsd5KM~b%3!kQ-_0~TwU1?DAAzIz&AV+;bGQqq$c&+fwxPYc|! ztQwGgMy;8J(^O?urkvZl7)1O~eFaJAJw2fgc5d0?IPP4E`uuopmTMHZ!)A7+Q9pUq z8JR*=X7`&mb$TJO#BgG}I#6=LwVJ9-Px{+^HC@oOn+hn@b8TJJ7CAr^Ii=*V#gd+L~4^RuQv_zHf8kr~pH`b&pAA=ok5LdJUc^Xix zr`2e#**+gGSHp58i8-7EYhpE3j|_Z*|CsemfUxHZ$8t zlr*p6Ej1_iBP7$@qLm*}=y0p)^i97iwS4;ic2_YQ;^il6H;~Ji2TTfy&)(M?cjGz- ztY$lJSJW?FUWbl~0kxOaI5!()=H(&m{TNkKv_A_}j5~|Qq1-1N$Y9KS>OVtoBm+UY z_5v)ZD;=Zm*mw14(_XbvWB1OyvZz-XuY=iCk|71gP%#k|D#Lke=it%|t9FF5bXvifqS3KvzmaXv0Kg4XrnfTi@ZVCWi=$8WZwFn-v%&#EBMED#y;1CC?h(4dPEJKQb zDAMly6p4JD!(B-(M^{sr`P%RZ4fSQNhg&=)A5137S3u63ab2D4Z)Gel8@CgJXr0(~ zmuBSY*#%u8mLV-ySLWY#DCr(ht`(2`9m9(jVSkhnNm{TiEUA(IOt3+2TL+ob!|F~rT4#J$HRCUSsfk#!RzQ~Z4!H1kz z{kSrhj`F5gyx{89&UH>ItB&9_PvG&%>TQxVs5*N>cB`n{%wIvAv!^{@S9X5pRrYy* z<0Q!VDa(kRF80Mjs#2KmLSR((-isZ9>&t=?4bEjkXU*!#wp=*z5(%i{r*eeyQi3qh z)!99$#2jlso-wv#P3_X%wf1!ACDY78PV;0x+F8X=I~eFtvD|eqIS?(RpjlH_(rpE#wgsf=#I|a`Md+K-pRa!^d)L4IKv>%X-_yT zi{Fadf!6tuw1<4K%2G?-$X!mXbWb_2UYf1i{OKOUnMc8$v^e_dKUfJj1=KU`+dHA7 z_jv03Y+pFZuQ#OB6LD2Y(;I?RC&Pad9A55hpR!WkS)Z|7iVmQwrL!exOIh&Cn70}} z&}Jp&NFplN%+G_NE*2G0`dEJNL#HcGZuU!9(A!V;o20FszhJ9P;i4}$D8lO7^M0W_ z$Tm6olrLl@Y_qEkRsOzaMWhtpB*84(RQ5|2@5v*=#WxV9Qc7IR$POo={U#&K6k)fWv}(;x}6 zxXVeWFUOFA%Fyb@^%BblA3lrDR~5n|aWn8luanqz29R;>IH-7{-_Ttyk-rdsCg5DL z^yUDJBLT-aj~Kl=s}TnIQla&11mu&Rp-S;(px$ z?^hAUEr(;Qx6poHwBIL)GWU~boG71cT$ZT=_Mf-*YAL)Dh&z$@@U}v7o82r6$4clC zhELQ5@^~AxBY|vz5ESzFyk~;{L7#tM%XNZv|Ma&)I76Klpag>V4`^v~BLO=24x{?N zxTej39Z2ba2&jz*Xy9Mr@te{HqWf3cgg&7hp#I)rFbIi~#q6ps@EEf7d80?={!KB$I^TMSWDFdMZhbyMJ zD+3V&eD{Q+l;g=>kF`I+zR5-5O$%mrE1oDb0`u8*pU0Qkwz`KCfxd2FN9@>O{Ltdb zCAXGR9B{f7SCy3+?V;T>HhUg~#fN>pW(4NTt}%l9=7AY)Zj07)*n8jBX5SBa zDxK~`9b-&}cZcq?*}W0rYxC6p4dY%#O|5YN22Z53J5?SXeHtb>^z{dw1jiyww-jOz zS&zAMnVA*r$dv(02>g1Y2~xrtUwszQZLivlI$h7%DO?g!FHW4S^GHg_!I7;InZ!G} z{nf;mq&Sn04^9jNo6J$Gy6YJ%s;df3>$%#D z_OY4>%HI{mjy-R|#USB&%t||CopJ<>IdS#DqGGo&kpe`GB+HN=6SKM z&IL=h8}UYLIi=N9rc;eX8^X<;)}e=l1md;|JBf-2-pXOKyE^IiR6^I}WinY0509?R zskJMu#n65~?;g5$$O#?R=FYcJf79236t(7O|T)<^`w!K6|P#9_svDS*qSm(B^7AP<06Gbf|3!vQb9a$N z`H`CWj&&5v;zqqA&-o(zVgTgH z&Wq0W|8?{IQ>t|59Y5>Lk>xF5f)dgz)*DTU@q6hQ`=)X1) zNmEV?w{O!4Y}&0J5K|kjAy7QffBRO5d6E(L@2ai!U2lc{>07^30^!p7(|AGC`b~l0 z(jrs8%Q&bpkl+8Rez++RJjB22Lyip)>c3^Yt=%4I8tNa<;dcaITcjsYKjeQuq|`Jc zDEaLYa{o@{kb|;(_ond6Evdn|x1rb~>a0UoUqk z{h+C6qM+BAp}l76v>T-oQakK5)*}sYep`)4g%o;2F1maUYGz8kdXVd=!=Ql16DfEL zsNtzb)hftvwt!KqrPj(+4iZE;3w2QACV4G(q20hpPHa$mmm^`jD$*?t@rhL68m}?d zOSGXDPXbJwQjm00?mnY#4%dxx^*@Ct$a%WvWC4`gs=9+_qxLEbv7~54zxj77U}Mh} z7b56^Mrv6IN26@}3iqkK%ZmXtF^$Ec+!rsYMFJHybut>M2CuoGKgWomLqV78SptNbNg02#a%3P`N}UY(8wB#hHhBbN68MPJ z`ztbxa!P)y(?Nm$%rwxWVBUk{U}HLt)+_h#u~cn*Qx%ET+c+0F{U3mL0Dd*D+!jAO zMl>Dv;z*%IdX~&+tAHbz2Uw;;Y|Lzuo9ndlnQRVVrpcBscuTdJT5YJRBe*gLi`DN{ za9Yur1fiSIgE!!DsM`@X>Pc~ggtt^=wt?VXrDya}3(dt7(a>=Y8eMX~h>AAs4~i&l zhQLq?kCtagGm%^zGz_3yk!c1DS*+|hGcn{K$xC~SNghL!bJcY)!lE+JzY?#c4(e=@ zfwpGfGFTWx=O--R3r3v3*ggy$vEo^7KE-?_W~S3Z8#8E6p3rX#Qr|TyJ5Pd{%;!*b zx>qsNulQsVl<`^P zFNYH2lX?=(TScKbzA||RqBTB2#7y|Q^N$jRlc0^i@jLrWj#2Eb{~n>J8zR*7#V@Nr z#Ygs-vyInhVRV@puSzU4M!)2*17-fuK{k~^E~>&Dfs0o1qi*AZqk-ig zYfm$Ep{Z2KDlIF;AZ0cYi(ST~D!o}OZ>#DnWpeOKUfY7M=15MRMoha*iqbAXH~@9B zMi3Ztkj~f08obbS9PwGyf*WKAHt)GV&hV5YH1hBj-*X9gT>n6J0`)QAJF3nlT`I*TRfTe0-1=AJOtpz+25g`>N> z;YuucUcU)WfMyLDA~r5&IzApICJ#3~VI#2J2U*!Ub}Wz4+Cym?JamBIfUC5a3L$@U zDAW|uA=i*R9Q6cjfHT^XX%AX|oexR&qU5MKY!XtGYLRkoF|q z=5Q7k$83ZIt_OIpi;tXY4gUq{p_`_jqSxO|<9Wmurq~T&>X;uud?uSamZd7b*sCy> zbV`_KHyKh-5az}k;@?_i#|@?tighy$?vyO|TE9uKdav=Pb zox!7`(9#ds`#OQ_DB4{dERLnhHk_FhFR{j_QKQS)XDghs_SE0~?BdAHY`g5%f-=r* zn8H;Eb3_g`eVoRYJU5)YU_?TkzkGEfZGq-KHf&%O@37@Ib&}xqanQm+XHhk3ppq1SH>E*s%U**BY!tL^Vx}y>hUX32eDbXW~c*2DXyH8xTSC2+I zFT>4Gf`wEIVg@5$Vf=`r*7%kJhs#WJ2)AeEt8RYm0(@}6VcE)Lwf%Qy4k%#N)fdRX zl*!aZSMg8o;`f%wD;nsr6TDUGOo_RZY3Hqm)jPDaHL?J^swP(X$!Uyy& ztrAs$yQ@|fRc?zc&m5|Q)!^+>QHg&qgfkrcn_AFlh_b^tI!wHL#60wFBGXquWcl*gdpwgPUexk?D$H$DH2*)!gM z(gB;n_?D!fBgO&Wd51y(j_~Wbp}#E8$8K%df@eowQRLiCp45*6QQou8M5ge9yRc9F z{OJLU2(&&Ze*gvn_ZMp6#(ZztU5_pIB>KhSE6_vxO*#ZD+@Il zf(qqgPdqgyJ%}*?nfZPYF1ygZHgp|Zz6@l-F`uGlq*<~dbkWT6 zJ1_9XJ7~hOo?;8RqEw_Ib(|MYu9Rcz+V0`;e6^mVm8$ZoSjVI$0iz?yKCT7e<3o&L z{rtyF)cC3mBY*6-AD`9e(qkrlp?CAu`J#dS-qFN$Im3AzxJiKKDY$+LWK{IigGQ=0 z6}Kx)okAm~!Do=B>PqacUw6RUEdyI`7@(>_KN}h)THIP^L8gL2Gah#5HGY%`_t+u+ z>47o!_MRuw3btPi9pjFb-svv@r`vRAkaByJX>*vxGY#X!fGM=k>59M;{%e?&?z(8? zq#+nX6tPTop^t)wTsWpsAXZ@C#xu!{H+pCHqVhE88Ev7&0!6c*LyDN7rZ2?7>9`D` zZhs>gWTy4 z`^Mv8RiKX_bUtj3jTI_;7CXYY)leHf?M0N!CKbBXjP$;gOr;j!3cKo#jOAtGIzWkb zsFlIfw3KzzVGELn>4Ba3WuhqZdRopxO; zLsrZJ)VIi8K6D{E9$o+#xW=l9l%i_H>r58sAr;k9VMA*ulL=SlY8AonI#8?NQ+%=& zEv#5jkx3p~OkoZ{u5oHLz@kprsMi*5m0EiL66p#i8mS|J-67R6ZRbGxDw{qwN zDKw_3L38>9b?#sFi{3hZYtnMdf!#8+|v2Mc*wiCMj$ zvH+H$SJ?y^{hk-N{gWU^2|JNn{y;9%fIU01+fqYzwOIzPFny76T2#Q#snuGW>kO1> zFugKbAHf6wSyU-EicL6EPR&_of{O>7spddRma=%qPS1EG-a#wF74VU;d`zspny!kB z!cE@O>2mdIhKb=MHN*q_h8=9ueR(==n8|n9!Dp_qUWrA*fRoNs8h}FBc@+wIz`U*Y zIR_0Z&_zH2YZ0+stgIs@ZKSv21b4F_(P~X@!($Do79Bmu>aINi!Hn)c??S7SIA%3H z`#`CeSxK}(Kk7KDrmcDpWkRU9oPxRBI=yARI7rRZQLPTxTHf2ra3^N_tC_hzk+nGK zROx2BKz=!P87dBGx(d^J;#H~QR%UJedXOTNl%XQwwgWart@^^S27Gsdsg|s+CGW6@ zaia}ju1(O(WSrc-{Z(JSClavs2nX;!m78IhtiE7-px|~dN?Mp$Q=_=>D+7gR&3u+X z10N#*`SI6=d}I!O2is%m_6{hp!E-Kv4HQ7Q70kF}RJ)tMFE|3P+`vuX+U&_GbSX-s zgt`){hwp)Sb(#;J$6Q}4^e(3deG})?P7noHsXwu}jQR-n0}L{#qbUsqINYWb{zh1t z_ro#pc5NPY9VP(p6=U1p)DSTy{-q+aV_IF-7W%2VIo34bZ$Nvk5zMf91Nd36{>Wm( zDf8mO)~zPu%~J&(V8Z|hK2x3q(%Ig~P>sA2gX zC@`d}VVUCI3wTpH#@Aj%U=+YH5V_`PI2WtOV?#aNdD`|S5b(BXIyu07yxp4FG8&~7 zuH`)VH8tQ58H24nG+o(_%Ertc>|Dy#KRp1{Z-J!l6C z!|D{7hSm)(rwXR_0<}l#!O(WuAb|#FgM^#HhPT#4B1K?^rHh*{iOm4OKM0rGMbG*- z>#J_*su?|FjmBvZXsGt{pypD@;s33W@O%9CjDr8m9g{?N;5N7B0fB-1ml1#|1R4eX z?_jkQNDTZR&i9`_rSFs+eeAmffgt?9eTqUhFB%|66UJ9Z!|RLS!l~})LIRa12Dwaa zpVw9^Hmg_;d(0_o)V*jlZrS~c%czLszNJPqRaZ(rsTMCfM58TsA*N;`ACVGsbP6sa zdoRJ=gro!yuiidc8S?_O9_?35103b@Tb_%VR_l?to&RjlN$ySdjc?r76Q}q2%A5gs zy&eG8k`j3hi5+KbkR-t}xvnVYU{BJP6rT+zj6{hhxe?i%I@j_N0io^0*TxZA*I;}! zq-zs+1A3j!o;*>#@GjmP$}*>5ds}hU4tP7(cY|CvC*QyeLo-F;iY}-FmVNXK;`ce+4EPh?6(7vR0sfk zHOuZ$3vksvE$qr)j3rxt5SrbU`hcqYi;Dqo+vI3=C{F7uJssF;(UMT^fSC|29f=Nz z7D#Dv8`7v6@JS^i#ciYrEj<(yi5k&7FIHfphE{$_Q+ zY?WsBa6jFeget{YQ39%5_FRX=J=3;=NW$_dvm{qF z5vk*ha-Bq>bVm=TWj;tw^c)KFZD-iom9F@fM4Z;%axU+0Z^s`(FPx^k@P7b`f-b(B zkxZJ{_fm-zi^;H&bM#inJY)pe0-C#zviYf_)jqZcQprq?5={i4OOw3Eq>F{tR#py^ zuml#x)IY2-50}GJCJ&)gtn4RfrdRS==K{{RLW;0u0|fWFV1t=RKX9;c@`nB#2TnIi zi0{@17>qfLCrOymiYH@h@bmzdWO2)cL<$spqe<5BIS@xMSwM?K^6$=RU8V_(CQ;OP zTDirHjDHPaalij%GMtmJks^U|8hOTfdHA4s6H^d3a1L_A5=56fI>jnbgIs>UDMBeV(!Ga2qtKEaW5Tfrk9;+e3VL*`ShV0(yhqw*@SwMQM&!ckX53=2$ zkdl2<{Qm9c8*x@QW~|n1Y&4bEe4QH)f~Vj-&>PE- zr?8Ow=K4;Du#N#Rvo^cI=3?q=WU4s8+CPisd2*Ay>$TpsQRda~k$B>_|MGbaDBzPX zdcMhJTp!F{yhCuc^C>Scxhpgm`T{@Anoj-P`3F+2V~&G%Vb_!Y~7fq zumJJ77mkLvzv!@p$i`Ms`*_{M{TboaTROVkq2bL#Nx%TWe#Tgj=tx&L)v3)%pMu1J znXu;e7lc_JZ#~(8edp0HbX1~YC*GiF?IFSwD2>@kPKMEEzs+T zxD$}|luG(|srV>Te{P493-EI&KGRhsK7#tP*tK}(YB^~!E7!{mB~r;Mi-J0$`vk7T zd?xc3Qz!?RvOtuYy$;1gGznWaL;s>BIk!825WoPI%-vFqAgfR#42IQ12P??tgjj=28si>7`g|~4vn%})LpNxL3 z5hc#Om2sGAPn>*hs~?SI2I0xS2z)Brr~A}iZ(s+!kPX;`r+DHF5VSa`+2)I(`B*ea z=XbIr&>nN!=~a`@US#qvp4q@QsH?IhhgKJ%kWBqy{>a>2ah~BVupmy2jMlQF82Z&7 zoFY+;;(s>qw_Ena`IMygCSfbeSea#2k*=8O{c_hLi7D^w@^c<958l;Q%PA;r8%9Q{XM3u9}Ddi8eOoj8aiKn_feuTjoyqebkTks}-j z)~V?7Ycn;Oq@p%gHK=!2(YtYqyQ0=3MBRlb6ShHOi|NRH9bZ!6hRqSPlXS;6Tqn4j zOZuKFr3U^Yux)U%mG^c*3}>sstjd7-UP2sTd>72Nd`R`p=5!jOx&LBAbHVG9ZwQeW zRYbvak&hh45!uqha0y9a8x=-|Q%gAXv)W^zo&7!3xMAQaHcKddzGxG!w@=<#!@-{b zsl%Mz;jLuokfH;||7|TD-_o=+P(eHZ>oY=|%+S!{4*$|~Y!X?>chQB-hs3WANO%C~ zL6^OUfZp#wd#?D9QLkjdv9IbC{i+ei8soo4*PP1e>Zl99=@jg7!CnCx6Vt^=A9x+G zTGkylhqr;~39wo=5Ebc{+Bb=mBgwO&M6J)8Lr>l{$6t)ZZoczgjlGc4GGiHKx79FS zo}dLSi0vbCd1tCT+3oVqI0)i({~QLWv4SE_a^1N*bCt_KZ1^^osaeP;kyxb@$2w%I z=v0^e#LQ90Kmn*mA;E}$@src!6 zM=U&j^mEp^lUaaKIfBA$Qry&&i9J;z@PCdD$b zIE$RW7*o`Kjmo6+;}S!LMFb)Nq*;X>$mU4_i`SWtRVB7=aVIfugr`p4%l=a&<^jSL#;D8-~sk|8cefSbClbSn4ca z^t+xqF$Y{OmCExBcT%5ptYa~iz|xCtO85MHJKb(W#CI4$?~ZwJ7?bma*m{>I{={oX zL74yR5D@W1siw`G+M-4!)(0HA`$-N4Tj7i6ALU|yf9GZL}nx zWDYXHHbrI+7K4jwC)~gXV1pp~>yh}2Hpfg~yIFs9YlEukDih>ARjYtFew4@R7sUC} za}UMse0LOY2w&y|c?yeXIzHzH7Hs@iUg_OH zz%DQR)N-c8_)(B-GQs2oe#*KEH5Y@zf>oN!GowO!Qv%-uQpsu;zzU{BLka2xYjR(_ zVhPQOk7Xh#d3PboQ*hQ(fIBD*yoZ1RUFOa z!DDXUbcwH$Qm>`TCwZE)m{P6eWLl5HiE?itr2$u5-$8EOiE^%5vW1=fSGtUsyi#rx z)!dxQJ^0Y9EMs>+z>pJ$x?YODncLO(O|8w_KV{;Lc<@BRxMN-n-fF(xPb@$7YtP-`*oApG3*Jh( zT1?YQS5%l`6Mi0H&Gr&TXKxKt`(nuZjTiH~#AJy#vfF3tZtKdSTjS~=d+ZtvQ zZBm;KR6O|`tebq;sK!Zk3wM9sBE2u``=K+e_y9<0RBjvH;dh|8(?vgvX!?g=4diA8 zgXS_u6@UW>4ZgBQ8QeR8g)qXJUFYd`vLDc{p>c2NW4P1-lK&Q-fTYcep6CXyf|kE+HOS>Hg5P*aUQ4sE1tN=(g5 zv81`NLcZd+w36@)For0Vn+{cbZ59E|Vxey2w9jUjgG{k2_F@SB=wCi_`Oymrs2EaDrVjegLWZU9LY~-3c-yK`S0x8VDzMwX+}$ zKM{MSun*!5MwV*9)xg`Z`3kRAx8MZsRUzzC<>p~2DU?zb^WX<3`8nqz)k>Dw&mVN9 zwjacR@eE|?H7aaX@fS$#5%pMUsw?yb%32EB+}XnuXX#EETa*o|fSj}~9rHV;QVPCBDq*$UN-7?PtVvZ0LXg zzKSy!<1!qw*WRSxJ66nO4G&s23i4iMv>0!zIWpIJ3xR>(6Nj2nr;2Ql_f#C}05>uO zR8f|=Ou4YenX?)|v^f_TGC82<6h;-1R5o|uqL~rN;o6%2K1B~>er+?2Eaa(o7YeG8 zCUQ8XSISU<-qL#OR4Zek*0!4BVeV!(^CrN6TWM;)LyC+sRVIC1evt3-kNo_C_@7+& z539)H*0eYIE$;Jw%k>;>wqrmr|I_sS$ALip)%(-Jr-5Gn+ewGb0L6m;S7>JwlA8Ve ze*EZLY^O?dS_L9alUoLY0;tH?qX;4T9Ra3E8{{r`5g(=?HeRBKp^Z?9MhW4Z*Va6D z@IA6arQ%jtVnHc(fpI~HC5#7v)|?R0b$=+~OBf?*sk5uqd7t-GR-AvnJiQ_OKrpmO zAhh)w1ba={ zAfJaBY(zO_MCLw+0T3^x%mID@K0a@A5%Gt3gUhTk z8*GDR)rTh){g)y+DImu^?bcFX0>(_+%ie5j5Ka}UgEGBEQnQMKo}(9ZP!1-m{7#eD z97Q^<39<^WLXr1h(0FgajJk&3>2-G>66J>Qx+xI@U&umR2(BrHRvd5RLN=q*~Asj_HPk8D2tZ`PVg zo`pHm8>g@G2iE^@rv1@nl6Cs_V$j2-&F=$|r#bi(|e zNLfJt@dl5{)aMP3TnU)hF8rb*Q4N zTsuqL=MBbdP}Mtyc@JS~sD*VxG@i1^0!D-F0erDrx_yY+8lxHK zne)6{IY`9t%X+K{q?ScXKm}=r5HMyj{neI&3Eu&KHo7hk(g;;$3RQ~4H!?ABP{=~M4crN%X(`o7&W1d zW<)VTJ0vQ*c}uAVtE!*1x#VHUvI^3Pz@1SlCwQ(;aA-Lq!CV|LO#x@e*S0QMT{|}} zE*c&?u1jazH`%OoR-LWa>#w{sFLQw2sW-i^8^HO|SRQD-=!|=jsF9E=!XJsZ{RmKk zo`3*=<>sXf`5(c}Y0L^cZ(_vF1lBD_%4~kDBW*snmIP%^db{F%@4^kqk^`wGn&rxT zdiC`6MAjv&t!dYb2%#Y?@8k8#rX(F7Ndm1^bZe+bQQ@5XCL+n#d01moUI}TAI`!M) zn3|waEH>2h4f6g491bZoOSZp~vF%a=Y50Zi193BtT8dI zCJD0^aycZ|U#y^7w8{J0NE`x zh9felhPcvO^Q0}fHhIh3LvXS^}8Wjm-{a6O-Nl1)^x4U+=*f%){B&{gmNV8=81 zA|oLhzsI|VKi;K?rE@*Ju}PP!LkDtbHmLNB4Ay2MO5U|=fz7ESdbMzmSy#T_HKk|4 z>S($HC2htN3r3tP1AyZgf`haEqz~5@N^^(p&4jF9R{y>t(k>lj52cNxPhlTn9WGTX zDJ@F6RV>eLWhtBiA)Dis#^w?Heb@(#5AlFZSD;ZY!rtR-?(y}pqRzv4Yd(ffykiiO zE^AG`TPqah|y+Qz~=9?Io#6$UvjkC(WtK}7O&5ccetG6R0U2y)v8bk%3q zz+mo=?Xo>iT!yMA8%?67pIrt!ND&4y4k;MkG#v1BIk{Dr5#upLfKk^>U47ojFwo5~ z)@R1B-Qv?~r3TqoDSPkuqA)EDdL1$0E?;W8mNn8-9&y9j*QPQuJ5${3oH|GzJv8L6 zDU=ELCqwdF$mWqHW!#bk&%6o?a243g+^?c@w53CXbyB#818n{Pg+_qR&&ButSVe)O z;JOXP$;S^XxV-Pfh8{z9f4k*etiKZL+2tU$o(kVcf_!F&&P9nUY(|rVn=p5}?!#dB zhBHhQC)l$g|H^I}2x6cSj18-^e#Y*#ck6Cw=?oo9$$#os<3{mA#jdvgBW-Sf&C4g0S))ECS9CSYDzrxJ;ApW z*#41u3Gwk}|lZz%WU&Qa?Lk@R5*JDdF zA3b-y1BZrtLR`j(2$MTB&lEi=!4gLTVLZiMn?#Ub)q4!i;sg#cwiTuT=A288LGL~{ z^)I=hojYD2fJ2gx!&_;fko#dhKEhNG;Ff%oQsN}a8L_WeQBBA64?ds00r>|p0zgsO zB~;?>v-){=YfFvf=~O(Jm89c13(|b8s-6z z7N!lBm=<`=%d`hKx{bgO<+l82%e$n3* zl6FT)ed;5#@MRX-Vg9J5+;;5KNU>qu`uJD72(M!Q>K0w!KQCCxV{-LB%Hcg&-MM+t!Y@O8EXtlKV?Km*lx&1R2er@=pS7@`e?yuX^1X)fN z=nf^fL0(-LI#5pdLEFQcHTAsR!TfxA`g%d;+O60)3xhIa*q5f8;zeyVH%WS)INSDs z7gLPw0FOnA2cu)(MR_SB8gCpY^ldXSg|fVznUtw}%)XSqU~0A#F;FT~j_cVFmvAiF zhvPJSIh8T{las260~6;0;}n~1YY1Lf;`$s z5ef(oXS4|Ws{{qRQg}PJYItu{WLLzebD@F4ADpt-PbTo+;sie%fzOLW&n52Nt{T~W zXd^P^;*-i5qgh<0Wt^w+c>ql!VDBF$&wO5=L!8Q@F9E{GHDW0M%s31qe${aRuV(TK zly9&qL5pQxb1*Jq-tGFi#k%jMeLKRQipbwjTK+DaGN+4*t#a#IzAK;3V$!HO)_O4i zp(j`L9sBY9Hxanvw}!sl3ybO}Utjr>mVUQo00^}c3HtpbR=-?zu#Q$hTf@G6+wIC) z=RBztKvX=u80e73?*DKg!@()?k7G*(|Lu@ye?i$ERkC$7)H>#Caek?WWPY zqWwyvT1V&CU*fzuv%eC1B_kV8{aH58e=I)Fh2Rq;r-NgS;S+l-mgoJ^ z*iT{~N+o@$VrrGlqd8^E#%|i2<%4d%M!B`S!B!N5~y{Rey>6w%%*W()7GAw)-gm^+0)KFZo%5jGl zRGMs~Pl&a?6I9xejDRD?+WD_^-Nc1AvR~{hegBUcr|a9ps&(eTfct%x_m6n`fABPU z+Rp-B(EslQ?;b5E-{qCNymi~hHZ6QaWMEKWP{{b8pON|GKZEOoQ%r-_L8CQv6UnMc zyNo=7eqz+rwElW4N2~f(Gzvl!7)%-Ylr-)^so z$6-5*!@+!8*WqFL>FJWNP~;gT$X1r?@23@D=5NeEDfv&cCGGQ|f>FmLfHos4jHvos zR%r^DFi_B?Y}i%YA__rrFt%^a6fLcFe2~@Y#ODejoWcVbu@FGTF$bWf@{@|Ka)B&B zk`)tv#q*fuPDvI-W~jh&njn(yj-v%U(WDL&;B$IbO(#1mE_v{m!^B3r!+iY^flVIp zFhY!2nm(b44y#dTH*g<_yQ8e&b~e~~p6GRCZ<%2-313e;mdHneZSBNhfL|VWlwA9x zND!APU}D@oJ8Xi91`~Spn33%;^=1wh?Q?umuz2m@?vl<3V1A#J~akhrc&Hy7?DHkV0gRnZZ_wLBEdne^%E5bCF)CX z42bP!0rksb{sytmL>!w1@HbLe&0=o3+K;Megxl9iH#`7`CRGN}bOyPsu9%*Sme%-d=D()^& z$)19LDqV5j!@of<=KB^$r3nBqLRt$fst{OJYY;@YthM&q140*4m@%tM8_Bf4WSzB0 zUe+UL^7Z>@4DnK0zYUi6v%?tE*Ud*ZQlW-#K}<1Xg7(utxfo07dcep3`E<Ry+!;>^B51-PagWw-Qq>hzC+K!$9y6*r*%N#q*={rzV~iv-1v&TZ!yVzwyxL4^To z+pusdi(kI|=2z7Gbr%4&E)9NRxR<~meO<){B2@cDNWgaq^gK(Q66qXPAm>6^RSh0# z1l<37?pm3$*ap^??X0H9xBU4Yy?K${1loQ0;9{eLrnR=DYXj2$pQR$@_Ovoy>ztd| z^MWtbiv>`tmvpb`$3DE2eRs0ZGfS~M^ly}im(|E3`VArT_A>yn*HB(qI>q((l7odO zQvVkfA4D;ZSb1R2qU5aau+tK1WDZ&>A;BN@(2SLg`MKolLZg;abUu;U0-#ln5RrFz zHD8a&;MMy`yW-8Fjh?(SFtp)jkV<`EX35HYDIS|yKNNbD zT3c8zW2Zgfan-xRppCp|rLQ+zHBrAN?VPJ-C%Sk{5eHqOt}aB7*rmc=sy9vUXW)7V zIT;xul442jHw83b>D+#O^~v`d6yC1x+n0SMtGV4bgjN8-I1ut}yD0Ud02+J=gCdgi zeawA$mOk}cX_77}o)Fq=X7HJ{*f)c-o09&3LCW86`T6Iih_sMaLd6E11C!AH%1Y_3 zo&}oO1FNilsYw2UZF%_-rhmpX-JGHD!}6BDAW3Lt?IXZ4K|A_QGz4lel1?xTi_j38 zu%^#OEF1veSksb>I0c;1i_^k7i-9Msd-me@>#;4P1u3ip*pXqk4k|gX`zpd1h2@_H zOJa!XlujiF@w_ku)X`otDs}vOgO%k)J^XV8gz_x!{Z7}u8IKmj#vXJ#xZZU+gb2{P z=a54h_B8BRh=1dC<@J|wBKzz9u{lxQ74)OV4S53~hPze_R78jljv^%a*zygWWmNL8 z73Kv+TCii<6LooWC}l`L^8ehSojL~;8G_|K?1glTYZ0EJ@w6jd-5>GR6-d#~oQ&VN z@oU}^^WsC-b0N)BcM|?!)T=cMOjG*HFcRNdhlg3ttUWnoF68cHsL#JG*8)~pG*9fC6|hNjk!KYSZF$I_8$(Hi6oo*E6w0l^22&xDtH$sHhKqX zx4QhXammw>q`ydHpiXdi^GSbS7%cnZY1aVcZ+NvYV4nyG&FrZ)eN0M}hho)J|AaH5 zNaRj6x@4LE4Ej{?{GLi6NnpSn$Mxnk%;nau*n2COoD=*T)JHH_9quHTFsKsW#Z|;P z^(#&o6T7LdF@6Q$AtePo8GcaqgL6>j-lg=YJXj9wl*Uw7Q&)YXq1|Hc7QfX>SN8x+ zjzyUnMzqQ@WL3Pd{vP|+quAQkHKJiYGv%JzS;?2IN6asBn=Z^-!(bGUr>yk7&j|4FSbOL?6{|JHJT@TlBYGqtNNRV@J&zDTDN zY_P#ki*}Gx^j4yMtHVMD(x~iMqv_MCl%Fi+Jgidp=zaeUcQ0yrj*bK{pkm+EJuDw| zwiGNh<@y<$IcFYZHpB5z`7D#GX+3G9?^1xBdn2pTr#x<=e+ylzAJbP-Gt>kAc1Yc3 zXVls|wwI!{+^BY5N=wUZM^(~R1{*{jbOswpx`XJ%wr|vUWU|+%6xLJ?9kQu)Y2Wg_ zaXROBEtD2_Y25m5+>xkr+`Xd<5ZzOI?G*S)-VUCucc&jI4aZudKAXi*q6w7ymSd{$ zR?m%FAbP6bItw3H{oXnj1gHW&0%Z}Z%4;ezced@nieAWnb+Zdg`Nj+vgS#>kR@h| zCkV;^Q3)RlU1c1PeJA^dfEGm%B>`hcVilo+-#zbRPXO%bINVEh&H!7Yu zl2NhDNFhl@CNzp6*o3nB~>N0AS0dc%ampKeXvdv!4(xbCE4R*+UE7!D?k z6alpF)4l?Uc|$Zn-XOKC#9X?x_1V6Gyz zuXxlMElsI?Szu3L;kuAo)!1l|MZ}Z;;q-fiw?F)SB~{>NV-QURA5jQ=q$MKOAD#IM zzCFc`KkAFrH~v63Y)u%?ek|}o?4?Ez6i1x!_v0?y9uSfMSK(w6kI)a&i{M3U46`|% zbgppa0~UyOQ~BA`XslJ}S1%OA^AQ_*fQ%(C6*@_-kogFx8SoIsDt=S;%Bo#qZVF~! zzn`B337wADqv)7YX*A$W4!IDSSVN=jn1tOpKcS^w?{Spo&2yu41rGsOR` zBHa6HP!=7r(~Qf?laYCs;0xOmEej#GEFR)gnM?EVsU3|qVVeBB+)hHe*p~SPWjdxt zH4ZpbkTGptvGdNy+No)tT#fkFS-ax3Z~+E676zLlhuV@ce3~rFpbv}Hfr{-^w(drH z8x#92<%QlYTsy*W&E|z)5lu{ZRls4;eJ;6agv!GiElkuClUWgJyHjAhcjvYhO7Y2) z$FaIIoUwL8)j+DQ#A+ZKw8LT%6)=Pn+X1NJD?N0K4EeirM!XC8V7P^NGITf}ukY`) ztd=vy*@e^NEZ6$(O2+DXmOz5=f_%m9iG7?WKGEPC{{HR^`Ppp5;~PW!E?~bP>kavd zuW@(0v{~09evg@nisNysV8qf9p$~>efaTmRA(HVfeV zYaOKhVO^+JW#+=5uB_4%@02j#O=8LM!mh;cMmaHhX!{W|Oj*d2D{S~+8))Z_E0CC! zJF_&y4#Xn}Dy=LWT0p>DN2;`n66i3&59V1Gt?@K&BgilXah17_6}29oqvdGjKFn`X z2zZd=Zh|~V#W>&6v(4GwK0%6+;01_RC?a;izKI7OciS#``O`JhrTq47!0XL!v~InB z*>IUzA4!DS;Nr+G-RsOQwL7$Y#%#G4DltU-#k@hKvJld8{)Yr|173oM&RK9+pck*eiO=TFa?qm|HZ~@kv(;%d3nyeCHbM}inXsX zKwp$_jlOuhW%@xw8`9UvI|jht+zshPJ4$l3N-NNzefCh|PyrFkO z+6Kig)!IEa+=1B+1m6Ew%#I9lcA15@+ohxkX?LZg^9({L3^HqBzLo}MEkDFR35-4h~K<(`3cL# z&u3zy3cKU=JmY!?1il_8ue+6c8^SfSv?m5@0oT!M7bN<_9`-?i)94Hk?XioQ@|asc z3-}w(luczjL(s*~an6FIlUU6kPbkDF)tp5~ZG)SdggHjkn||MFo{3a>Vjbx>^Y%3|+_f}j< z;|;qOO1D%t`zO@CR5bOsfKKvMtGh5ewlj3XKL<-fVc~IlQ`|E|9l9*Mu^bY&=%0T( z$4oh6PP)FZRqn>}#_W>)3xh+d{z1qSI=6F?Ehy;_goZ+yqF%2&!N7$KmQ+H#xzp|}vUb?#EdAF~evUzv9 zh9V9`TVCLbxsR_6Pog(q9}y8vaC_1A^8T zQ*4M!$CN`us!;)Siv#kQHf+7Px zadAeED5DuvAb(q@0y}Gtp*OJh53obAj||jc*mQ+_BBX5#+r<^L!gfkAni{r3rI;Gx zf=|}h@qjsNt(o;J)7exSgoe06oix;41x7d3;Ri;y)IkEkWm@Ybz;F!=V`14R269kr z>O%NH=^VH287br1^2rv8r}b}c|PfnmA89ux!jO0TQ$znjPrOqg`6Ejos6CgQX=h*= z4`gp(+V+%KNZtV4SChWvtqE(O)w|z5?0Z~3O!^QWT>6u%pgVo7_Y{3Ro;=q;UAehO z=0Lf}*&jH2mmk2_5Faex*}Y&NFbpYVJEo1B5E~lG#QMsu=JpveI@%xYUtqtqEN(Kh zE%cBA?I%|0waE031^!mHb_;~+1J+egdda!K(TnBiO%+EnZdd~#djy)ViQ zP~HaA0#fGFZ?;pJp-LN89OI0sa>H5n-$>HhAPZ5?Eg?Na*)Fp!k2|{^yl6=B2g8YL zW7Tvv^YMaQq&kAvRRR{?;i{MNMvws_HJCA`z;Lv92=wUjViAS@%pxszAkv`dV;qg= zlHy@TM>3%kL=V9LG7{4sRPz~0GmDZb-OD(W(MC4ZH=<;-bvexCGtBw`T!b2yr3E5R zy912D1<{MIv<_u^xUVMbGSUH;sJ5Vrpw90mC&v*-#Aj?6DXuW$L9b5|kwSpI3RuHn zU3yIdSlwjZBgei`U4_6V_KB~ut`~uaAJ*clb$RSyJ!qUQzG8pn65Jpr;-I9=h4it8MpacnBpdm` zCsCZzET$u!v1<7XT{U@~(>mY}%V*7RE$}q!-xQbB%nu;LW|ycQ_bloH3mOoq(X%;N z4?L=42PU}J=vBW3wk-$lB1zyG?57>8<*Z{i3H|Pme*Nn{E~7s{K``bMoX&CP{h2cK z6Yt7|z9x-izongC;jn44oUV7xN&9U?$`%&doMExvEeAHz8I8}|vXG7^Hg}Ku-_!G- z1LM7rQ=5$n1msJbN+b-4|DUlM#Q(olovj7$qq>|dKzV;_MiDEB0s@B=;S z2n+@ynFPkKuYPH6{mTz+xT3yVtBKjaO47Zv!FtuI(|Wtb zuG7laz4f0)XLWP6-1lh@2VuG_nVsL;j_+xX_rLbjkJDbEChopuZhpKkTGy|DHG0yL_(aQ(<$&$9 zh@}?ndIKV~EzC10p~$Fm(dl52{hST6Dpc;pwl4N2)oSR$P6a8kk_QO5GLWgHKD)bK zQlBj5unFSL#5TRXMF1IYxh8xV#CsL|5Sq@t72JCuKokI%F+D!oX!24rSyw}69@ZjB zXXvu;Lg5=-)L8v|ePZ*Zi2ll&G05r}%ts$H;!;t9V*t6~ z9?=W*=`#?Tr~_a{?0fVmi@9LS_=ziOICBBkg>oJ(%r^iznW5xBWYt*$>bINsikeN-h!mbb{NI z9A=$>Ew;PM8&4p^RA|;s;xfn{$cWfv+}RR?riWybLo94CFZ*OP61O&!gJgFib>mJi zagMhTdak=2?(OA;*))lDIe;%tIMeRfz>D7n;rgvrEP~M_&nI(dODvkWB+DOKREhH- z_j)E|g#p|Y!z|__NHx-p;IgI5R0|184h${FM7EcRbu=!IipOz6(#)c1vOO#?LriIl z5-yayCsfaIvP+b?wvx`^N0#r9ng1w1HfWMr4WYDg*Xb1cl|;1vrF{>|rzfQl@~M{h zwy52rbo#Eko{<8(j@B~nRgg6Dsb1eAzGb2Ug7!D0?DHWs3 z5R*TcW!vS0j>$!Yb466P(A0voE}XKy*<4V}aCOPIN`=z?WkSJltX;di=SGJn`6uc% z72ee)kOlRGUHR;SGY@8`hPfSGT<-c6xlZ4K>1c`X;wHwKP<9sc5Z1}-0y&Mwvs1d5 zDhjaXNxF2NPmS+BG1YZVpdy$%7xIGgq_Y~|w!l~1kGHf`^J9o%2l#={b1R^O@^l0; zas4IbMqkv##5W|t^hFmkIByy>xlGB_!pfbqB#|3MaOUxzW{qcPJyl4I zet^%;!2sRIk!N+6I^v^^xX{L&YuO#Q=W^paS?2pmle&^NxafV{7n_Cu}WEVTb$(1&bK!{xa*xi=Xq#FVbj&66&|DBsrZ;ON@;v}#`(mbN{I!n10?s*z5((X zgL*5S6lgE5@>X5m2)YqI_u(7`oe3(TcO$02f#MFtpF2KuZ;H9EKFz2WnIUjM+-#j{Ph zS=3>;y@7j)FDhWPrap4y1xlaer2weNw3w=Dwv&xg1R3f3D@EEsm_79Du-jZ}g%!+d zl9y?>BA7DTuqI;6pw$92p6d znjrd<32o2Aci2z(ikdn3hH`bPkBvN=ma!M;txeT&wM1*AfWX4}&lDafDS+yV@IiD5 zuHL*+%$}|&)>5%hpfqEIxRDzq26@uZSb^jjMHbQ$f77K!hQJkP^`S5(Nb-lT%EYY% zPt#hlJf^B$O` zFO^jc@yS`U*@&wkO$6+NC(ZCh=$YQiI9%PsW$)w@PFSSK+s_M-4&K;gS03RAUFB&0 zQWGV8cd5Zh0Asu`8{c6+Lc}cGb+YSZ*3)g&J?tRfEO1;ibtxB40a%xd)Hwvqpg$D& z@YBXV8x#wY0Do|~WwMJ66Dq)UZ?YH}km0_}DI8}L>~jR5SdvUj+JS;aJCx=#Z7hP5 zVyRceBt2R$O7V-`-YBc8diVrG)-JV6RY1V2Gu=Co)~C~BU7A>>5yXFM*@{R%=s|K z3*}CuU8Y>TRW!QdI2NI!TY)x360>hwF1yrdp!j|Wo(b5P57~WHXAa9$18$Ewl52yT zo~1LuOodzM)QQkK+^+wWJxQ?iw=u;+rna{#9cSlS{OoxR0T1VG@3ot^iS^4CtO-oQhvOA+J{ zxO9O>+w~7cD7i}-%fZ?Rum%8YX^Aeu~l;76D=NQ9a4o4c>NiK>^5zZ34`B4Iy!!EXr^V-O* zc)8Oq+6bs!JV~?qz>7F(_A120g)8AW-m3g9R?!jZvBb}TAIwxNze@~hX33A~n8<01 z;}EB1@?b^!O#{dpoxc&{k?EHKZM_Q64u%F1$4c9HfB`r--f-VxEyTlv`!<$mA)W9r zVz^of$IoXC-Vvy zO|x%`>}|4Qzu2!O)Ak)a1l;-(MIr)@0~dZTaa{DcAlAD%6Q@Mhwh+<0bB}^yidS6o z?x<>a0CaMoeb)i)ptJA{M~33?X~-0sFHDX~xz7A-p`}%FCQTB;V#JHj8Id=HJaX%M z%;KMv3slxl6T1W}?bDX`=0$jU?I2@+oNlf3!X5kP4H*oBnd`d_n@7LaRnuAlAy{1A z(z5#Q5%rUL@9j~`^4_`;?X;p}NIabrDrW+0z=!;_((7%c`u_Q#W5kJ2^ed+z-*gFV zSqyyXJnkb3hpe zhZ=2JFrDNfd~TVR7PS5}wRF?qdS~n+peNxn!NoVhPI{r=*roFtyyKHjEvtZMBc$|* z-a$1e|A-**lGAtJG?$V5JZd1vb&pv1S^=7Gi97hq!K+cjqQpL!n*99+sNI)5)`&6q zvYrlj(YNQrIV@)W5LET7v0Ky?%j}f|clGpl+Y_|EFMH;+HKbo-{`-ux|7Nhw+fcr&ad?obMQh(_L%dVzovQl2Z_z>sJ_ zBU?Q9={O9Fe&gN;JTJ#?|s zZRX<7F6!L@IqOcSSM0Ps2l6zPdt4&>9K^P2YT{GuQ^`Nam4MjQK&cPno3!f8zt%gt z^p!MPRbt!rA95vC-t+Un5VtDLY^-uic8#H~n|)sXRR@Lr2`Om}{t%mYnV07L9USjp z9B1=3%BoO^uLH4=`5-b0sB4~p5=A(Bc$sg4&yMt{+)&}buHyT>!D#J>+a{K4l(r}F z%P1j(tUH?29LzIm|9a%ky8%Hl%T1JN>X)6nK_;Mi>q}m%bNC|DqN+_dF15&gR#Ceq z=h^g}i@o}k;Y=_`gkVP3hw7SmzUJsR-n8zA!_M$`PDuH;qABT+=cx7xf+z!@t@Qi>LPZ9b$PkGQ$OfY zodDl68BZDk-bct4;2aOP>yNAx#P1{1iUND{{;|~1c*I*eMjFsPSuE&_(aB*wNjDbM zf~D8AA>{>u-;c8%YGKH>!FHQ73tF2fo65)7PcK(ad~0%I?RBnp38TD%|3Ifek3<=b zCzhT|d#DlPAnZD?z+w}JpnprzPi75to$7~x?1pgH?!S980?seT!fN+pzJY6EIhM

(E$(6<*Y@f1#|tGNgQ%=kSy7&Y6H2nf(h|@}G80edb7)K_2DBH8BF<}H|0teF2(Aom4olrR&X;tVP z8J@{Ky*hV20UiB@*8W#!UfHHkZ|a0qyoUus_C| z%oohE|L8{Sgg{*H`+aZ$-SKyQ{IKgfv7PDbAEAavOK7%kh9 z;!w@%O6jLy<_L?z=-N~TBUNiRE91`)T4wlU=*Oic0H-MpNmG9B)HSvOGxwhewcXp- z{M&mCY!;xZ|6EfoAN@*K(~sT;RnJ_<&IW!#veGY3==^*KkKC$mhv4dNjpq9&tDGnH zdH?M5U#{a$1%{??ypskA3zlAjxX-lkfTW8H4LfP@hoCpxg~WnT$;X6UkEApmAryGX ze(j5O0kFR8fGR%HTuYq8xmLrun5OU=zNNl8^|y)WPL$?dtSdC_7TefOtDwhLy2#L9 ze1MlwhPf4P7k+-zK1woj38^wV)pEn=%guo;1J^g6_y?Y8XXp#v@_h!&K-u={rY~i! z-uMfeoYT~Veu1heu;CvzM`1+VpFXbWjl<0gB4`_^rz-=8lXM!)R%{4ROZ#C zv@%NQp6b2WCv;vJ{jhEpRNWdBf>Nagz2p}XY_UOxDfR;w)Ee2^kc2Mh0^7+o&=KZP z0XqY#HFw?!YipNE(325!@rIRvDIncTeB;}y=Sw}laophkaptZMGJ7K{s)So~N>AJo z<6@YViIIOcK+=QOcy9sYH12aW z4C~3QJH%p%I$Myd3bJht3suwon4eP@+AiC*)RUk1wiuSHj>M5Q*so zJmA92aN-3a*dEX00eyW~?8CrtSo%T&el4=Vb?;oxJGOhYx!i1YYW{M6q2+kT@q+Nj zX6}}{2bGlP`~z$^Bp#GoV8p#Lr#+Z(-1!6VCsn^*Z04n;F;hs3@6$+1xV3x=WMR!~1Bv-ssdH!%ZdrZgL zj%I?#R8C<`p!(J;DnCbB+-z(sMMWT8?(pi|+D-LtCcnj-Oy)z`bmuQLb)%isvc(4# zFS+GkTi}up+UAV)AHp%HMbvc4_TRndYL=*a4g?ivS84&Uw-i}V^>pf4K!upHob;52 z^7iASw(|0Z_VS%|yw>Lntp)lmw{IG|&>1PUQ|GQDPPz3u(DXC+#HC!ia%AZc0Wc-~ z(q)*9KbpFpnE>-kM>DWjX(sb=l($z@y(=B<{MP{qFyoLEGeOAjqR3w0$~v#eUhyKD1jMCgk^h?#hvbCZYDWhm3-TYE zCdSXAj~>MOe-@Zgh^IvelDjOYGCK;lcB2O~{IEaJs)_2&j5{cqk- zg&+v`e@V-iA|QPKT`@?2q(c2?AC{kerW8Pg|7V~7K$y{#K{P@B)2#N)rxZXJcj7(x zv7LQk{h!_=bn09QFU0?zmDPauP+dmK)r%DhXg6MWI<;$x|~ww&Zt`nO@$4=2P-G+0X9;(|KB!Qt8`WF}GnCW*WSJc3q>sut&McLT@g3LJ2szXo(=P zfC(EMy#!SJmXP@sLBAMglyou2v<%w|Bu%7PGZ;nsvD(nQ67fMWRy0HADpXZXUTqXyzoS!80(6ID zuUIjq;UB65qjj?&zg(8h8;;bFDMXc&Z-=d`@kg>mWA_MV;9~>!_tx-0Q&HO($eFZ#&QV?>U#HYMPQboJQn%i(vd6i=sPP;UNk zMCA61&+|v6PIp-Xc#gbiy)98hdMUuFbbIVY8+Pj$3n_LKik}*Bd=zZ+2)~akds=K` z4(zyWiH*z+w9TT!`&pe%cFS~I%vGBRfpIz7r{iBy)O@d%SE#V-uh!uk|p^ z{_bEAOar|R#VwC#-rg0wK)p}OHzE%a@hqJ9XqXR8u8pib7Xh)VPYq$ zYi-(dxx5F(^V9-PGCVgC=^19K2BDWA4i48^Dj5?pjb-9h!j+d|+50NEaW#x#&k-JS zy#fZQ`Y^6YT4o~GC6lwBei$rDBMkwzHp0wS8Y^B zHa_CDD5mtrHDir zLz-MU0IM9%8FAJ<~gdDhjQNxUjcy4!thxHYkUo5FK0c;Y&{5np~X)}|p-ul8MdSi2E<_^@5{MICQsAl?B}rhTP_z-#qeFJzS@N*qHk#piJvT zj}&vI%Lz4Wc9$BvD^0*=OX#Tcxu&|$3*T4SJ|c*@y!5xvDS0eqdOHBcB3D+MH(&YW zF&F%#2`j~lysIBfwizpKSLp9A>UEZwy;|UkO@%=xUhK13Kt8bh)Ak4QC{Uk(8|CRV2*JXIqpw-(i3@sQyU$+eo9Hi|&DG zcHBM4SpF?Fo za2&i@mR2VrGJal8IupAuN)xA2S+*wxh`V0~SAV(CYowq3WMSV*uSM4F$Tj*;rKAsK zrBjW$%?u&TW$*yhU)(afHy?KyQrS*Jo!sQd;j1p>m!s5p?MvZT8Dn4|FqK1fY2HOg zO`smej6qgktEM+6{dhpJS~-<)moqC>h5?I?;_A~(|5Ea}tr3N)iW0=G9>W}A)P7mH zsC6I|R`o0-3muZUhj`=iqW{zF@7C1n#5~!YDpow7*pg+*A(0pE+cLe3tk=2o~7~fq`saY-Euu?yA-WFn2C<&OzlNi z+SID*+q5XnN*TawFPo3LA1lB;r3Kll%@Yrrn=87HJ?PptPnT2*ffK}+vZ$Rlm}YMa z3UWCt7%T%M)lw3*FwNk_)7Y(XQb9P|aqZg~M$(lUSHs-FN$4Xu;nU~y2n#vpTU^-0 zyk4a>1GwEMsa^xWC};$NOX}<4PKOs#qsRqX8gb}ny%pHAR5b(AjjOK2RtJyId_DfO%alDRXLQod zZcSyimmj9VGK<6%mMWxQ;>swv3warZoQ+#yl~C&W6GU;N3}xbl5kyrM-aa|zO#M!@ zj~JQ`rpAR`o(Z~`5$aChI)fT*Pz(aoI8}A?;@KJ=d~9)nlzz2J5o-}q*VYSoO*(3} z`MnA_7D7B}U>tj5vnGR;F?Z@Z9!lZrmDVE6JBF*l-@Sux@;MCt)eCy;R*CDgvgShV`Qp5FW13@Kgt-iPB~IID3Xe1O|4f0K511bsGjur4fJUGY#Z!NjD3USCmqdo9YLJ9B_aegvaB8MjtW(OmxR5##Kvc>?LHl+dz5t|5Fc#w_?^ZB#_S zL_2}j^5K#>i){7jyOTU%<*er!0HPXT7M#%zAbdp=?t5zKk1^o>ZYALDd;4J0U_;-M_R#Bp#L+WA zCL9D&@6ul3-S$N;btb0r%?73e(iln6) zpOZI`VcrQ-q?g04yqB8aU{YKPh+lX>EQ25l`Rybd$5p#=q!`BD@t$E#6`Rq~H!NNnBHVm{qd@%0_vBe(Q^dg>N4 zTxNjH4&It#;r#QFdmN6h5*fFz${OTRYp9*T^ge{dU z%S^tBL|JA8gXSagnWXs!Rg}R11^bc3KPve@5EcZn`)j@*dP*I{|A)r-5y(ibtOg*W z|HE?d^hRytp)3&dYmj^{x4gCA{7W47y<>4DA*(cDFTa}C^lGw43Vr0rJh+T)Wn1t z)trLY23)i%wqv`+iWW+%irsn=qlqFVb=i9mG{_S7wtl#aKAqmZ)c9sdin8xNd)vc)sT!a{Ix;Xn+zk@c} z5d(baVZfOepsb15!BwqOU<<<`VI~Pcf2Rd-(IuK-rNan^_{>KSu%N?&W~j{>9ZF?} zFOLj1b_8(t3=m$xtm$_e?Po&T+qyGjUja*o>ok`CT0jT2MNRA!yXv%2G50U;GMf{; zuRsC#u%s;%1T+U&5N&}Fr?Y9iuD;8Nc<62*#R`K!;hnJenaN-9916n0{Z1_-T~gQEL=O3?lBcjll_6^g~V30F}@vwAy0Jp9kHV|p3j>|&c!?hNqL@sv4LW+!P@CILLXodfFD5q&^axD zI{ZY95_Cr z(18-5jV&MaC9qzMW((PbT9hT4ii#IM)I+>l2-YGg^WvPD@|WQRU(f|CPa&sGP>OPp zG%(bRrhzFeil$Jcza;vp2+qM*ifc3AOMY_v&q^LyO-f&&MUe{b4X)HI5vyfDht1tt>*&Z9jFmz`D7asqsfKP?7YP24Fh zyq1U`*5>X8HwT*09oe-WI&=ugFDGRdn9M9N9v#*-JZ1EA!J?PLc#Gqb*L`&W2b+rE zlO8N2okha*<~a=|czT1%6()-XK5z^{l;jawj#-O};*%}-wG&F0)my62I+4&t34+$I z&;V3XY6^ZxX<`GM^;2D|RV!;>ac(zh2+KKStu-=0x?hx#_BnT!sWuV6J~KQ*yH?k?S5QgRv$ac*kUk~~ zresnN$QMPBU5U10!NzAjcL?eqIT|IXKZc-kh!f2YX3Zfo7C1}TrmICaTu9dWKrute zccY+8aZE8jN9E^O=R1)p*x!(=5=TYCjLtVGSr2?Ae^ii(*bOUbDpwEza#Pbzh}Eza zDklNRpj4G4y2@;!WGT~MM@Ak)sect0#FCcz$EX3jdGV|;{K1ctZyE4eN4%b%xkA5L zLC&EVQlH$}ClII9$y7>wc^)k}#F}t@JY7_%KgaS27xmiY1i5oGUA3_eDnIJ|*J#90 zY*9P}n9fKxMvriPVo7WOh&Zf4q0&H~s!o%#_jNd`+emcX5cqk+jB30MU5zYA=vV{K z;Q^6p^!O9RL4Fjzq3YVLqMQi056u$ej!bet7B4|t&=^j^S&%$T1bBH2-T1B&Mf>=)>ODU9|rHaJ?H2Z&HD z%ukNcVtOysL21lS2$7(uoq@Ttf?Y$+EO9`d0M~hJDZ6N<(XC*Z(DhvBg0pC0#2TEq zbZXy)QNDjK=+~Edi4tgRQ3EZO6}+hK1Hoc&vUBQ++Ul>W8+ydrC>tp6%2W+6?YZAx ztKtQESVKJPauVkN-})~|bIv_<;oEqjZy7)$caU@3kP>rQpp{|zenD)0&Bmol5Pu?z z>o6%rbY{%rsXX_*#)G`tLLLKHdZS8;uwfQnpntk!m?OksbwKoE1&JD_dM6epIz0wV zSiiiR)Zq$tf}W)lO&#@y4`wHE!O}1UM6TonqX!k@px!0m_<+RwxYQ|8wP0?{DSiyg z(4j>%NxM0|K3V8Em3LxqeuAO$5=Vy@Vu`#V%h;jjXR4tWqG#q5Ta|dcH*OS{SBS1O z>=PdaBFp@S42LF~kr}2SoDx@uD7lZ|k6M^ljSy0ug3qy;Jg`OjJ`01w#>Wg5sywJ9*`=pri`B1& z^5koAQXf)sl==zFrt+pOjn-ZtJs-!@*+cevB~aAQ4q3gc4*=}v4dB}9-S#JE9r z3_Jj~CS92t^Y3rhc68x1|nIpB#RE>L{89A zBn%SDuVH+p2i}b$2Ffv~Xm$1v((VS!z~lg_{g=aQgKG8H#uXw{VY_8FSB5qE5;}Mj&?Mre@y4x)d^XjB_cUAyA zv*W^)Yr~8RQ+W#yMdA*i-b1vG`v4mfw_Is1)zEctRnf&8Z(_gt zpmxNZ3G@t03rczjnDqR|5VCY)L>fSMsnn`q#{l=@z?<>e)d(lFrgOs;K^XKC3b1yP z%E7#;Z4(Pmw6~?3K$~nhaR%Z^L>faLuT~jQb*LjT9k7Km$Q)Tk1CU&Sr+0g(D$?4 zJ#dt-?FYFx^R*5%r=F1eh2#hON@rb3_y>HCz#uynx7OdxcBzOgk@pA=6I`rumtfKE z%7yP_aUEGh@^AECIKNTkFz2N>`>7}87nY)$;$spWveD?)j%I>8leGRBVM4A0a&Iga zdFk2$l|lSV#B(hW42!J(g&sidxTrvM z4hDD^Q(;;L_9_!dZ%b|)l=M>|%YOYkls?-sXdk-aLuoL?anvGq{W=Bg%ksK%vJ*Zb+UXcS60YRGXDF*=4h5I zez#&wA0O@&IZi)+Okd*&aOm^2bhO>#B=)3lPKs(-vi^N)W70QvP1T@ZN^;4e$sv$G z=I71M9N(f6-<=)#R}$s-U*fkn@Jp>oCkj0i3UzxXzeu#zRKC;(hJj) z93sn`h!~1dAp#IJa28*Sg!B#RI%?CV+la* zh-TwbyfyW$PdA)WfPI5rLggi!zliglKw+XS|BI_{4(_b!x{Yl+6WjL0wmGqF|6-dH z+qP{_l8J5GzImSSe(!th{<*7m_vxn3k(_`?I$$RV| zAR2-wOZW>aLtr`e@%e0j z-zgD`1nwwbVROW<>S8~~5AOMA@)6yEeMVjGkvVoRF>-qxAV0D?j*Yrg_;)gnmAkX} z_jH}3t_t=#W96;^M6a1Sg$&P7S^Mho-dm=J1=f498hbOEEk$)3?3wuNh&(nP=k-}L z7R`|>HW>pa%|q79YPU#h$(aULdwI>2h%Fn#1=epKjTeluO?*>a%}1O|k0`MXK9gpb?F$AmdCyk0VPk)3-D#tl9B_=cdVALh& zC$NCvUeF_!M~vbs(x1tomNXYv9_t@&FyTpM(de%`nyX|Rtg8h_Ns=Zr3`)t<)mIy~ zdztHe6k7|AJJ&GVdXfOWDDY0|vOjOM6=t(Y-C(O3!MJ5=|!<*ow4UTSc9au}2&r%Km4qmYY zC_7PBz0jX4a{S%?)CvGvjCv2B`!p<+jdNzb-1hG+yP=Q_m(sfvNw(bsdXjhqnj6s| zx?|g(eckErQ0CDcu`(S2tUt<%Hbe!6kWWXu!OVD92D9UA0dI4tP8#ZCWH&ED4M$7L zw|d+LD=&xkFQabx#HRzttm+k6dc}@rjXKk_sxBSf3}b)ClQF={T5YeJzW-WT)K#wI zC;imHl_L68F88J+yf@~HVv5!a!Rfx&iB?j{=asxR%Hm2t8+H8J3C;w!iONLN>6wt}F^^ArBGw&GVRjvBHx{f(p6MIyfWJ%0HRKd(Zu0a7Gw zp~E+V<9p(zzX^-d-dVJIV`owLWn|wugI}OmgUdJV`Zoa3sFz~gSmpUf9G;+M!F%Q` zq45iB)WuRUrCP0%lXp4uGW(Vq4&zMq_ys7}CYcv!O5HCmRA2Ws((mV7J@gI~zih&@ z!PQ>i4zFb|iR`{c*j!rnG4+Em6QyEE@-l^eIM6k69{P+^Ux_9EW=U9@6n_-VRXjQ3 zEl%&kn-T&#Wbp&U`K~#6xA7ixBE1uN?{C!?GFb0+v=a5dlvOALZ>#TQ1hpr5`acVK zgg91MD1OiTc9bDDzB7NzHXJMYCac|ALB%bdSo#L~`Zf7_xl*jm3$NbGm!i|{N-_nZ z={O^a%fkLj2!y9&+LG2N01&@d8g zBzhXViXH_VegVpnT5YrUUj9F|+!n$(5c+>{J^616PyLHdF!VR)YZWy0XXLln{lCh% z_GP>|{{nd6gl@J&A^X*OLaKkOtX{MXc z`}I077HQVJYETd-v13+wcD;3d>u=D=Tttc(K;NTw44}M&DB_*BkX;<{LL~p=``@=k z1kc!nyQ$@a?NDE;S9`0S^%2AL6!S6oJ-ax#TVa_Hj?LBML ztF@g5f3_K5xFvRM6$g4pL+(Oq#!_C?YH&-1i45(wvg#p}1ogMl>Bj_UwuIH_2y0!( z_njm!VStMuDQg}~Ze{eQ{c~%ciq`;4L_-r99`pMMYhH2j;i$GWMnD?SIlw7C7~(8R9-L~>IbUuiiKHNp8$MSD z8-B?mmkFm#nE>8V0a{s&1H@jDDH*1eIZj)rULhuGZcMa{nOm~N89TbT4gqI?wHxw& zz(3_=BIN~#1bu+YgC}UvVYG@pzU`Al?cjsj! z>dZ)79EAb2GDsyd8k&c6uVY>h!6lZ>5S!Hp!w1(tizJ>a2*<*)3i#3zF!)A#g_1ui z)hi5o7R#}eVUX)(4kmF)s|SRmN@3t~17LacXnjCdOV)-CMP=!(>3x7{-_qs;b+QET zhEG8blE+`i+45AuI)9d6C!OU(Xzk}-37x&U>y!-L^GR^yp$)G^U^38anzuC;6bNIv z$H*Pcp_uIL^M+Q7^QtuFqQa_6nkyeesHOB z{^oLv4ni)y;Y^U7l^-pp?R9XHr?%x#%#U5 zM5_(s3%|jwT{{%xVYlagJ0Q|mQXRH!&3To+z2a>ksuqx{FBc#&q&)NL}Z?66#u0sld(owV|~O?SKzFUFR-a?rMS!VG`y|_r5dJ zBfFPdykK0&-l`lq0dS_BaRlmP*6Y@N0=U2`zn?i(hDgp&fm30L(_mH0Sjl&6JS$bX zd{32wJeX!(wmI)!C@1seo7zj8wu%Aka~ zI7^h+CA-<(f~Sb9ah3%J=bim_SzHD|8>o^VFx_PR86f1Q38co7)Ym^mpohXNTO zU8`}vf#TlZj?XfxT@5BPeQ`L`DNiCt(ecyADu?i<(%BbU4-6j0KnE&*7LA^^t{LKE6|H$=un60hqI?Ut?vB+)~-|3+kxNdg)7! zXGtqG#GKVpa;{#K|6mD&hD9kk(HUdLW_==Lp)NgnEau<KP#9M5CrMRC^(%{>QI@+qTVj31 z1cz4d53x<8XTum#wN+kggb6pp5xSUjsU9*6JLEv0sm4L+?&eF*vOwPujYUzQ`a@nu zGACImF+xZO&81bt@X!=178OuViL$`y)wVsMt<1fZ4jyw%%_3D|eH?Qmb@ShHAj+vs zbUn@3-$QKkzNv+gjIp78VEFIy5wDxD_PyfK4 zElA6#C|h8lu(Cd(aK}lH26b(C2cwOY%DU%yzLO!rLYYGRdmJN>V=- zz8!Q45W&F*?}9)(KW$z^#UnzdadNNGB>uA3rc1&C!M5V z%YN^ZYsa6Crr$-8%M&LfJKcLL9RZ9FtzLPOKVwj%l|M1fNrB_Xf1$&c;6w7)bz7GM z56T1$Eyze)wmuFmI9s;Cw1VHr*=Z8SP&|0Xh9VS5(~aG8p%`7J(B2Rf&vmB`Mgoe2 z(XO5>_r?m$yHkU%vnSEuZE0di0*@4$%ca*+=k5ULNB3}ob z5<8YCMO$;8e}M6jG=T)Vo^)o!%X9UMAr03(FNlSvl$~gd@;B382a%le7q5L0)|@Hk zd2-gML|)15h(9wy3XH4x${e})W&&&&D^6UTju^PzHt%1-o9@m;>D@YjZrr%PvY&)D za54gbp|1C{r-~r&!Tu_9U+qA~ci+r&3lzNid|uxlri^N7`p6v}4gJX7jh3y_`=xR0 zp?LJ`rVGKh4DnZ`N|%8SObb3?hdW`x;>=NMTJ_5>sM-$JPmSO%g<4Nlvju>oM0|f& z6ZA{+@rN=>b+%mI?0fOF-Pj16*VOsT-QgsLU}x1G&%4<^mVso?%vHk`=O@FD4HqHs z#3yHV9BkmNmnQ0-1zesL(;=o?FmzL9AW+lvGPuojzj8xm9CiH_4QW{vP94XZ%40!+ zzeC|mwZQAxx4{eKik@wW7Q+Ch;C_OsB#Uu?&FLwPwXy9HgWuxrkQn!8+H{UI-)4_8 zH~$*R*e%qj(oxN`0MfeUivo|C=9NVfl^X>hu)cV1Y-?BXavy zJ%tFylmo|6u1X+Z9LCkOEXyb=P)UsroVY6f51c}Iw+^d>$67A@UyI@tHyLEtyv&KE zCXgy56;@pQ)R&R#=+YS;@Co_gN#d+SMLrGUEnsa`*oDM9i z|KSKaV$f^NX^-viCBD0r6N}{Px(%CuI{$CMFlf3#+qF2pB7aNY$Y3AY-Frh zfQ_ZOa17VmWN;)?&P2t9+F=3+@q$!M60=YX%qQ{JXa{kG`GXsJQwHM?JRHvnf~_Ln z0IUEsa{^)SG2;iQ0D5+gqId#wr}q(8gm_>DvH}D|5&i1!`U~>s&Wpd)4U$s^B);Ue!eVdQV%r!I37$B;8z|KMxb199W$Ga-2TwyH6Af_7I-2>$pW z)-^Wg0)Pw-rFNO7nWuqqoudwI3QTLY`ZGIL;*KVJsHq-K>{Tk{s6YNWlSzJLr=DrZ zzCgnFOIrO8_`Ok-U6J%LL3`3= z$%HwDPmZg>fnqz#Tn`L$SBhTl*|AA`wq6k-8Ec3h3!B)$`!7FWVglC>RSOvyup5pd zChR|9Jv2`qI9Wdl1?8<OQ?Plp?o+DZYG6EJIVvj z{m4k0We$at#!f!$-;LZogdbCPxNo8_wpZiUC{NkFgQVS8#SnIiMA8P&eT`gmg7Dq7 zTYK8swRABOjcrc$8jbj(*%9{J!D%xWLl?=AD1S7~RxYnYsxj4I&wJ4sf2gsKZE+cjJ=;0NjlCg=C`Hz{9xZqYz}Fuy zSp?2=(x&{DO)h5D^J5)E$pNkt^%|p!tul0N+68f&rEp21j$|GL3{O!$L9qZ{pAyPj zcf1vFwtlBt*O}DcI1MI;JTE>0JyzJdf>s(Ne(5C`$6qCjo@C%vxMfx`j<{l;Tuu%^ zwb>+H(hrCvNiw4e67FjxJDZl0n!_493y5jQpIZlJU-`LhLQh}$&1}MZt$|Lg!=HJD z*%pQpjf5V{ZP$WlH{*A^GIRneB+hA*`+1F|Dh2pP)l!T}Vw)TAqZagMwO7i{Pr=$_ zm6LR%A(!B-Zhx3~Nog)eV_`dKn=^I{_H&{$y8L9>J}oAXDn`?dnznDYK0$nd&=epM_)Ut!)$G0D^1>{DCoccQ+WLQ{Edn zIYTt%ja;_vJ9>yNai9vokp|>T-gEC>;qDi-Z2ND8l=xt7-t)OnJ)M+X$9HVw{~Fv} z+JQv1IQDn1clCoMqLNI^*+xww#E1352-k}1)O4@CP2!hg5hw1UN=i>B7W``;&Lm9B zvW3jLFi9kJR3j-b-vW2?(^_IPN>OoAEiVEGXaL=r4a=FpU_t2vKG-cn@ZOZaRh4iGP5!}-V|MC=6V3eKLrE(%OSq(Gq%Pyt zSAfM85|UmHn=ZFcL@t}r%b!<9ytT<%*4v#A130gJ?bn7bg8XZ0o)o$z`C$?iPKhbr zW=vxjJ>l(#XBG{pv?ij^rUKPSY0#ZzTm-^TH2^Q6!qzHFnn;4nq7<5DF)~2TuUf9$ zM)>7{rC+#t=ty8f%rh|gI=|LtI7`pp){JHz-7S!P-(iftc(fly%8+Bbc}2<-d*GlX zd0jRN76an+o&`)g4(%9a~JCd^Rxjs z%vjl$5?31LG!C|V@%V!A>hH0JL(Y8Oin+9-b!ktFY$wE-MJR(j7A&t4VGuFkW;Y0y z5*n-@6jq9W>KO;?{5cJmgdy1Y={>NGq@k@~lTtc&mdNA@x=PA*@8`3*pk&%$5LB5z zW5Q*Ghy}a^f}*?$Ax_YZW3uzDXe)1$qwDg;CcNq2R6=LaeFs=lZ;nEH26ZBgOgS$P z=B~+wYvTE1Lf%9KM|i}8c;1M!BozjvHsBa4Rr*7~(D>@B1F|zWx+8u+H$dLP65OZQ zk&R}2r{#qfU3pqC>9&j)fSomMW|RY}DaoBPa3myt899R=HiKU{+bMFG^bnzj-QsE3 za;r2MPN$;uYJgKM(~1SH%fG&8{LUo;IyKwE3f7bP$p+5}b~$=SgHz%oyE}@rVkK5k z*QyFQzX7IftwHE~RNpC)B*P2?yIa3l@03WW_IcS9YW2X@To|PmJ)=js+{dtnDU*Gx zl6I_wf@PlM2svyXm9w=e;Bv6%yE+G}Ezt(oA>P5whp7jLT7m5=7EPLnv>*m+fP?GK zo)KiOB^`UpLo6!?MSo|sF;LWqy?%o0|FxwUIN@t zd9g#FxLb*;T0d?<-r0J zuk>P$9y+kqIf~aHU-m+ zlSc>CaSSNT6b^G`DgjyhWOj-K?j2(bH^pw1Y!z^Sbg>HFUfP*e8IFrgVih~?y$ttz z68Gt%G@Ox-dw#>bpn%0iA|o@X-lGG`^3A+6yAyLc%cQD5RM>yZz%D)?k<+c9h&I4! zl`iL!AzDEu9%}%sozEFhmK~hAtHJQq{bMQwM^tn7()aR>24+vTwG)q9WoN&SST>_v zB1|pApn3NK;eg7xKhf~yD3dKC11DvNJ7{CJWM1bul7Nsn7bldsB+j_#&M^R3LJ)9^ z>yW;13)Xu8hnrJt(b-P)27XFUHO^5VzW8-+CrzAJJGdiaQyP|z6?MPF^|qXZ00;Sp z+1a>Mfj4ITSA7*z8{l`6w8=PAHzC`b+Va&$nB!W4F+cO=q?lud<(O$gBp|%BfQBN0^L7b5g9?6T3vE-9{R~5zcuYX> zRMi*B&^t`&*#N9joGO&CK#hsT8UjC*v4QQELExKqKJy4}OXKS)@Vf zTq^;PQ)!icfDDR=MP{{91Lc@MH1(eHU*m`$O36PanTc@=vK-nsBNmWT!WX9>ekm$} zD=Ngio1?ak!{*~P$#v@Nxl`SkDgmUM9$(s@4#V4~@Rmr`fz@$Tp z+VYeUZGMcn`np$~P?$*r29OdOiO4Bhh;FTyzt$d9B7pNZQ5Aiaw1&K%5w}6z6OZC( zwbId-pUzDrHzGnEE0OG8)Y`6tAJ8n0d?KYbOgh|qen#KY%<%y@u&%5NSmL|NrC*RO z((kg8tJ$e1oQBpYs641bP7cSDJ>L$^LrHwY>>9j&ZrtDyRx=M zdGQWFd%rfS4s@N?H5~CTBq(bgj3uAM4Tby(wnrdTg z|D4sXaU~9QBEKS3_#Q!2gZZ*UBF*(^FJLCdBNKwLMg z+Z&{+PhxzB|bCK!j<#< zcfyt>x)U9j0W(VwQ^V`fxML^7PWJgIl?hkpt7S>$s!A#B{$TUFtH(xhN6hk-E@C$muxD&7V8B9Y7IFMHn zd=MfkAz91Gj5gH_y>d&C`f8#WguOY*q%A!r%A0D1k_%5&!1%F~kw8YA_4^yQeTJITQRQw&b2 z;e~GjxdSPO{^xm({`dk1uR&U&a6mv#_^Hd33k1Z1^-r}MKb4`G z7rO=24AchppT57z>H;GqBi-lUZ(2DuRvnliHQNysDHW!L7d`dG3lsq$B4RG6P~Jq; zNN?DrM(LAF-6;6?59Pfr^x#D>Vk667KaP<>=YhxH`8P-H~6QZEASWH4D}XMzf!zTy-n2 zFK(l9sYqqU2t7mGpm#U=$VaprV!H5A!qB_XJ6OXBudVqqrsw?ibxcOLB%n`ni1Nm zAI2^e%~%}Wt~6I?tB-&5A7-RCw%0Xi{3XMsGZ2L3TgVg;33GIiPmP`}xjROVX40G)7_N;yNi&)A$jfH7%O0ua zvxHpo*i8rCQE{r9$C4e8Wc*5J=XNV#-gT%iJ*ZIST34#Q?zBHl%;L9Sp}L!aG%_!@aO$6M9A zv*8<6h)m+|9@3bXf8VADyy|!t3H@sO!!G}7J&GUsIIJ_v zKfsqDP-3gEzfb${rV-_HF`hoCwd@&PX0xYG{sfcOPn7cfru2@#n_*-}(g=A-Wj8wy zQf%KbadscSWWlSSOj1+14Yd#|E|y1sQTC2C6j=0RsE}Cn1d-N~)xiDGl=sw>AN5S{9UrPR zqx^_u8)H8=u}OFk29XvM*2ri)9!Nho>H1+s<4{}QkHP8{1CY8<)6dR!S;f1zyQ&4aazucQ6>2?gc0O2U zx)DH2k9uM_+~^cjRFakOi}vM-(^l}wXF&Ff?=*Ij%pwfRx>M+8!Wi7pEv@;)4#oWZ zn`Mo3;zk}#iW46fMoeC7{HHN`N6Dby{^=Aog(wcknYq15-c9zQLn!LmV;=Pwx6_j2 z6)azLsqq60tU6vJBB__SM#UL^26hG}%mFZ@OJ4^v1tK{!`dn-@&sz_XV600sTRv$E zqjJG|FFBw}ydqqrvl&}(rDX}3up zjMk97V*$1hXP?l<&OYoDeoe}ze<@Bj)j`Bf3@ko+BD-;@oV91+s~AZ={#o*KeIKCO zeudO{7&DC{3ySXf3w6;jD?~eN*1KASV-9SbJ%%}Mq5_8&=QlwbrwHiDNfpQ$#`WKZ z$UMT6Pn?pZ7F<%>0SYiJ%dQP0cd(_#08;H9h02CLe=Zzh?BRXa^|BoV|Lj<5nps#= zWm@enAHmYIx4%nV*&?`?Da%1qAaj6wS8nhl|C3DR+W>CtSR)#a!r|E(_K+GdSBdpN zd1gAN5P+RziwuC9vzsp*_hQsOvW16CEoSgXNLbZX)a&GOW^Y8yMbn?4{ccV% zuw2_Vbwfn4w4KhPtu*3RE|S)emoJO;5`WcBXEk3x=yYW!>_M}nTmSa)5pV!byH^6I z-y55^YfRP-cKFQBj|lwBNRQ0{tC|3 zbtZ+G-(WI$i}vQ#$n)8DXr#2YbGY~mFls^h#fh6$Qdx5$dipRtGJ_MzwlryKMaX#Z z_JUYOX`Rx=mS%S{3K*%R#n%82b~p^TF|09|^>wBm!_(!QplFK)UP6r@VsB^>zmlF& z2uPJ+hB_zSZ!-g!DlwxbtQy2F)JeM|YGz%#G)R#u7d&5&;;A=L4{I7^$^SlV#kp$; zq8&XHbYDWayvv5$l{Kg9dyCg=%9}JrkNhp#!f5T*J5^Do$=Zs4rq2fG&%^2{9W=VN zOSoO;@~E%#*`+5NlY1k|TReC7+NhH-+^}ZnMS>;^dz02oBOI(dgrf?|&9~t{7Q##- zs>ukaI#OTi=zN3jeg86cP&i)kS(as#f?9eSbt|9X+ zC|9pQ+IyyZ8$5mVEm8P$G3FXMAZ2HN>xR;!PJwAAl=8ik)^Y+qDQhD^1gFScd9urf z{RgwD9mB_I28k(NYtuzSk`LewP>sKq*_4aXcrg-Y{n)K32t3t7@vO43+bEe(*M>+<*k$M)Zawe&R!%Hq8TT!Nk1H z_S?bkW*Pz1ce$tIZ8C#axdbcwu>k0GI3pS}6m<|L2q9Pbs9&O(pP@~}eY1Api{ZF` z=(xGgZGOfsN3j{=t`5@6nS0eT`MIMKkwu}0*`c-UvQ8uIuZg1i1~2OGJ#d81vFk0!sEzU)H0OcR{-*vc#%u7v#g+lwHS&%CDUu_JppV^G+YUuiN6zF zv$unKRj}FHcVRb%bx58a4+H9IHhcY0ElX)QI?! zSI*LLf_^UL5`2a9K%!GU-!|X`4Bh}v#lrHrs6ch`P z^{wxOc7W~sHDznsfc=A%v=*uzB7O>(Ci^!Kj!@h}oPI!fxOnFIG&zY^VMC$>(uIK) zpSDc}hUJ3Z@MiQ#{v364ijtR>*H%gM!e&*oRNj?9gEYH(PB5>$t7-RF$JJNcfIL6= zwzES%NNR5zGF?&_9M%md;YVIxSUes;VyH>Mav2AwnZ0N-$W*f(`!r&3l_C(W6?~dL z&Ksuc=ZzMLg~l0pL;#yE$mOAm%2OT`88t5@#_uId(ud|9kse#y`PaSnpB`4!`9mG7 z^Ko06xobGAb0E=GBQ|0Me)95SJP%A~*+;3-o9 z{3zj)4s?8m zJ8v<<4LnODpIXj|OflQvXheWNUQin=PEk>s5<}mlX|vc=YAcnXtU@bZj5Vv9-T{O#Za^E5}(Zz@QJPuF#8Q7t`d8ei{qU&i2qLm^sK$iQLC@MRY zJA8uXsAY;9Uv#$UHT+2C9VFYT1A3i=1MEAes& zT)4WvW9MjnK4{h{~bgOl@3;j#IfDx&uS@e$p5Oc-;DER&I=6-RcAw7 zRQ{AieyiabYh@$Z8W8qSKzV0WE!KcP`~WY9Ppb$7c@zP9S8G&&b4C;b)Z^ph;oAa{ zRT7$dX)B74$YMx{#uLax-$~fK1%7YY3`vI$BRQ6Cb&- z{8&j#6JDGaf}A3KYFPt~8(Z=kS#o5BgkKte>UdpKaY9UM*^BF60D}QsnR{X(C7EdA zVXm~hR&cO^8sZM-v1C2ewC(2XG#OLb3>(O`0ooGv^4OvhRC0uFt6c6Na;lVqzKsD$ zxU}?UR139xWN%rpVySzEwU`q`6YGaaraf~?0(Fn>5YPS?FG<&ejAnbh^K_RxMPf;t zk5TId2!pMuosE?p;LNF;Y@>?WcH%{Gb-tL)85)op;U?W0dkrsUxpo|PIh18>)#18= z>{S!-v_o&%e`iyiA|ozi6gJja4zx#ced6a;tKIi@($rt3avopqLCKAmJicDB8`wGn z?Ky$Kg#ioQK3E8XC;`r|^0Sq!JLwS_op7qHSeqqr@NP#Cz$G+H9t+B&jqaU^&ff(V z<*xD=6nZV$Cgg1?l{$m&nmYI#Rare#KZK$2%6=v$$yL2;P*%Pq#TBabC9cYA3R=`6 z!(!rDml~`=0_&j@6HB3bK5&F>MTe7R@_Ij>c9RrQaxpt_t#%h2a=YQfkk^q%&O zCCwFkXtg~#=QBk`=O)7X6sWqSD70H?91CTBml>J?+`v+cKaJlW^fL?Vn@K?oTa$L= zL(}Ha_1$rBT_W4YVABhrSIw(|6O_L+sQ++|LX8!R>tI#VpS2!V4hHT(&C${zES?~7 zy-JY2XJvqDrA0V;H9BD~oOMGZ-m-nhrZTmCkbjKywJyS#Pb`YNPzt-n0{arlJ2ZO=i7O z?ui}b5;JqkaVkf?d}z(~P>Ni4Z{DlfJLj)44mX;isV@S-Xi?=@tqt#wE|(1%;GgoF zAX39;dKz^Zy|yKZzXa=fC5bVith+c2`66;(pE>PqpLeRu?5I>&w&|HUzVJP61+!HK z(CzHp=;nM|)KOLt9oWudFPdvAn(Nu+{KTu8H1Kj65;~Vur5{ zQ%hDzH`Q2=DYFSSB&4b`o{?L_!e%Yz9QxLOqCT(6tSU(;NAA+VRb4disf8>Jh7$wm zAnkaU&zw}D4S2u1j#jt(q<-fPlqbpqG$bnH}BtwlPHl~Tsej>WYojFpZHPBSOi!750qsj>$vP&pXLaXpzz z(dJ2&Eb&K(cI*(;TzH^Kdv2)ng^+pW{xwC~9;^SLv^u!^3uoz8aBq;QmfE)h*n6vO zHe*<{bC8?1vX2dK-TDAa2M^V4joyw#(69EcoXqu!HQx%AmiV)=edDFp9vQ*h${IM) z@sWL7kx4Q8$`U3S=73hu7s&!Fc_933!;bbAiMV}x8xJw`E3+>Ku6ipP{PqFjwc>r4 z$$i{zH%&aiHf>Tqz%~a&>9%JCpfJ9~Q(wO+%nnoKSbLeBKL&}@>09G;I<@^tdq05f zZH<`n%=dP!Am3JpnPcylGrjGknIvv1ZI`7s-cZQ#m7JNVAhA;Z5c@WStW(cwKfX1jQ{HdgLF zHhwW;q;&*KyS(du@U8e$t*{yP0yQ^Atw?*VQ1XmxIbuB7LqnWl2=5qc*O>I|H8+o^ zLM_)jN{TTAb!c6NT-8VbmsTa#Ta?u(-@eGgV?mkoe!o`25G-MxD5L1*&_;KR6wc9! zr8_KShFu)B(XdE2=q1VJlky>#>V?)#`s^%PTs7fl_i|m6XENU*ElnD|p_&`q6t$Ap z1g(#lqNKvVm^Uai#}@DSJtKq~s*q)8gg4A+maZgveL2q=Q77zs$NmkTx6iaDPd+_%OTy`p zfcGYGDD*I!;^y}YP>*g|Uvb`d(1y|9>0u+?m9=Zf_yzUJz)~ypBO|omgisA?h##Fy zS)L|n9FnD$pXAVV@I9PfW9lAMw|bkLeL7?1&eq^{V_c;jUbmA$FPP^72oi6o9yoi1 z)d{2%s48EGHutR}6X6p=GANV2!OcLCtqFEZw)+WfF<94-Fg_E!p8;cSUntYBR>QOV z3-DjG=U@1nF%eYbzoMqGDWHFW|AQ-A{sx_Z{!hf45Bm5oR4?K9%3s1&0t)jF$}Evj zAs~QtMr8F3d~<>SgY199?d`mnslIifXaHTOxu0l!Ye4?2kAc+5N<9@IA7QF%kbP1m z`6h)!Omql)^C-&ca!@iT+ygyIuYY1<@N+&CW89R74NLeb$4K2~F1wDhJK8b`1$r_; z+@n;nN#JJSs@bALOxBD~wbp7g#U>8Zj~XMryoz_MaNpJ~ew*oEP9~+>*gi80!2ygQ z-{Y*<+ss8v=8d6jQuOSY_b0yOGmXM*RrN5os;IX- zL_bjr4>`PEA&YQwr9J|p@p{QAgw%;=_ln95l0^uXo9-r?)&;$=Ywfnwho8G+k#we=o(ZdDojj+oNj)}T-j8VJ4&V1rclEF<5R=7c2L#@mQ>FfII5^RK7w>yTWq|ItEr7$uiV+?Pq8I=#Lbn0@txeu6kAywPenTZ2eE3 z<*hwZT5uBhSdb2x9tlO#f;VV2n6Y2~UD^LE%9MI6J&2NTh2IeP)EnLJ+Q~Z%@{sYeuW&DQh%7hmaaxQ9Y@%2}eFKQAK@$N{a4^ z+?kMt1(8i2gA}X}viU>?$O-(PxH-HOJOnF@O3hl+zJ!4%pY+A)0@(D-z4r&)sY(2) ztSc$N$5nC#5W;pjCJ!2d8H>@xN|ATo&pJjxbNUo!sv1mMUDK|mxdT(GE+K687+R{M z30yKsTU+{FzEIwy^`VYw zq~Fk@1A^dNI_r|;LjIb;r)|c>eAzJ+{p%IQl_v_MNpP|><`mcxr1LEP_DL)IgCsiu zeaefV#60I8Sa3s$bwNTOkB283No(&1@$tVCRT$+W_~k9x*^df~vWoLV?xUx&w;P3X zvKPm!6dAS>BU)tCM4*W9_ zc|H_4a~+V}=8`FFQd4$D)m%8w$lCvixEpszyb{bUhf5K5Ms=`t!VwxN4UCi;t`{f{ zU9<3)?3k&r2;nTiO>ySsVY5Yfu~}j@MU{%t6~x?lM01B2kg~xQWIN=D@fG;rfJZ}! zk?cj{L`RVir?RDEO+{6Tu@nRY>Plz(%}w^%w6JZW0>r+rb=2k0P9N(hv2tT&MZrhQ ziDB*`N7IXO?9t$CL=g-xv3g>4Mb%i{GrwYO{W&(eXUSeXHiKjAE@JA%GUlye8pSf~ zEMyv79(t}GrXRK)eqwcHd1TaES{}eM?JQ!N#4>nSFbZNCcNaH8OMX8EjNr8FxKhVU zbpMXDrDfQc8k-a%D*1z?Ic5rTm!_0W7wzkA?^FBYSRhfmNJTyQ$# z^&E}aQ+)e>sCvifzMeN;yK!S%jg7`m8{4*RdpBxq+qP}nX=B^|#!r9$^{jQydA(=9 z*|XN%GkfmOb*;ETa{gI%p1|uF5~ZDxDN9_Ltgf`9jkFUitqX2on-Bp}${GsX>S-F< z#3B8Woqigvs$2=&vh0l6qKgFVb9gjHE30JJ&$a&eJzsN%G}J4OA4VT}Hk`HSTM|DV zlqmX@rD|?b&%MT-4a@sIHp$O5xwdLdCYjW=u`_)X);@iv$EfwN@72A#MDyLVcfD%x z3etX&s*%l^qRsHjSmJ1!!pmslIJ3dac;LXd;aIh)U$JQzGOL*4$hZJFcx~KP&+Aug z8%E4(rUWwHIbdv%Rtf6cCJyZ~yls%y22YX$Wgo~c5UYVJMON%BIjo=OrJRnz|nSsl0{M}5%Q2EkB3FG@gc0+z@ z8QPF2>>f6EKDRPMIyR8SqLqlYVQ(c;BC5~Ql;22gd@2H;4-<)cAR=9?=OX5^%oa+7 z!H^-QsMu(gE`l7kEYo12>oVOKN`z&bt~OVYUz5%;XPiGG+i?s#mryKxpLm>6tn8w9 zA9;etPxsP%g2hke)psANhv7SNAFhYhW%MFa6W(qAB3cu+$qYc`bBYQw2j;`)6Q_~P zt(pB1H;IXGq*C44txZ@xG3u98ZBKPl(!&O$I&l7eQHa1krJ%4T31^jARO*CUa^2xT zU9LN+Y=KP^j)z@BW2HNLMXY>e9DS^@z*=Gc8>jpPit5aH9|4jN?gXlzgNZMkmn&deQ}U)sZ{mWb3QEc`+b|;yTJw(5&DG^<@04WzicdaWzSWVNeUn} zWKU0kmZUGRYcgbg>w{=t1e`7i3KsjXUho%>O?6&a7fWL9wvlg7A#{@8$f~a;PV=rw z1bCJ|1pnUxXR1>{8Ho4g%M5>tzaqr{>KoSo50x+Cg*8ZAnq%GC%wPiJAV>L%!Ym<| zpP&1)4~1i_-weGW;|Dkn$JlRr$___&vV66Rt0gVlDozh;Oyo*1@?4Kx)lO@EEB;k0 zZS5GXwN(h^{(RPAFU~4EW6$5z|lf>aoAa59OXeAg_nduiX zeLA7w*I1Qw`Q5?MN6M0xm5aN0Aiyqf;s^a-?HweswumWw9rW7~XwpJuRjIqM5Uw5Y zWNgGt#P(KQ`fNHq*pbA%w#1eFYJ#_!`ULvu^6hC6nTHeBVE&ar#LZ zg%c>m6WE+{1f8_L1t_@89xO&=vkl{Lu@VSz-R6>IF2=lMfX7Ls7CW?}1Jk=vqNSYd zTFcL5Bto!f@FE3%9931U>!Q{3vul_m{Qj={YG^g#K`=tP`L6$BxOH&R=D9IG)1q$J zg>;LUT*_BnT(y)aa3XJ6XOiqa`lBS&VikTsJqVIU%Nnk0LZ|z*-Ay0U)6KA?M}aZt zhj?E1TYu{gy!c-cNK>dXz>L4HffEOA80#BOc)0dx_0mrDFG7@PwH=&P;SA^eWr5f2 zjs%D1T`c@DF1cU?1qNpM7{wisYFsG5@7+Uq*bmD$Wpl}=A1pIU3egsr_hqG;4CQ7_ z!mV{6>thoOARlcsSnc~lbzPdEiUWg$PaiOZRVv2y;n1Z8MHN{BJb2^r$zkbK2ysN3 zPZkLw?Xxti1j91N)p=pw8pLDbq5L{gR!K#8j`UPbC9N~VES9oKgIge9E*J72R4ZlH zHL6faC8=}6jQ%Aw{6;&Os8IqZE)}VgN%2~xm?vTM6EmE`Egg;`Rvtd|0kWX6?xUKq z#N|0N|J}E{bqM?&_=>KCkA(>7-2sluEAq?9+C^hHiC+ch7b~^G^!D4ztSm_?y`I^@ z^U&*zMF?}|SO%fdj(M2_N2ah*fTFoRVAJK$!$FrRjp~f#&aWR#-2WN(`uzF|K>`r(MIrs#yptV1C-nyln& z_?(=80X2`rY+jMwILzXp62Tc5H ztDvy4;p+#H{xc2Jx{5!f7{!?(*$$Juvs=W`gW$T3jwZ)nj%McWDbP(r8!7$-#A+`t zC%eU<+NtbN0GY>>|1k%e* zT$B~{)6W@O*T6GIp$qWy`uWf6()d594 z@p>yvfP`Ek(ihs7910#JuHun6WF8Bm?a|}<2iCf7hfM1$4R*ZyZ zYf1fK_dQ;puc{yV@91AvTE2-#rNFu3-gJMxp8HEthdfNZT&SAbUg^dpm<&G8^N8zu zxSk!;dSK1-&DW$~U2>5&`^pv5uVU(Gx396?fQs`?E|{~ej~0)V{_Bn{=H{H2_p5hc z7MMM=*!;&27&B9U!p&eo&FwBluBb{C6%ebnDQT6CZYF3`eW&J{b5}%?E6ql&y|I~8 zH-48te9)>tP5_9c@Ga~gxpr^}=lrXF#WkXjbOvs;!nbGd zp_A?UKKI%g@Ga#FFA#dUi^0*nifi>M4_at=IJB9@89o+oM!AoiS-J`v>%z93hM6c` zKX89IYngZ5uJBXgE6p*II9%ZZ_8_$hExeiLoSa$qkYsvz=VWV|iAa`5s@VLv?IzO& zv-zi6_`Extr7A|xe&eyicl@%>I(Hah-KYi{Y(mQ#r17$Fv?2VVMU+O-6xdDwWs zEgMsgI7WpAX*loEEi^LgxC*`b@3ecH_)0j;nD7c#s=Vp%w9}~4TI;ow39}zun7|R7 zTS7zgitL|B=orkCXwSk+noMDM(tZ}pbx|Viht)1~wh^_r!vWM2Sskzm@$VeZXrmd2 z5g_5YX^C3FVbk}%5vrG&mhG{E(Wx=?>ozQ=g7Zc?$NqKn>xJ*8qE!8MQTR=D;!Pb` z(m44lb_IcDSr=x;bwac)cZb7<34owWy6kWy!}{8io~kM;TqT)7LE{pmRSMj=Ia)(l z(6XF+Wd7;UrcsoP&X~=hZ4C4b+-S0ezrIe7jjF0+qt`b)_@|~bcuM}k@1!f|#ICgM zK_RM~-oA(ryL_55eyCBR;C}7c>4A<{mFjZ?x~h-Uk3(`T3r9Ni(2F7#&%kE3fqUd@ zR=!cf^=x`^xtG%`k9t(hvjAp>XfJs{v=m~@pOwXaCzg|M5zl0GT7nxD?H;{OnA$@@ zditsfHC@f)2RqS^290g{e`Z8P==;0W%RFeGrV|~@PevFjRPRT$56RhDVo#p^#5p;; zW@Y_Q$~I#>k7vFSoNVaG>I2FC{H@CYKY7M~!>it~2@oNw3@PtF!TCCcY>|(ZG-_?3 zAS&FoD+>M~@Z*qo$0pl2Ko8B<8KR4EyClUZDMNicYSkHHP%Bh5;5L6_RnZACKaD|N zWtk8|a(fO{R3yCO--eP>9~l{- zjlsH&3pI`FB3|`NcSSwwTKVDr5bZ>-4a>cd{X^o)@hq=Nv3pqi3OBUqg81QI!opMP zzOll?Jvo2ol!I6=1;VmuM2iGx&I?{ZQKDTwu_@b`6PY zhuHZ3zKUxLSA9YV;bm%&D9gtpMt{wL6xj85rdWoy(H+Lym4~W9Al{MEae26 z=|Shz)#$e-JB?4B;kFJ)#!Tg{B1S!Gj=_U|1xE0h#-iQtf6qZY-G+Tz@gtPYznv%X zy3-K0ueio_#(_I8mW+A7Jj z^VzE;D|$UF2Vz#i9Tp?F5wBK+dX_+0p}^5lJsy9Wl)=)Xa|J03Ji<_s*6iAEgkSa& ztk#*6u29q98t&Cen3nD*M`@(oU7L0VX>R!tp{CRE%z>tut?>4aG08o9GDZBfQ$e3V znt}>=zv^9V33uEE58?=?IN3jK0|E?O`8{ek_|X;+Nfa{wK0R-oSov8x-YI@3V!gsf z3GFvI*Kijf zP0d&MaXRvMu zc3^Xy&+zJpxx6g%EFCdFgTk;S2Or7_!Xl)n`v`@$2S*IKbeoXN-_Qu;t3;kRBWrmZ z2$>S?a(_QczfUN54cHd2U*K+|dz^OtccABV5MT@m(EE<~Lx2QVnDu%IMloYef8l(W z>>Z0`+QvYXTiRwr#IkT`hc>sk{z{GEPz)WjxJ_TxB@sA;M{mfrJY6ldXrm>{?Tbnq zs`UkWj>;X|fbQ~FL33YShk@N05||mtA z$TngzsRYJ&v7@&HmzvgQ7G}cH<-)7=16mqT@3nZ)y-dqlbn38OMxVHQge8OUz9{oH zj{H;8U9|rM7eT|iD0bFcuq9W&Tq#75`d$zXTs=7a$8G{ixM~vpgdMY)zIW1FTjO z%T19jaS47-NF%FDm*226@LI57oqg+R_u<^m^~Tnj(`tvP_Q~}66*^(f7F?L~I~2XR z>q=f}Jdeh-;8RbWE=4L0?2Mib50s=d9WpUWOS!v#bnP*UBK`O)tGA3Zpv6=9Pdo#VDfcYu`#Mt81;TTkATU3N?xK#Z@`ei7VWa& z2g_VG$yIi6IGwL>$al=PB3vp32u!T>I41Hov-rob0cA`2u^bjidDZk=nSB@hzHIYy zHoE{S%QSC}UIM)?wo&JF#B6i?+3EA&*dg10Y{JwFDLnqlTl}@gHze@ql)&8neSh`N9^FZr4KSDDeQmI z5yk3CKL;2Hq5de6wiD%>+^mhEwhSO%bF4!W_e~kKU`c2Xxu2nfeyml=4|dal@AYA4?G63W zgf5U${ke4Ry@Kr?@u3s_1)=EKc6s|kvd889ULZsHdU5N5=3V&+_pt!Zgb9;4V>>9m zDiplp+az^unosm~w`l=IoJ+$TC8sTn8Q-+EoXw_9Iq)UB1VBH6g4@R4{^^0})^}#| zDcHYo&BzC@-#>AU*$KB5|J*Ila{fm$!y00tw@8-9jNOH&-&39bvy?$If!lg-F>Cyfp`z z_`!AZ4u#R1B7jz0A7hh_A2|l^(#b;Nwsoy>YvbPgo9PpA(U$VPH_3^+HrmIg;fFx( zR>|s>bJXGUQ@szZ=ZOl=dUzY!zGN&@9~-P0Gc|618h&p#W(1@%F%=Dcl0h@1xU>U( zV9=jw{66bFG5y{@Xp_IExKNvSOgwN(deONEvxmsGO@T~Udjp!z7}IO_Oyry$a1dU_ zC}#OwfZSyN>Y&(;q9p#ouS$Ev2F*7rW$@J&^jelEK?cbODt9gO%owU-+)id!2jkT~ zs<=a^T5;!L*n!ON_}KNFyZtKm_i>yYRAHI5J)CMqh;_v(Ls|>89REZdTMhf$2uOAO z1}t_QHb68TnP{aHT3lTZX~b^*DVrvDf_nKUN&r_ zliA*G5RYQ)??Oh=*+#WhN`qJgHR)J1rp z!4N}ily(B+AFo0U5_IopZiH7hMDh?i#6gtbO{z5|Y)0f<2|0GjiHSF4igX+8@!8+T zAC92)cTGq70v$qcUy!tJ<){Zkg3u_GG=MiRHuTrA(|VC@buLi@S*@upg*3F+c%VU_4O{d`b-2t*R)fQ~cG1Dg7;I*K6M5XL>7 zJId8&KIIdX>Z2Cgey2!bk4?8@YUz`81|D;z6qdO*-jPmJA{@7gbSg;QTCic+6+nW& zWtYOFRMw%)K)?NGEhBcNYmhEUt-z^PWegokKT|A!4cf&+kS<-Vm2TvD(b(FzRWnq# zg5F|s&P`Gcw|+Y+`Wqw{Mj0|z0VrG$+x{*qGj2{WxKA|4uIKFt_Kt=R?BR}mE}XtU zj}Imvh=+b;O9;Cgmc$1h`r4KYAK3DbxJGuM>4yKa=Y9R<12%h5a3BIC&>0K_%U|1H zL-~Dqy%u)a7Eg^KHir5jJlZYkhdtd_JC*zKm+j_p5J#|x7JP3+Ij|S>yeN^5>p&

H~J)yD^)9n9U7abaBJGm7|8FTYW6iG%-u~H&m#s}WXNEUd{ z-=E1X*xQm;4;G;Mfr;;?Y!BP_hHUl2t-}+72yI86YB8TE_=RmEMpig{ex#??{xmcq zZiIKnGk~(a#=1e611nLe2e!?>4%&hYI!WWXMR@p}{~4Pvs@^o^Y|7cbc4RVS*ZpEVI=`gu`Fh(j-WwdW11X=DgvVLn@*&aoFuWWef_3ZhZ%la2|Hcvv{ z8{7DnoJaz?n)!4)7ko<>RtzifRTuA%pUu?-KMS%A3%r!Xs$@B7XEI+TTqr(8#p{Hk zjRMD{peGercBm=fqm_qyeN5?9X!df|#)6mrIj!hCQIskBfR%BRR*5v|6dEXpC>BCH zH8{fv7I?oBi~>oTK1V4UIkjh<4u8&bNn&d5Xc$JHP&zjkicrift1icI7A{=qCz&vU*9lrqGb*m=ClLS~bqL*%cCS8rE_8S%4$p)ErjpLu>IrUbj+$u9g92~mT72_2!o*p6^vCZNn{iRlw0Rtz}& z(r+ni=qb&sNsg+5eTtCGXrv9Qm5|{vk?9rjdMnuar4AAc2OM1A4794c#_$c29DIUT z*Mjx{{9{hZ);d(IewVkDukh6@x4rlLlk=1Q@IC&OU9qyQOFYftpMqp6d7t>8=_rRN z=7~uV86@U$8B<&7r>0wF(x+)tV_}_6w<#TZ^R9W7cQ03~4b{g}o%p$QS@^5f*KWLw zm0H-yiO`p1>I6BT8Ch<;qHoHcJf=%Td|WXO_R*jVp&02dbP5sOw z;Q`9@AC$;`f{a!`mzNXsc+sY~_+(M4co%DeN=p0Z8ONJVtdAAFs*3V4-@k)wZjb>D z0n7+GA6R+bZv%!*aDjlSStLS8-aOZ#L6Kw6k(6`|5Wrn%W+9j32$sD&jvqGts|1Emg{72Ok1ZqzUVz{owogLBV?^={24A7xE_w zY0zLYlf*YjZdgYja!LWEOiEe2uQT%v45ZTG_Xx1sEkunjbo^Krn#WMKmaVJM@uRRb z>K%*>flWnE8YgvCl^t+r?NLpGuOFXRlF6pH>&wLAFFl`E-N20J;kmK=7Yzz%HV;U( z;mKv7?ym%C+WsE}YszGasKNv@YsRD#JfdbJH*0Yo_Dq~P1&Z2hu{+8JcFSThEtubS z8>qbZv4E{q|x*HevCmY=@#<@Km=69Qy*_(|123Hs{KY67s zZNb?S;~J(Cy9Q*UwRylhoiHrIXimT*!q-;;-e#AJi}NJ`-FFCI-9R)_1;Uo!!(&S4SgBXnSBksFi|u*t(MWl#>HC{$qe}Ck zNm4O!oqim*BIC{VoM~grQ*a53@C50yFzy;FI;2_oBLrKKYhfm5Sgf3@=4PQs_%e|| zp^P`5^dki8_vI;mW@l0Ft5f#)VpjeriAJt*tzHCh~3F7W5c6t;h$ zR8so{{f$l9xZGE-XN`}#nELt@WeNaeA|XLvm~_Miz}DYf38Jd7~`lsTD00} zMbJsyB2y{dOxgL@q$a@HvK$rz{v?AI9|C9FxiS-?P)VxL*X0af;BBe~R7~-Dd#Tab zIQwGL%gJZb#MbsU6Z!-qEhXv+%!CjNV~aVJmccsK4ei99>tJ3HO@6vta*kc{GQSO< z9OIQ4XM$P9@!4@We1SQkdz`DxT1%@HHy?}KP&yD~xgPQ06^%}B^D80*UcuKKza>YP zC~KcEtzDEO5Vh5p^JeMMExhPLC;u=v-*+Wpj9ZcR`^Wz$+nIzO2G5q>DxG~URJvky zAW5!|MQigP*S*d(S&f`2eB5*MQ5b-MRQn{VNA&qhY!*pj?~iYmCCK`+d1ZZaaXjlF zZg^VZvz9DtURjw{>*PlO^jjekGZd1gfNR?RsVCObL<`<6|b^ zP8fnK1f0I+B#QZk-#NmaI*{v(bb;p~u_j5a8ETdqmW+DJm5VKf%}5;PU~;vv5w^sb zEpSehtMds^eJPuKhbHO`wBlI15N>mET5m2SJ%O*E4u=_7>`cfbIyx{yZ^uZQ|?^s*Cyg&W8-NBL?+tVd<&?Bvzw1~p2{ z;mSu7r4e=NJV~J*z-qh3@i(0+V}i`zBr1!6HQJVurT9Kp=nv?b`o*gEJu?>dWmdQ< z^Oooy(^tE!W^^fdS?=(biG@4p?x?HTh&I8=h-wMq?hs-N(8s;M1GBldF|=Q3PeEWM zJ+GMVcbew7B-!7|+`~9;{0&X!B&|Z#+y$PAU^o+inb2Lnxs$%}-dCTwzMu6Gk$yVJ%wm-zq zTJkQPGVdzz>cTsMh;MM8LluQdcySQ^vfbj~A8Wmz+rUb%^PVub`}p$Glgg+W;^#<_ zSF&6Y`N3ac7F0ypN?fzsG7ppT?3nB~K(0R4hIWKN5;RgaQ1;}1xFX&!gJi2@lSEPu zr6190wyx=UD8*z|kbBIzxQb%#=cL#oJZF&?9z18cqAnd@$93~d;%ka-n|_y~>$JE6Jbk)loGR-chc=0f)23$^<7Q=| z!;{JEB#iF6eAQvQZ}k@r%T)!fgyHZSY;@ZFtnLd@Mr9=Z^QP?UOgw*2M&`NuUFst5 z+JeZ{AnS4molZ635;=lwErU$F)GA~Bb!nJcj#4_?deT*;pk>*<>L~EIyezo2R6dj~ z>9QpQF45WkXg{36atE=&&iB)do>?(zTc94@#r!O<&fx6nYypb;13Fb350NgM*G#>t z>AMnSg{I{))WYPhA*($eWBg z^klIhrtUr)V_8asdkyg8ArwBAXx@FyDIgoaX-hQ!+I!rZ6+inw-^ zIW#y0qeZo}Gp6t-7Dr+ykl$IM;5}Ze?>LFHQm%{R&huX=9q`$4K_QiV zkXcv=1mi6%Ul!@kmu_LtbXTMRRGXY~DO2Meb+;l{-&M zk+X^Y7ox#yH&jwPcQ{9a4757%Gvp8|2h=ix=Man}%e=J%s+z;n>6)@hTbe|rh*F6= z-&*q_H>0%aQ0!@`HdW$4{2umDyKCcN9du})xSM+Zpxcm%(3&!dx1%9~E6V1eB{5Q} zn-BC>+2X&pBRAMGHDxk6HGDn*gD>|e+?rm0!}7sN91!N@VfCc^ND6%cvLsk#_vd}h zkl!Plob7P?9)c{teaR=_2+1&4ZnV2OfM7u@tph_)p8n6tim*wQL-4{Ey?>+7$fJ@q zf}O7_u0U87%?3jZ%Q(6B7q(Lwnu)8AuN{AaG~T;(T=u)(qhu*rVCfXlci@>BUnx~_ z>;h#fwA42e_;henD1zk29n%v&O?KzXc4bZFplY#MUHTcuE^vkS9(o=0=K4}zx{Nb_ z3wkkpQ*ugjl3t9>?cR2d{$Xv*41W14Lf$ya*Tnr zgnQz0;lUQdhtkq>`R)Ln=Nr$dwc5-))~97>JKa9~Gu3s5#Wp#hSKzagB$JptBApg7 zeKJ|l9cy1Vd)x1>!4~8brIh$pT&TN~IsYT}6CoMXs7qrqrTKZhK(=qO>=YW-Q}ni< z?xs@%2a!$ci}6bpjVfYURvdOj0sAE&5Gz-7BW6Stwc#SIfg- z_ZcByMbGWTYW4;IwnHNy-}G3;t(}iDuC;LcZvz#vVayUY8573CsdrasF0x}}=+~Bk zJtHBuwhfSZrN_#(>v}J$Q9V4eA-~c2xZ}M+q6+6O5622Bd0ekwBw2*F1MB4o7++u@ zh|#Vu;(VsZ07f9fy^f|wn{At;4(lve)At$qX3Zc^jer z{d!nIb^_O-Cz0n2JlDxHx16Wma7riLbt`8yRj2#5F-Ej zGS!8<^7PSnk=8vfsbOa1;qHl44sRBlR=vSVmSF)a50CiuTbM8W<%}H_R>oZ&tegA_^WDc$yDFaHEo~v zx?>bzHt0lcZmd7qmAw32V^7o)uLYtlptkJ!NyX2{44-l0mYD=(x-01EZn2CV+&^$B z2NO9)b->yfP)_i>=%6z8zaDXE4B1*|uV%FcgjVIdL)VB^V;pp5 zQ`8)p?NB^9j#bFdhkktiUt%D{|1v9{KEcU9{(+>fQof6U5hd3^!nJ^oAyof|hx+fi z$>+<<`oDAIH~jxKM*eR=@CC#J;(vJ+^MBERVY1blanvtg{AvHkkNR&w%0VX|4$!3W zPZYkC_6d-u@v^p&kdq9eE{4L9AcIpk%6X~S*8t)=>++0DAc_ixjNZcF;dOq)ApO;}oGPq6-B zw)gaNxAXMVd(Y=n*6aA|3?Ga^Fu-SFki31KZSe3zK8FzQW=@y?uW`sLX`X>AJ)cI8 zbIB5Vjh%e6R)Jd026~JLq6z$=4$(ShB@)zt?-IjCiu2!J;>_w#t8u4pbXG9Jk4t@* zu@tE-d47BC3{ix-+FBhPjW)KIR+aXfi*=gc5Cb~f|Fw1@|K7{$n~hC^!T>JNJzgpu zOn&8?Mk$}qB8^lXR~wXiC1h*TF+&H;(N$W=D6E%0eaC1KUOLO4+s+o_#X3UJ@7Tab z8V<=<@v&>?t|-y1lB`keNM1Fg;^h>}SEFat;I4Avv(j8b+#4({b+B*{-Y<9H6%T}n zQfp<}WEw>h4d@CWRK_z{6b0C}bYTIzmquf*7H~lCuIt zO{37kM(&CgH3LK`u?iwlv}{8zKw9~EHn)QmS$c`+z>-w{ya;Bp5onWWJs7oe(lA%Nf1^69{FpiM)pt&iYxt*RghTGWQq7-O%qN&4 zC*nCBOK~zqmY>NEcyNW-PqmeJIbz5u%J6N~DpP1i42+^3bU`gg|MYo7Qsf3)>l?oe z$Rx~jipgY?MhNqF%)e$vyo|*muvklk%5cSv`&@|Mp@)l(SpbK$?aD|<85g69UUEcj z$!^kbENk{+&8}f8Syh-_R{YjNrIZ6cym5O9-jpZ3OLU_bYBwjbmHrbY)2_sW(|&)W zAz)-MauuFQjEqp!=aD(6pxGghD1_C#d-^VFrbH2hp;K|;sAAIp23LFyt!?5J_

R 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

s!t&W|)iGy(E_CgQ0@H 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 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 127/380] 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 128/380] 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 129/380] 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 130/380] 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 131/380] 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 134/380] 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 135/380] 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 f54afc19b48804d697971f284884835be1f290a9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 18 Nov 2011 12:39:10 -0500 Subject: [PATCH 136/380] 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 137/380] 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 138/380] 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 139/380] 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 143/380] 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 144/380] 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 145/380] 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 146/380] 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 147/380] 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 148/380] 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 149/380] 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 150/380] 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 151/380] 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 152/380] 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 153/380] 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 154/380] 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 155/380] 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 156/380] 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 157/380] 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 158/380] 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 159/380] 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 160/380] 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 161/380] 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 162/380] 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 163/380] 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 164/380] 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 165/380] 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 166/380] 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 169/380] 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 171/380] 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 172/380] 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 173/380] 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 175/380] 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 a3aef8fa53c0c66b20023e4f448a6c3c18911eaa Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 22 Nov 2011 17:19:30 -0500 Subject: [PATCH 179/380] 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 32adbd614f9dc39b09fb3aa3fd9a2e910e3b4e24 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Tue, 22 Nov 2011 22:48:46 -0500 Subject: [PATCH 180/380] 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 181/380] 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 183/380] 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 184/380] 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 185/380] 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 186/380] 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 187/380] 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 188/380] 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 189/380] 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); From 436b4dc855942cb1049af78d81be3fc2dce475ee Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 28 Nov 2011 08:59:48 -0500 Subject: [PATCH 190/380] Updated docs --- .../gatk/walkers/genotyper/UnifiedGenotyperEngine.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 c38bb5b42..c861af1a2 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 @@ -50,11 +50,12 @@ public class UnifiedGenotyperEngine { public static final String LOW_QUAL_FILTER_NAME = "LowQual"; public enum OUTPUT_MODE { - /** the default */ + /** produces calls only at variant sites */ EMIT_VARIANTS_ONLY, - /** include confident reference sites */ + /** produces calls at variant sites and confident reference sites */ EMIT_ALL_CONFIDENT_SITES, - /** any callable site regardless of confidence */ + /** produces calls at any callable site regardless of confidence; this argument is intended for point + * mutations (SNPs) only and while some indel calls may be produced they are by no means comprehensive */ EMIT_ALL_SITES } From 3c36428a20662babe767f1ca82f193d0a0ef9fef Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 28 Nov 2011 10:20:34 -0500 Subject: [PATCH 191/380] Bug fix for TiTv calculation -- shouldn't be rounding --- .../gatk/walkers/varianteval/evaluators/VariantSummary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ba7164400..503cb8ff4 100644 --- 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 @@ -131,7 +131,7 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { n++; } } - return Math.round(sum / (1.0 * n)); + return sum / (1.0 * n); } } From 5c2595701c0c534add4a4a9dbda1bee22ffff7d7 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 28 Nov 2011 17:10:33 +0100 Subject: [PATCH 192/380] Added a function to get families only for a given list of samples. --- .../sting/gatk/samples/SampleDB.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java index ee0873c6e..9f00257d1 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -142,17 +142,29 @@ public class SampleDB { * @return */ public final Map> getFamilies() { + return getFamilies(null); + } + + /** + * Returns a map from family ID -> set of family members for all samples in sampleIds with + * non-null family ids + * + * @param sampleIds - all samples to include. If null is passed then all samples are returned. + * @return + */ + public final Map> getFamilies(Collection sampleIds) { final Map> families = new TreeMap>(); for ( final Sample sample : samples.values() ) { - final String famID = sample.getFamilyID(); - if ( famID != null ) { - if ( ! families.containsKey(famID) ) - families.put(famID, new TreeSet()); - families.get(famID).add(sample); + if(sampleIds != null && sampleIds.contains(sample.getID())){ + final String famID = sample.getFamilyID(); + if ( famID != null ) { + if ( ! families.containsKey(famID) ) + families.put(famID, new TreeSet()); + families.get(famID).add(sample); + } } } - return families; } From e877db8f420defd0ae2059e11481d01a686e922e Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 28 Nov 2011 17:11:30 +0100 Subject: [PATCH 193/380] Changed visibility of getSampleDB from protected to public as the sampleDB needs to be accessible from Annotators and Evaluators too. --- .../java/src/org/broadinstitute/sting/gatk/walkers/Walker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java index 792fef9c3..6264808f4 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java @@ -88,7 +88,7 @@ public abstract class Walker { return getToolkit().getMasterSequenceDictionary(); } - protected SampleDB getSampleDB() { + public SampleDB getSampleDB() { return getToolkit().getSampleDB(); } From 795c99d693409878dc64fad5232bb4574e7fa31d Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 28 Nov 2011 17:13:14 +0100 Subject: [PATCH 194/380] Adapted MendelianViolation to the new ped family representation. Adapted all classes using MendelianViolation too. MendelianViolationEvaluator was added a number of useful metrics on allele transmission and MVs --- .../walkers/annotator/MVLikelihoodRatio.java | 50 +- .../MendelianViolationEvaluator.java | 221 ++++---- .../walkers/variantutils/SelectVariants.java | 90 ++-- .../sting/utils/MendelianViolation.java | 476 +++++++++++++----- 4 files changed, 561 insertions(+), 276 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MVLikelihoodRatio.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MVLikelihoodRatio.java index bd0d4e3fb..b9e6a5b2b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MVLikelihoodRatio.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MVLikelihoodRatio.java @@ -3,22 +3,18 @@ package org.broadinstitute.sting.gatk.walkers.annotator; 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.samples.Sample; +import org.broadinstitute.sting.gatk.samples.SampleDB; import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.AnnotatorCompatibleWalker; import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.ExperimentalAnnotation; import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.InfoFieldAnnotation; -import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.MendelianViolation; -import org.broadinstitute.sting.utils.codecs.vcf.VCFFilterHeaderLine; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Created by IntelliJ IDEA. @@ -30,23 +26,26 @@ import java.util.Map; public class MVLikelihoodRatio extends InfoFieldAnnotation implements ExperimentalAnnotation { private MendelianViolation mendelianViolation = null; + private String motherId; + private String fatherId; + private String childId; public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { if ( mendelianViolation == null ) { - if ( walker instanceof VariantAnnotator && ((VariantAnnotator) walker).familyStr != null) { - mendelianViolation = new MendelianViolation(((VariantAnnotator)walker).familyStr, ((VariantAnnotator)walker).minGenotypeQualityP ); + if (checkAndSetSamples(((VariantAnnotator) walker).getSampleDB())) { + mendelianViolation = new MendelianViolation(((VariantAnnotator)walker).minGenotypeQualityP ); } else { - throw new UserException("Mendelian violation annotation can only be used from the Variant Annotator, and must be provided a valid Family String file (-family) on the command line."); + throw new UserException("Mendelian violation annotation can only be used from the Variant Annotator, and must be provided a valid PED file (-ped) from the command line containing only 1 trio."); } } Map toRet = new HashMap(1); - boolean hasAppropriateGenotypes = vc.hasGenotype(mendelianViolation.getSampleChild()) && vc.getGenotype(mendelianViolation.getSampleChild()).hasLikelihoods() && - vc.hasGenotype(mendelianViolation.getSampleDad()) && vc.getGenotype(mendelianViolation.getSampleDad()).hasLikelihoods() && - vc.hasGenotype(mendelianViolation.getSampleMom()) && vc.getGenotype(mendelianViolation.getSampleMom()).hasLikelihoods(); + boolean hasAppropriateGenotypes = vc.hasGenotype(motherId) && vc.getGenotype(motherId).hasLikelihoods() && + vc.hasGenotype(fatherId) && vc.getGenotype(fatherId).hasLikelihoods() && + vc.hasGenotype(childId) && vc.getGenotype(childId).hasLikelihoods(); if ( hasAppropriateGenotypes ) - toRet.put("MVLR",mendelianViolation.violationLikelihoodRatio(vc)); + toRet.put("MVLR",mendelianViolation.violationLikelihoodRatio(vc,motherId,fatherId,childId)); return toRet; } @@ -55,4 +54,27 @@ public class MVLikelihoodRatio extends InfoFieldAnnotation implements Experiment public List getKeyNames() { return Arrays.asList("MVLR"); } public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine("MVLR", 1, VCFHeaderLineType.Float, "Mendelian violation likelihood ratio: L[MV] - L[No MV]")); } + + private boolean checkAndSetSamples(SampleDB db){ + Set families = db.getFamilyIDs(); + if(families.size() != 1) + return false; + + Set family = db.getFamily(families.iterator().next()); + if(family.size() != 3) + return false; + + Iterator sampleIter = family.iterator(); + Sample sample; + for(sample = sampleIter.next();sampleIter.hasNext();sample=sampleIter.next()){ + if(sample.getParents().size()==2){ + motherId = sample.getMaternalID(); + fatherId = sample.getPaternalID(); + childId = sample.getID(); + return true; + } + } + return false; + } + } 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 0cadf6c0d..363f5665f 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 @@ -1,5 +1,7 @@ package org.broadinstitute.sting.gatk.walkers.varianteval.evaluators; +import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; @@ -7,9 +9,11 @@ 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.MendelianViolation; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; /** * Mendelian violation detection and counting @@ -40,12 +44,25 @@ import org.broadinstitute.sting.utils.variantcontext.VariantContext; @Analysis(name = "Mendelian Violation Evaluator", description = "Mendelian Violation Evaluator") public class MendelianViolationEvaluator extends VariantEvaluator { - @DataPoint(description = "Number of mendelian variants found") + @DataPoint(description = "Number of variants found with at least one family having genotypes") long nVariants; + @DataPoint(description = "Number of variants found with no family having genotypes -- these sites do not count in the nNoCall") + long nSkipped; + @DataPoint(description="Number of variants x families called (no missing genotype or lowqual)") + long nFamCalled; + @DataPoint(description="Number of variants x families called (no missing genotype or lowqual) that contain at least one var allele.") + long nVarFamCalled; + @DataPoint(description="Number of variants x families discarded as low quality") + long nLowQual; + @DataPoint(description="Number of variants x families discarded as no call") + long nNoCall; + @DataPoint(description="Number of loci with mendelian violations") + long nLociViolations; @DataPoint(description = "Number of mendelian violations found") long nViolations; - @DataPoint(description = "number of child hom ref calls where the parent was hom variant") + + /*@DataPoint(description = "number of child hom ref calls where the parent was hom variant") long KidHomRef_ParentHomVar; @DataPoint(description = "number of child het calls where the parent was hom ref") long KidHet_ParentsHomRef; @@ -53,11 +70,65 @@ public class MendelianViolationEvaluator extends VariantEvaluator { long KidHet_ParentsHomVar; @DataPoint(description = "number of child hom variant calls where the parent was hom ref") long KidHomVar_ParentHomRef; + */ + + @DataPoint(description="Number of mendelian violations of the type HOM_REF/HOM_REF -> HOM_VAR") + long mvRefRef_Var; + @DataPoint(description="Number of mendelian violations of the type HOM_REF/HOM_REF -> HET") + long mvRefRef_Het; + @DataPoint(description="Number of mendelian violations of the type HOM_REF/HET -> HOM_VAR") + long mvRefHet_Var; + @DataPoint(description="Number of mendelian violations of the type HOM_REF/HOM_VAR -> HOM_VAR") + long mvRefVar_Var; + @DataPoint(description="Number of mendelian violations of the type HOM_REF/HOM_VAR -> HOM_REF") + long mvRefVar_Ref; + @DataPoint(description="Number of mendelian violations of the type HOM_VAR/HET -> HOM_REF") + long mvVarHet_Ref; + @DataPoint(description="Number of mendelian violations of the type HOM_VAR/HOM_VAR -> HOM_REF") + long mvVarVar_Ref; + @DataPoint(description="Number of mendelian violations of the type HOM_VAR/HOM_VAR -> HET") + long mvVarVar_Het; + + + /*@DataPoint(description ="Number of inherited var alleles from het parents") + long nInheritedVar; + @DataPoint(description ="Number of inherited ref alleles from het parents") + long nInheritedRef;*/ + + @DataPoint(description="Number of HomRef/HomRef/HomRef trios") + long HomRefHomRef_HomRef; + @DataPoint(description="Number of Het/Het/Het trios") + long HetHet_Het; + @DataPoint(description="Number of Het/Het/HomRef trios") + long HetHet_HomRef; + @DataPoint(description="Number of Het/Het/HomVar trios") + long HetHet_HomVar; + @DataPoint(description="Number of HomVar/HomVar/HomVar trios") + long HomVarHomVar_HomVar; + @DataPoint(description="Number of HomRef/HomVar/Het trios") + long HomRefHomVAR_Het; + @DataPoint(description="Number of ref alleles inherited from het/het parents") + long HetHet_inheritedRef; + @DataPoint(description="Number of var alleles inherited from het/het parents") + long HetHet_inheritedVar; + @DataPoint(description="Number of ref alleles inherited from homRef/het parents") + long HomRefHet_inheritedRef; + @DataPoint(description="Number of var alleles inherited from homRef/het parents") + long HomRefHet_inheritedVar; + @DataPoint(description="Number of ref alleles inherited from homVar/het parents") + long HomVarHet_inheritedRef; + @DataPoint(description="Number of var alleles inherited from homVar/het parents") + long HomVarHet_inheritedVar; MendelianViolation mv; + PrintStream mvFile; + Map> families; public void initialize(VariantEvalWalker walker) { - mv = new MendelianViolation(walker.getFamilyStructure(), walker.getMendelianViolationQualThreshold()); + //Changed by Laurent Francioli - 2011-06-07 + //mv = new MendelianViolation(walker.getFamilyStructure(), walker.getMendelianViolationQualThreshold()); + mv = new MendelianViolation(walker.getMendelianViolationQualThreshold(),false); + families = walker.getSampleDB().getFamilies(); } public boolean enabled() { @@ -75,110 +146,48 @@ public class MendelianViolationEvaluator extends VariantEvaluator { public String update1(VariantContext vc, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { if (vc.isBiallelic() && vc.hasGenotypes()) { // todo -- currently limited to biallelic loci - if (mv.setAlleles(vc)) { + + if(mv.countViolations(families,vc)>0){ + nLociViolations++; + nViolations += mv.getViolationsCount(); + mvRefRef_Var += mv.getParentsRefRefChildVar(); + mvRefRef_Het += mv.getParentsRefRefChildHet(); + mvRefHet_Var += mv.getParentsRefHetChildVar(); + mvRefVar_Var += mv.getParentsRefVarChildVar(); + mvRefVar_Ref += mv.getParentsRefVarChildRef(); + mvVarHet_Ref += mv.getParentsVarHetChildRef(); + mvVarVar_Ref += mv.getParentsVarVarChildRef(); + mvVarVar_Het += mv.getParentsVarVarChildHet(); + + } + HomRefHomRef_HomRef += mv.getRefRefRef(); + HetHet_Het += mv.getHetHetHet(); + HetHet_HomRef += mv.getHetHetHomRef(); + HetHet_HomVar += mv.getHetHetHomVar(); + HomVarHomVar_HomVar += mv.getVarVarVar(); + HomRefHomVAR_Het += mv.getRefVarHet(); + HetHet_inheritedRef += mv.getParentsHetHetInheritedRef(); + HetHet_inheritedVar += mv.getParentsHetHetInheritedVar(); + HomRefHet_inheritedRef += mv.getParentsRefHetInheritedRef(); + HomRefHet_inheritedVar += mv.getParentsRefHetInheritedVar(); + HomVarHet_inheritedRef += mv.getParentsVarHetInheritedRef(); + HomVarHet_inheritedVar += mv.getParentsVarHetInheritedVar(); + + if(mv.getFamilyCalledCount()>0){ nVariants++; - - Genotype momG = vc.getGenotype(mv.getSampleMom()); - Genotype dadG = vc.getGenotype(mv.getSampleDad()); - Genotype childG = vc.getGenotype(mv.getSampleChild()); - - if (mv.isViolation()) { - nViolations++; - - String label; - if (childG.isHomRef() && (momG.isHomVar() || dadG.isHomVar())) { - label = "KidHomRef_ParentHomVar"; - KidHomRef_ParentHomVar++; - } else if (childG.isHet() && (momG.isHomRef() && dadG.isHomRef())) { - label = "KidHet_ParentsHomRef"; - KidHet_ParentsHomRef++; - } else if (childG.isHet() && (momG.isHomVar() && dadG.isHomVar())) { - label = "KidHet_ParentsHomVar"; - KidHet_ParentsHomVar++; - } else if (childG.isHomVar() && (momG.isHomRef() || dadG.isHomRef())) { - label = "KidHomVar_ParentHomRef"; - KidHomVar_ParentHomRef++; - } else { - throw new ReviewedStingException("BUG: unexpected child genotype class " + childG); - } - - return "MendelViolation=" + label; - } + nFamCalled += mv.getFamilyCalledCount(); + nLowQual += mv.getFamilyLowQualsCount(); + nNoCall += mv.getFamilyNoCallCount(); + nVarFamCalled += mv.getVarFamilyCalledCount(); } - } - - return null; // we don't capture any intersting sites - } - - -/* - private double getQThreshold() { - //return getVEWalker().MENDELIAN_VIOLATION_QUAL_THRESHOLD / 10; // we aren't 10x scaled in the GATK a la phred - return mendelianViolationQualThreshold / 10; // we aren't 10x scaled in the GATK a la phred - //return 0.0; - } - - TrioStructure trio; - double mendelianViolationQualThreshold; - - private static Pattern FAMILY_PATTERN = Pattern.compile("(.*)\\+(.*)=(.*)"); - - public static class TrioStructure { - public String mom, dad, child; - } - - public static TrioStructure parseTrioDescription(String family) { - Matcher m = FAMILY_PATTERN.matcher(family); - if (m.matches()) { - TrioStructure trio = new TrioStructure(); - //System.out.printf("Found a family pattern: %s%n", parent.FAMILY_STRUCTURE); - trio.mom = m.group(1); - trio.dad = m.group(2); - trio.child = m.group(3); - return trio; - } else { - throw new IllegalArgumentException("Malformatted family structure string: " + family + " required format is mom+dad=child"); - } - } - - public void initialize(VariantEvalWalker walker) { - trio = parseTrioDescription(walker.getFamilyStructure()); - mendelianViolationQualThreshold = walker.getMendelianViolationQualThreshold(); - } - - private boolean includeGenotype(Genotype g) { - return g.getLog10PError() > getQThreshold() && g.isCalled(); - } - - public static boolean isViolation(VariantContext vc, Genotype momG, Genotype dadG, Genotype childG) { - return isViolation(vc, momG.getAlleles(), dadG.getAlleles(), childG.getAlleles()); - } - - public static boolean isViolation(VariantContext vc, TrioStructure trio ) { - return isViolation(vc, vc.getGenotype(trio.mom), vc.getGenotype(trio.dad), vc.getGenotype(trio.child) ); - } - - public static boolean isViolation(VariantContext vc, List momA, List dadA, List childA) { - //VariantContext momVC = vc.subContextFromGenotypes(momG); - //VariantContext dadVC = vc.subContextFromGenotypes(dadG); - int i = 0; - Genotype childG = new Genotype("kidG", childA); - for (Allele momAllele : momA) { - for (Allele dadAllele : dadA) { - if (momAllele.isCalled() && dadAllele.isCalled()) { - Genotype possibleChild = new Genotype("possibleGenotype" + i, Arrays.asList(momAllele, dadAllele)); - if (childG.sameGenotype(possibleChild)) { - return false; - } - } + else{ + nSkipped++; } + + + return null; } - return true; + return null; // we don't capture any interesting sites } - - -*/ - - } 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 b0016ff4b..fc01dae9f 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 @@ -26,9 +26,9 @@ package org.broadinstitute.sting.gatk.walkers.variantutils; import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.codecs.vcf.*; 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.*; @@ -41,7 +41,6 @@ import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.SampleUtils; import java.io.File; -import java.io.FileNotFoundException; import java.io.PrintStream; import java.util.*; @@ -282,6 +281,9 @@ public class SelectVariants extends RodWalker { @Argument(fullName="select_random_fraction", shortName="fraction", doc="Selects a fraction (a number between 0 and 1) of the total variants at random from the variant track", required=false) private double fractionRandom = 0; + @Argument(fullName="remove_fraction_genotypes", shortName="fractionGenotypes", doc="Selects a fraction (a number between 0 and 1) of the total genotypes at random from the variant track and sets them to nocall", required=false) + private double fractionGenotypes = 0; + /** * This argument select particular kinds of variants out of a list. If left empty, there is no type selection and all variant types are considered for other selection criteria. * When specified one or more times, a particular type of variant is selected. @@ -325,7 +327,7 @@ public class SelectVariants extends RodWalker { private boolean DISCORDANCE_ONLY = false; private boolean CONCORDANCE_ONLY = false; - private Set mvSet = new HashSet(); + private MendelianViolation mv; /* variables used by the SELECT RANDOM modules */ @@ -344,6 +346,8 @@ public class SelectVariants extends RodWalker { private PrintStream outMVFileStream = null; + //Random number generator for the genotypes to remove + private Random randomGenotypes = new Random(); /** * Set up the VCF writer, the sample expressions and regexs, and the JEXL matcher @@ -380,8 +384,6 @@ public class SelectVariants extends RodWalker { for ( String sample : samples ) logger.info("Including sample '" + sample + "'"); - - // if user specified types to include, add these, otherwise, add all possible variant context types to list of vc types to include if (TYPES_TO_INCLUDE.isEmpty()) { @@ -421,29 +423,7 @@ public class SelectVariants extends RodWalker { if (CONCORDANCE_ONLY) logger.info("Selecting only variants concordant with the track: " + concordanceTrack.getName()); if (MENDELIAN_VIOLATIONS) { - if ( FAMILY_STRUCTURE_FILE != null) { - try { - for ( final String line : new XReadLines( FAMILY_STRUCTURE_FILE ) ) { - MendelianViolation mv = new MendelianViolation(line, MENDELIAN_VIOLATION_QUAL_THRESHOLD); - if (samples.contains(mv.getSampleChild()) && samples.contains(mv.getSampleDad()) && samples.contains(mv.getSampleMom())) - mvSet.add(mv); - } - } catch ( FileNotFoundException e ) { - throw new UserException.CouldNotReadInputFile(FAMILY_STRUCTURE_FILE, e); - } - if (outMVFile != null) - try { - outMVFileStream = new PrintStream(outMVFile); - } - catch (FileNotFoundException e) { - throw new UserException.CouldNotCreateOutputFile(outMVFile, "Can't open output file", e); } - } - else - mvSet.add(new MendelianViolation(FAMILY_STRUCTURE, MENDELIAN_VIOLATION_QUAL_THRESHOLD)); - } - else if (!FAMILY_STRUCTURE.isEmpty()) { - mvSet.add(new MendelianViolation(FAMILY_STRUCTURE, MENDELIAN_VIOLATION_QUAL_THRESHOLD)); - MENDELIAN_VIOLATIONS = true; + mv = new MendelianViolation(MENDELIAN_VIOLATION_QUAL_THRESHOLD,false,true); } SELECT_RANDOM_NUMBER = numRandom > 0; @@ -479,26 +459,26 @@ public class SelectVariants extends RodWalker { } for (VariantContext vc : vcs) { - if (MENDELIAN_VIOLATIONS) { - boolean foundMV = false; - for (MendelianViolation mv : mvSet) { - if (mv.isViolation(vc)) { - foundMV = true; - //System.out.println(vc.toString()); - 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, " + + if (MENDELIAN_VIOLATIONS && mv.countViolations(this.getSampleDB().getFamilies(samples),vc) < 1) + break; + + if (outMVFile != null){ + for( String familyId : mv.getViolationFamilies()){ + for(Sample sample : this.getSampleDB().getFamily(familyId)){ + if(sample.getParents().size() > 0){ + 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.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(), - vc.getGenotype(mv.getSampleChild()).toBriefString(),vc.getGenotype(mv.getSampleChild()).getLikelihoods().getAsString() ); + sample.getMaternalID(), sample.getPaternalID(), sample.getID(), + vc.getGenotype(sample.getMaternalID()).toBriefString(), vc.getGenotype(sample.getMaternalID()).getLikelihoods().getAsString(), + vc.getGenotype(sample.getPaternalID()).toBriefString(), vc.getGenotype(sample.getPaternalID()).getLikelihoods().getAsString(), + vc.getGenotype(sample.getID()).toBriefString(),vc.getGenotype(sample.getID()).getLikelihoods().getAsString() ); + + } } } - - if (!foundMV) - break; } + if (DISCORDANCE_ONLY) { Collection compVCs = tracker.getValues(discordanceTrack, context.getLocation()); if (!isDiscordant(vc, compVCs)) @@ -657,9 +637,31 @@ public class SelectVariants extends RodWalker { final VariantContext sub = vc.subContextFromSamples(samples, vc.getAlleles()); VariantContextBuilder builder = new VariantContextBuilder(sub); + GenotypesContext newGC = 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() ) - builder.genotypes(VariantContextUtils.stripPLs(vc.getGenotypes())); + newGC = VariantContextUtils.stripPLs(sub.getGenotypes()); + + //Remove a fraction of the genotypes if needed + if(fractionGenotypes>0){ + ArrayList genotypes = new ArrayList(); + for ( Genotype genotype : newGC ) { + //Set genotype to no call if it falls in the fraction. + if(fractionGenotypes>0 && randomGenotypes.nextDouble() alleles = new ArrayList(2); + alleles.add(Allele.create((byte)'.')); + alleles.add(Allele.create((byte)'.')); + genotypes.add(new Genotype(genotype.getSampleName(),alleles, Genotype.NO_LOG10_PERROR,genotype.getFilters(),new HashMap(),false)); + } + else{ + genotypes.add(genotype); + } + } + newGC = GenotypesContext.create(genotypes); + } + + builder.genotypes(newGC); int depth = 0; for (String sample : sub.getSampleNames()) { diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index cf45dab79..e140575c0 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -1,147 +1,399 @@ package org.broadinstitute.sting.utils; import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; +import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * User: carneiro + * User: carneiro / lfran * Date: 3/9/11 * Time: 12:38 PM + * + * Class for the identification and tracking of mendelian violation. It can be used in 2 distinct ways: + * - Either using an instance of the MendelianViolation class to track mendelian violations for each of the families while + * walking over the variants + * - Or using the static methods to directly get information about mendelian violation in a family at a given locus + * */ public class MendelianViolation { - String sampleMom; - String sampleDad; - String sampleChild; + //List of families with violations + private List violationFamilies; - List allelesMom; - List allelesDad; - List allelesChild; + //Call information + private int nocall = 0; + private int familyCalled = 0; + private int varFamilyCalled = 0; + private int lowQual = 0; - double minGenotypeQuality; + private boolean allCalledOnly = true; + + //Stores occurrences of inheritance + private EnumMap>> inheritance; + + private int violations_total=0; + + private double minGenotypeQuality; + + private boolean abortOnSampleNotFound; + + //Number of families with genotype information for all members + public int getFamilyCalledCount(){ + return familyCalled; + } + + //Number of families with genotype information for all members + public int getVarFamilyCalledCount(){ + return varFamilyCalled; + } + + //Number of families missing genotypes for one or more of their members + public int getFamilyNoCallCount(){ + return nocall; + } + + //Number of families with genotypes below the set quality threshold + public int getFamilyLowQualsCount(){ + return lowQual; + } + + public int getViolationsCount(){ + return violations_total; + } + + //Count of alt alleles inherited from het parents (no violation) + public int getParentHetInheritedVar(){ + return getParentsHetHetInheritedVar() + getParentsRefHetInheritedVar() + getParentsVarHetInheritedVar(); + } + + //Count of ref alleles inherited from het parents (no violation) + public int getParentHetInheritedRef(){ + return getParentsHetHetInheritedRef() + getParentsRefHetInheritedRef() + getParentsVarHetInheritedRef(); + } + + //Count of HomRef/HomRef/HomRef trios + public int getRefRefRef(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_REF); + } + + //Count of HomVar/HomVar/HomVar trios + public int getVarVarVar(){ + return inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_VAR); + } + + //Count of HomRef/HomVar/Het trios + public int getRefVarHet(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HET) + + inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_REF).get(Genotype.Type.HET); + } + + //Count of Het/Het/Het trios + public int getHetHetHet(){ + return inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HET); + } + + //Count of Het/Het/HomRef trios + public int getHetHetHomRef(){ + return inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HOM_REF); + } + + //Count of Het/Het/HomVar trios + public int getHetHetHomVar(){ + return inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR); + } + + //Count of ref alleles inherited from Het/Het parents (no violation) + public int getParentsHetHetInheritedRef(){ + return inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HET) + + 2*inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HOM_REF); + //return parentsHetHet_childRef; + } + + //Count of var alleles inherited from Het/Het parents (no violation) + public int getParentsHetHetInheritedVar(){ + return inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HET) + + 2*inheritance.get(Genotype.Type.HET).get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR); + //return parentsHetHet_childVar; + } + + //Count of ref alleles inherited from HomRef/Het parents (no violation) + public int getParentsRefHetInheritedRef(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HET).get(Genotype.Type.HOM_REF) + + inheritance.get(Genotype.Type.HET).get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_REF); + //return parentsHomRefHet_childRef; + } + + //Count of var alleles inherited from HomRef/Het parents (no violation) + public int getParentsRefHetInheritedVar(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HET).get(Genotype.Type.HET) + + inheritance.get(Genotype.Type.HET).get(Genotype.Type.HOM_REF).get(Genotype.Type.HET); + //return parentsHomRefHet_childVar; + } + + //Count of ref alleles inherited from HomVar/Het parents (no violation) + public int getParentsVarHetInheritedRef(){ + return inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HET).get(Genotype.Type.HET) + + inheritance.get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HET); + //return parentsHomVarHet_childRef; + } + + //Count of var alleles inherited from HomVar/Het parents (no violation) + public int getParentsVarHetInheritedVar(){ + return inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR) + + inheritance.get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_VAR); + //return parentsHomVarHet_childVar; + } + + //Count of violations of the type HOM_REF/HOM_REF -> HOM_VAR + public int getParentsRefRefChildVar(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_VAR); + } + + //Count of violations of the type HOM_REF/HOM_REF -> HET + public int getParentsRefRefChildHet(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_REF).get(Genotype.Type.HET); + } + + //Count of violations of the type HOM_REF/HET -> HOM_VAR + public int getParentsRefHetChildVar(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR) + + inheritance.get(Genotype.Type.HET).get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_VAR); + } + + //Count of violations of the type HOM_REF/HOM_VAR -> HOM_VAR + public int getParentsRefVarChildVar(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_VAR) + + inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_VAR); + } + + //Count of violations of the type HOM_REF/HOM_VAR -> HOM_REF + public int getParentsRefVarChildRef(){ + return inheritance.get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_REF) + + inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_REF).get(Genotype.Type.HOM_REF); + } + + //Count of violations of the type HOM_VAR/HET -> HOM_REF + public int getParentsVarHetChildRef(){ + return inheritance.get(Genotype.Type.HET).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_REF) + + inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HET).get(Genotype.Type.HOM_REF); + } + + //Count of violations of the type HOM_VAR/HOM_VAR -> HOM_REF + public int getParentsVarVarChildRef(){ + return inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_REF); + } + + //Count of violations of the type HOM_VAR/HOM_VAR -> HET + public int getParentsVarVarChildHet(){ + return inheritance.get(Genotype.Type.HOM_VAR).get(Genotype.Type.HOM_VAR).get(Genotype.Type.HET); + } + + + //Count of violations of the type HOM_VAR/? -> HOM_REF + public int getParentVarChildRef(){ + return getParentsRefVarChildRef() + getParentsVarHetChildRef() +getParentsVarVarChildRef(); + } + + //Count of violations of the type HOM_REF/? -> HOM_VAR + public int getParentRefChildVar(){ + return getParentsRefVarChildVar() + getParentsRefHetChildVar() +getParentsRefRefChildVar(); + } + + //Returns a String containing all trios where a Mendelian violation was observed. + //The String is formatted "mom1+dad1=child1,mom2+dad2=child2,..." + public String getViolationFamiliesString(){ + if(violationFamilies.isEmpty()) + return ""; + + Iterator it = violationFamilies.iterator(); + String violationFams = it.next(); + while(it.hasNext()){ + violationFams += ","+it.next(); + } + return violationFams; + } + + public List getViolationFamilies(){ + return violationFamilies; + } static final int[] mvOffsets = new int[] { 1,2,5,6,8,11,15,18,20,21,24,25 }; static final int[] nonMVOffsets = new int[]{ 0,3,4,7,9,10,12,13,14,16,17,19,22,23,26 }; - private static Pattern FAMILY_PATTERN = Pattern.compile("(.*)\\+(.*)=(.*)"); - - public String getSampleMom() { - return sampleMom; - } - public String getSampleDad() { - return sampleDad; - } - public String getSampleChild() { - return sampleChild; - } public double getMinGenotypeQuality() { return minGenotypeQuality; } - /** - * - * @param sampleMomP - sample name of mom - * @param sampleDadP - sample name of dad - * @param sampleChildP - sample name of child - */ - public MendelianViolation (String sampleMomP, String sampleDadP, String sampleChildP) { - sampleMom = sampleMomP; - sampleDad = sampleDadP; - sampleChild = sampleChildP; - } - - /** - * - * @param family - the sample names string "mom+dad=child" + /** + * Constructor * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to asses mendelian violation - */ - public MendelianViolation(String family, double minGenotypeQualityP) { - minGenotypeQuality = minGenotypeQualityP; - - Matcher m = FAMILY_PATTERN.matcher(family); - if (m.matches()) { - sampleMom = m.group(1); - sampleDad = m.group(2); - sampleChild = m.group(3); - } - else - throw new IllegalArgumentException("Malformatted family structure string: " + family + " required format is mom+dad=child"); - } - - /** - * An alternative to the more general constructor if you want to get the Sample information from the engine yourself. - * @param sample - the sample object extracted from the sample metadata YAML file given to the engine. - * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to asses mendelian violation - */ - public MendelianViolation(Sample sample, double minGenotypeQualityP) { - sampleMom = sample.getMother().getID(); - sampleDad = sample.getFather().getID(); - sampleChild = sample.getID(); - minGenotypeQuality = minGenotypeQualityP; - } - - /** - * This method prepares the object to evaluate for violation. Typically you won't call it directly, a call to - * isViolation(vc) will take care of this. But if you want to know whether your site was a valid comparison site - * before evaluating it for mendelian violation, you can call setAlleles and then isViolation(). - * @param vc - the variant context to extract the genotypes and alleles for mom, dad and child. - * @return false if couldn't find the genotypes or context has empty alleles. True otherwise. - */ - public boolean setAlleles (VariantContext vc) - { - Genotype gMom = vc.getGenotypes(sampleMom).get(sampleMom); - Genotype gDad = vc.getGenotypes(sampleDad).get(sampleDad); - Genotype gChild = vc.getGenotypes(sampleChild).get(sampleChild); - - if (gMom == null || gDad == null || gChild == null) - throw new IllegalArgumentException(String.format("Variant %s:%d didn't contain genotypes for all family members: mom=%s dad=%s child=%s", vc.getChr(), vc.getStart(), sampleMom, sampleDad, sampleChild)); - - if (gMom.isNoCall() || gDad.isNoCall() || gChild.isNoCall() || - gMom.getPhredScaledQual() < minGenotypeQuality || - gDad.getPhredScaledQual() < minGenotypeQuality || - gChild.getPhredScaledQual() < minGenotypeQuality ) { - - return false; - } - - allelesMom = gMom.getAlleles(); - allelesDad = gDad.getAlleles(); - allelesChild = gChild.getAlleles(); - return !allelesMom.isEmpty() && !allelesDad.isEmpty() && !allelesChild.isEmpty(); - } - - - /** * + */ + public MendelianViolation(double minGenotypeQualityP) { + this(minGenotypeQualityP,true); + } + + /** + * Constructor + * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to asses mendelian violation + * @param abortOnSampleNotFound - Whether to stop execution if a family is passed but no relevant genotypes are found. If false, then the family is ignored. + */ + public MendelianViolation(double minGenotypeQualityP, boolean abortOnSampleNotFound) { + minGenotypeQuality = minGenotypeQualityP; + this.abortOnSampleNotFound = abortOnSampleNotFound; + violationFamilies = new ArrayList(); + createInheritanceMap(); + } + + /** + * Constructor + * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to asses mendelian violation + * @param abortOnSampleNotFound - Whether to stop execution if a family is passed but no relevant genotypes are found. If false, then the family is ignored. + * @param completeTriosOnly - whether only complete trios are considered or parent/child pairs are too. + */ + public MendelianViolation(double minGenotypeQualityP, boolean abortOnSampleNotFound, boolean completeTriosOnly) { + minGenotypeQuality = minGenotypeQualityP; + this.abortOnSampleNotFound = abortOnSampleNotFound; + violationFamilies = new ArrayList(); + createInheritanceMap(); + allCalledOnly = completeTriosOnly; + } + + /** + * @param families the families to be checked for Mendelian violations * @param vc the variant context to extract the genotypes and alleles for mom, dad and child. - * @return False if we can't determine (lack of information), or it's not a violation. True if it is a violation. - * - */ - public boolean isViolation(VariantContext vc) - { - return setAlleles(vc) && isViolation(); - } - - /** * @return whether or not there is a mendelian violation at the site. */ - public boolean isViolation() { - if (allelesMom.contains(allelesChild.get(0)) && allelesDad.contains(allelesChild.get(1)) || - allelesMom.contains(allelesChild.get(1)) && allelesDad.contains(allelesChild.get(0))) - return false; - return true; + public int countViolations(Map> families, VariantContext vc){ + + //Reset counts + nocall = 0; + lowQual = 0; + familyCalled = 0; + varFamilyCalled = 0; + violations_total=0; + violationFamilies.clear(); + clearInheritanceMap(); + + for(Set family : families.values()){ + Iterator sampleIterator = family.iterator(); + Sample sample; + while(sampleIterator.hasNext()){ + sample = sampleIterator.next(); + if(sample.getParents().size() > 0) + updateViolations(sample.getFamilyID(),sample.getMaternalID(), sample.getPaternalID(), sample.getID() ,vc); + } + } + return violations_total; + } + + public boolean isViolation(Sample mother, Sample father, Sample child, VariantContext vc){ + + //Reset counts + nocall = 0; + lowQual = 0; + familyCalled = 0; + varFamilyCalled = 0; + violations_total=0; + violationFamilies.clear(); + clearInheritanceMap(); + updateViolations(mother.getFamilyID(),mother.getID(),father.getID(),child.getID(),vc); + return violations_total>0; + } + + + private void updateViolations(String familyId, String motherId, String fatherId, String childId, VariantContext vc){ + + int count; + Genotype gMom = vc.getGenotype(motherId); + Genotype gDad = vc.getGenotype(fatherId); + Genotype gChild = vc.getGenotype(childId); + + if (gMom == null || gDad == null || gChild == null){ + if(abortOnSampleNotFound) + throw new IllegalArgumentException(String.format("Variant %s:%d: Missing genotypes for family %s: mom=%s dad=%s family=%s", vc.getChr(), vc.getStart(), familyId, motherId, fatherId, childId)); + else + return; + } + //Count No calls + if(allCalledOnly && (!gMom.isCalled() || !gDad.isCalled() || !gChild.isCalled())){ + nocall++; + } + else if (!gMom.isCalled() && !gDad.isCalled() || !gChild.isCalled()){ + nocall++; + } + //Count lowQual. Note that if min quality is set to 0, even values with no quality associated are returned + else if (minGenotypeQuality>0 && (gMom.getPhredScaledQual() < minGenotypeQuality || + gDad.getPhredScaledQual() < minGenotypeQuality || + gChild.getPhredScaledQual() < minGenotypeQuality )) { + lowQual++; + } + else{ + //Count all families per loci called + familyCalled++; + //If the family is all homref, not too interesting + if(!(gMom.isHomRef() && gDad.isHomRef() && gChild.isHomRef())) + { + varFamilyCalled++; + if(isViolation(gMom, gDad, gChild)){ + violationFamilies.add(familyId); + violations_total++; + } + } + count = inheritance.get(gMom.getType()).get(gDad.getType()).get(gChild.getType()); + inheritance.get(gMom.getType()).get(gDad.getType()).put(gChild.getType(),count+1); + + } + } + + private boolean isViolation(Genotype gMom, Genotype gDad, Genotype gChild) { + //1 parent is no "call + if(!gMom.isCalled()){ + return (gDad.isHomRef() && gChild.isHomVar()) || (gDad.isHomVar() && gChild.isHomRef()); + } + else if(!gDad.isCalled()){ + return (gMom.isHomRef() && gChild.isHomVar()) || (gMom.isHomVar() && gChild.isHomRef()); + } + //Both parents have genotype information + return !(gMom.getAlleles().contains(gChild.getAlleles().get(0)) && gDad.getAlleles().contains(gChild.getAlleles().get(1)) || + gMom.getAlleles().contains(gChild.getAlleles().get(1)) && gDad.getAlleles().contains(gChild.getAlleles().get(0))); + } + + private void createInheritanceMap(){ + + inheritance = new EnumMap>>(Genotype.Type.class); + for(Genotype.Type mType : Genotype.Type.values()){ + inheritance.put(mType, new EnumMap>(Genotype.Type.class)); + for(Genotype.Type dType : Genotype.Type.values()){ + inheritance.get(mType).put(dType, new EnumMap(Genotype.Type.class)); + for(Genotype.Type cType : Genotype.Type.values()){ + inheritance.get(mType).get(dType).put(cType, 0); + } + } + } + + } + + private void clearInheritanceMap(){ + for(Genotype.Type mType : Genotype.Type.values()){ + for(Genotype.Type dType : Genotype.Type.values()){ + for(Genotype.Type cType : Genotype.Type.values()){ + inheritance.get(mType).get(dType).put(cType, 0); + } + } + } } /** * @return the likelihood ratio for a mendelian violation */ - public double violationLikelihoodRatio(VariantContext vc) { + public double violationLikelihoodRatio(VariantContext vc, String motherId, String fatherId, String childId) { double[] logLikAssignments = new double[27]; // the matrix to set up is // MOM DAD CHILD @@ -152,9 +404,9 @@ public class MendelianViolation { // AA AB | AB // |- BB // etc. The leaves are counted as 0-11 for MVs and 0-14 for non-MVs - double[] momGL = vc.getGenotype(sampleMom).getLikelihoods().getAsVector(); - double[] dadGL = vc.getGenotype(sampleDad).getLikelihoods().getAsVector(); - double[] childGL = vc.getGenotype(sampleChild).getLikelihoods().getAsVector(); + double[] momGL = vc.getGenotype(motherId).getLikelihoods().getAsVector(); + double[] dadGL = vc.getGenotype(fatherId).getLikelihoods().getAsVector(); + double[] childGL = vc.getGenotype(childId).getLikelihoods().getAsVector(); int offset = 0; for ( int oMom = 0; oMom < 3; oMom++ ) { for ( int oDad = 0; oDad < 3; oDad++ ) { From a09c01fcec2f703dfee9af8bfc07103007c723a0 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 28 Nov 2011 17:18:11 +0100 Subject: [PATCH 195/380] Removed walker argument FamilyStructure as this is now supported by the engine (ped file) --- .../gatk/walkers/varianteval/VariantEvalWalker.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) 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 10d4651b7..04bbdc169 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 @@ -161,13 +161,7 @@ public class VariantEvalWalker extends RodWalker implements Tr @Argument(fullName="minPhaseQuality", shortName="mpq", doc="Minimum phasing quality", required=false) protected double MIN_PHASE_QUALITY = 10.0; - /** - * This argument is a string formatted as dad+mom=child where these parameters determine which sample names are examined. - */ - @Argument(shortName="family", doc="If provided, genotypes in will be examined for mendelian violations", required=false) - protected String FAMILY_STRUCTURE; - - @Argument(shortName="mvq", fullName="mendelianViolationQualThreshold", doc="Minimum genotype QUAL score for each trio member required to accept a site as a violation", required=false) + @Argument(shortName="mvq", fullName="mendelianViolationQualThreshold", doc="Minimum genotype QUAL score for each trio member required to accept a site as a violation. Default is 50.", required=false) protected double MENDELIAN_VIOLATION_QUAL_THRESHOLD = 50; @Argument(fullName="ancestralAlignments", shortName="aa", doc="Fasta file with ancestral alleles", required=false) @@ -529,8 +523,6 @@ public class VariantEvalWalker extends RodWalker implements Tr public double getMinPhaseQuality() { return MIN_PHASE_QUALITY; } - public String getFamilyStructure() { return FAMILY_STRUCTURE; } - public double getMendelianViolationQualThreshold() { return MENDELIAN_VIOLATION_QUAL_THRESHOLD; } public TreeSet getStratificationObjects() { return stratificationObjects; } From d7d8b8e38030bef5518055734f8992805cb28bdf Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 28 Nov 2011 14:18:28 -0500 Subject: [PATCH 196/380] Tribble v42 changes the Codec.canDecode method to take in a String instead of a File; this is something that Jim was adamant about (because Tribble can handle streams other than files). I didn't want the next person who needed to rev Tribble to deal with this change additionally, so I took care of updating the GATK now. --- .../gatk/refdata/tracks/FeatureManager.java | 2 +- .../walkers/diffengine/VCFDiffableReader.java | 3 +-- .../utils/codecs/beagle/BeagleCodec.java | 2 +- .../utils/codecs/refseq/RefSeqCodec.java | 3 +-- .../sting/utils/codecs/table/TableCodec.java | 3 +-- .../utils/codecs/vcf/AbstractVCFCodec.java | 2 +- .../sting/utils/codecs/vcf/VCF3Codec.java | 3 +-- .../sting/utils/codecs/vcf/VCFCodec.java | 3 +-- .../{tribble-41.jar => tribble-42.jar} | Bin 301217 -> 301131 bytes .../{tribble-41.xml => tribble-42.xml} | 2 +- 10 files changed, 9 insertions(+), 14 deletions(-) rename settings/repository/org.broad/{tribble-41.jar => tribble-42.jar} (93%) rename settings/repository/org.broad/{tribble-41.xml => tribble-42.xml} (51%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java index c41444ef3..fcd85fd1d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManager.java @@ -155,7 +155,7 @@ public class FeatureManager { public FeatureDescriptor getByFiletype(File file) { List canParse = new ArrayList(); for ( FeatureDescriptor descriptor : featureDescriptors ) - if ( descriptor.getCodec().canDecode(file) ) { + if ( descriptor.getCodec().canDecode(file.getPath()) ) { canParse.add(descriptor); } 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 efa57c0aa..3c0da8e9d 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 @@ -33,7 +33,6 @@ import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; import java.util.Map; @@ -135,6 +134,6 @@ public class VCFDiffableReader implements DiffableReader { @Override public boolean canRead(File file) { - return AbstractVCFCodec.canDecodeFile(file, VCFCodec.VCF4_MAGIC_HEADER); + return AbstractVCFCodec.canDecodeFile(file.getPath(), VCFCodec.VCF4_MAGIC_HEADER); } } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java index 90d305d73..e4768fd5b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/beagle/BeagleCodec.java @@ -249,6 +249,6 @@ public class BeagleCodec implements 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 delta 7522 zcmai23tW_C)<5qBW?*Kx3CKlm0fGX8h?Z9{w2KO&2IeJ2(Gn3vLGccxvWm*ZS9we; zaV0NlhSEA&dCAS~D^V%aY(F*gt6kl-l{U*SlG<~g=bafwf8Xz$-|x)(%=w?&bIy66 z%bacY*>%{*k~LaU-2sdMD;MS0TVj>s>sxtZE$hUST)$OWsLtNOImx^$O(9(AClnnr9k>>&#+`wgqO zsg~YuLlw@MYwjjE6_)#gv&?&!%%M=Pn@E_C^X@QlPwXdnsszvZt16! zi}w0@0TA(tYt5OemNwp~1.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 Ay8r+H diff --git a/settings/repository/org.broad/tribble-41.xml b/settings/repository/org.broad/tribble-42.xml similarity index 51% rename from settings/repository/org.broad/tribble-41.xml rename to settings/repository/org.broad/tribble-42.xml index 6ee8bfb78..1c03ce1b1 100644 --- a/settings/repository/org.broad/tribble-41.xml +++ b/settings/repository/org.broad/tribble-42.xml @@ -1,3 +1,3 @@ - + From ab67011791ee5f25f30b9e192eb10f10e5a8e6c6 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Tue, 29 Nov 2011 11:18:15 +0100 Subject: [PATCH 197/380] Corrected bug introduced in the last update and causing no families to be returned by getFamilies in case the samples were not specified --- .../src/org/broadinstitute/sting/gatk/samples/SampleDB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java index 9f00257d1..929ad41d1 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -156,7 +156,7 @@ public class SampleDB { final Map> families = new TreeMap>(); for ( final Sample sample : samples.values() ) { - if(sampleIds != null && sampleIds.contains(sample.getID())){ + if(sampleIds == null || sampleIds.contains(sample.getID())){ final String famID = sample.getFamilyID(); if ( famID != null ) { if ( ! families.containsKey(famID) ) From 110298322cbb1ed8cb1e191b5d00f3d145628a9a Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Tue, 29 Nov 2011 09:29:18 -0500 Subject: [PATCH 198/380] Adding Transmission Disequilibrium Test annotation to VariantAnnotator and integration test to test it. --- .../TransmissionDisequilibriumTest.java | 102 ++++++++++++++++++ .../walkers/annotator/VariantAnnotator.java | 6 ++ .../sting/utils/MendelianViolation.java | 2 +- .../VariantAnnotatorIntegrationTest.java | 11 ++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java new file mode 100644 index 000000000..3de179365 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java @@ -0,0 +1,102 @@ +package org.broadinstitute.sting.gatk.walkers.annotator; + +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.samples.Sample; +import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.AnnotatorCompatibleWalker; +import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.ExperimentalAnnotation; +import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.InfoFieldAnnotation; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.MendelianViolation; +import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; +import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.text.XReadLines; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.io.FileNotFoundException; +import java.util.*; + +/** + * Created by IntelliJ IDEA. + * User: rpoplin + * Date: 11/14/11 + */ + +public class TransmissionDisequilibriumTest extends InfoFieldAnnotation implements ExperimentalAnnotation { + + private Set fullMVSet = null; + private final static int REF = 0; + private final static int HET = 1; + private final static int HOM = 2; + + public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { + if ( fullMVSet == null ) { + fullMVSet = new HashSet(); + + if ( walker instanceof VariantAnnotator ) { + final Map> families = ((VariantAnnotator) walker).getSampleDB().getFamilies(); + for( final Set family : families.values() ) { + for( final Sample sample : family ) { + if( sample.getParents().size() == 2 && family.containsAll(sample.getParents()) ) { // only works with trios for now + fullMVSet.add( new MendelianViolation(sample, 0.0) ); + } + } + } + } else { + throw new UserException("Transmission disequilibrium test annotation can only be used from the Variant Annotator and requires a valid ped file be passed in."); + } + } + + final Map toRet = new HashMap(1); + final HashSet mvsToTest = new HashSet(); + + for( final MendelianViolation mv : fullMVSet ) { + final boolean hasAppropriateGenotypes = vc.hasGenotype(mv.getSampleChild()) && vc.getGenotype(mv.getSampleChild()).hasLikelihoods() && + vc.hasGenotype(mv.getSampleDad()) && vc.getGenotype(mv.getSampleDad()).hasLikelihoods() && + vc.hasGenotype(mv.getSampleMom()) && vc.getGenotype(mv.getSampleMom()).hasLikelihoods(); + if ( hasAppropriateGenotypes ) { + mvsToTest.add(mv); + } + } + + toRet.put("TDT", calculateTDT( vc, mvsToTest )); + + return toRet; + } + + // return the descriptions used for the VCF INFO meta field + public List getKeyNames() { return Arrays.asList("TDT"); } + + public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine("TDT", 1, VCFHeaderLineType.Float, "Test statistic from Wittkowski transmission disequilibrium test.")); } + + // Following derivation in http://en.wikipedia.org/wiki/Transmission_disequilibrium_test#A_modified_version_of_the_TDT + private double calculateTDT( final VariantContext vc, final Set mvsToTest ) { + + final double nABGivenABandBB = calculateNChildren(vc, mvsToTest, HET, HET, HOM); + final double nBBGivenABandBB = calculateNChildren(vc, mvsToTest, HOM, HET, HOM); + final double nAAGivenABandAB = calculateNChildren(vc, mvsToTest, REF, HET, HET); + final double nBBGivenABandAB = calculateNChildren(vc, mvsToTest, HOM, HET, HET); + final double nAAGivenAAandAB = calculateNChildren(vc, mvsToTest, REF, REF, HET); + final double nABGivenAAandAB = calculateNChildren(vc, mvsToTest, HET, REF, HET); + + final double numer = (nABGivenABandBB - nBBGivenABandBB) + 2.0 * (nAAGivenABandAB - nBBGivenABandAB) + (nAAGivenAAandAB - nABGivenAAandAB); + final double denom = (nABGivenABandBB + nBBGivenABandBB) + 4.0 * (nAAGivenABandAB + nBBGivenABandAB) + (nAAGivenAAandAB + nABGivenAAandAB); + return (numer * numer) / denom; + } + + private double calculateNChildren( final VariantContext vc, final Set mvsToTest, final int childIdx, final int momIdx, final int dadIdx ) { + final double likelihoodVector[] = new double[mvsToTest.size() * 2]; + int iii = 0; + for( final MendelianViolation mv : mvsToTest ) { + final double[] momGL = vc.getGenotype(mv.getSampleMom()).getLikelihoods().getAsVector(); + final double[] dadGL = vc.getGenotype(mv.getSampleDad()).getLikelihoods().getAsVector(); + final double[] childGL = vc.getGenotype(mv.getSampleChild()).getLikelihoods().getAsVector(); + likelihoodVector[iii++] = momGL[momIdx] + dadGL[dadIdx] + childGL[childIdx]; + likelihoodVector[iii++] = momGL[dadIdx] + dadGL[momIdx] + childGL[childIdx]; + } + + return MathUtils.sumLog10(likelihoodVector); + } +} 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 c9ea7a3b5..143f2eb2e 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 @@ -32,6 +32,7 @@ import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.AlignmentContextUtils; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; +import org.broadinstitute.sting.gatk.samples.SampleDB; import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.*; import org.broadinstitute.sting.utils.BaseUtils; @@ -196,6 +197,11 @@ public class VariantAnnotator extends RodWalker implements Ann System.exit(0); } + @Override + public SampleDB getSampleDB() { + return super.getSampleDB(); + } + /** * Prepare the output file and the list of available features. */ diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index cf45dab79..d0579fc25 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -77,7 +77,7 @@ public class MendelianViolation { /** * An alternative to the more general constructor if you want to get the Sample information from the engine yourself. * @param sample - the sample object extracted from the sample metadata YAML file given to the engine. - * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to asses mendelian violation + * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to assess mendelian violation */ public MendelianViolation(Sample sample, double minGenotypeQualityP) { sampleMom = sample.getMother().getID(); 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 1824789a9..ffb9aedcc 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 @@ -168,4 +168,15 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { ); executeTest("Testing SnpEff annotations (unsupported version)", spec); } + + @Test + public void testTDTAnnotation() { + final String MD5 = "9fe37b61aab695ad47ce3c587148e91f"; + WalkerTestSpec spec = new WalkerTestSpec( + "-T VariantAnnotator -R " + b37KGReference + " -A TransmissionDisequilibriumTest --variant:vcf " + validationDataLocation + "ug.random50000.subset300bp.chr1.family.vcf" + + " -L " + validationDataLocation + "ug.random50000.subset300bp.chr1.family.vcf -NO_HEADER -ped " + validationDataLocation + "ug.random50000.family.ped -o %s", 1, + Arrays.asList(MD5)); + executeTest("Testing TDT annotation", spec); + } + } From 7d58db626e28283d472fe29d71643ce739eceace Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 30 Nov 2011 10:09:20 +0100 Subject: [PATCH 199/380] Added MendelianViolationEvaluator integration test --- .../varianteval/VariantEvalIntegrationTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 403ecce78..13876ff83 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 @@ -291,6 +291,17 @@ public class VariantEvalIntegrationTest extends WalkerTest { executeTestParallel("testVEGenotypeConcordance" + vcfFile, spec); } + @Test + public void testVEMendelianViolationEvaluator() { + String vcfFile = "/MendelianViolationEval.vcf"; + String pedFile = "/MendelianViolationEval.ped"; + + WalkerTestSpec spec = new WalkerTestSpec("-T VariantEval -R "+b37KGReference+" --eval " + variantEvalTestDataRoot + vcfFile + " -ped "+ variantEvalTestDataRoot + pedFile +" -noEV -EV MendelianViolationEvaluator -L 1:10109-10315 -o %s", + 1, + Arrays.asList("85a8fc01a1f50839667bfcd04155f735")); + executeTestParallel("testVEMendelianViolationEvaluator" + vcfFile, spec); + } + @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"; From f49dc5c067ed97820a1a509a17afea58142ffc77 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 30 Nov 2011 14:43:37 +0100 Subject: [PATCH 200/380] Added functionality to get all children that have both parents (useful when trios are needed) --- .../sting/gatk/samples/SampleDB.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java index 929ad41d1..1ed8dd7a3 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -168,6 +168,47 @@ public class SampleDB { return families; } + + /** + * Returns the set of all children that have both of their parents. + * Note that if a family is composed of more than 1 child, each child is + * returned. + * @return - all the children that have both of their parents + */ + public final Set getChildrenWithParents(){ + return getChildrenWithParents(false); + } + + /** + * Returns the set of all children that have both of their parents. + * Note that if triosOnly = false, a family is composed of more than 1 child, each child is + * returned. + * + * This method can be used wherever trios are needed + * + * @param triosOnly - if set to true, only strict trios are returned + * @return - all the children that have both of their parents + */ + public final Set getChildrenWithParents(boolean triosOnly) { + + Map> families = getFamilies(); + final Set childrenWithParents = new HashSet(); + Iterator sampleIterator; + + for ( Set familyMembers: families.values() ) { + if(triosOnly && familyMembers.size() != 3) + continue; + + sampleIterator = familyMembers.iterator(); + for(Sample sample = sampleIterator.next(); sampleIterator.hasNext(); sample = sampleIterator.next()){ + if(sample.getParents().size() == 2 && familyMembers.containsAll(sample.getParents())) + childrenWithParents.add(sample); + } + + } + return childrenWithParents; + } + /** * Return all samples with a given family ID * @param familyId From 9574be0394d9e61255548c63a95348c52fc26b68 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 30 Nov 2011 14:44:15 +0100 Subject: [PATCH 201/380] Updated MendelianViolationEvaluator integration test --- .../gatk/walkers/varianteval/VariantEvalIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 13876ff83..e2a5d89a7 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 @@ -296,7 +296,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { String vcfFile = "/MendelianViolationEval.vcf"; String pedFile = "/MendelianViolationEval.ped"; - WalkerTestSpec spec = new WalkerTestSpec("-T VariantEval -R "+b37KGReference+" --eval " + variantEvalTestDataRoot + vcfFile + " -ped "+ variantEvalTestDataRoot + pedFile +" -noEV -EV MendelianViolationEvaluator -L 1:10109-10315 -o %s", + WalkerTestSpec spec = new WalkerTestSpec("-T VariantEval -R "+b37KGReference+" --eval " + variantEvalTestDataRoot + vcfFile + " -ped "+ variantEvalTestDataRoot + pedFile +" -noEV -EV MendelianViolationEvaluator -L 1:10109-10315 -o %s -mvq 0 -noST", 1, Arrays.asList("85a8fc01a1f50839667bfcd04155f735")); executeTestParallel("testVEMendelianViolationEvaluator" + vcfFile, spec); From 1cb5e9e149161e88890a74fb4d1f523c7313b139 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 30 Nov 2011 14:45:04 +0100 Subject: [PATCH 202/380] Removed outdated (and unused) -familyStr commandline argument --- .../sting/gatk/walkers/annotator/VariantAnnotator.java | 3 --- 1 file changed, 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 143f2eb2e..94902e828 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 @@ -167,9 +167,6 @@ public class VariantAnnotator extends RodWalker implements Ann @Argument(fullName="vcfContainsOnlyIndels", shortName="dels",doc="Use if you are annotating an indel vcf, currently VERY experimental", required = false) protected boolean indelsOnly = false; - @Argument(fullName="family_string",shortName="family",required=false,doc="A family string of the form mom+dad=child for use with the mendelian violation ratio annotation") - public String familyStr = null; - @Argument(fullName="MendelViolationGenotypeQualityThreshold",shortName="mvq",required=false,doc="The genotype quality treshold in order to annotate mendelian violation ratio") public double minGenotypeQualityP = 0.0; From 20bffe0430a6fe79b8e9cc558bd3a55e25c91f37 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 30 Nov 2011 14:46:38 +0100 Subject: [PATCH 203/380] Adapted for the new version of MendelianViolation --- .../TransmissionDisequilibriumTest.java | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java index 3de179365..6cc8923e8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java @@ -12,10 +12,8 @@ import org.broadinstitute.sting.utils.MendelianViolation; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.text.XReadLines; import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import java.io.FileNotFoundException; import java.util.*; /** @@ -26,42 +24,33 @@ import java.util.*; public class TransmissionDisequilibriumTest extends InfoFieldAnnotation implements ExperimentalAnnotation { - private Set fullMVSet = null; + private Set trios = null; private final static int REF = 0; private final static int HET = 1; private final static int HOM = 2; public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { - if ( fullMVSet == null ) { - fullMVSet = new HashSet(); - + if ( trios == null ) { if ( walker instanceof VariantAnnotator ) { - final Map> families = ((VariantAnnotator) walker).getSampleDB().getFamilies(); - for( final Set family : families.values() ) { - for( final Sample sample : family ) { - if( sample.getParents().size() == 2 && family.containsAll(sample.getParents()) ) { // only works with trios for now - fullMVSet.add( new MendelianViolation(sample, 0.0) ); - } - } - } + trios = ((VariantAnnotator) walker).getSampleDB().getChildrenWithParents(); } else { throw new UserException("Transmission disequilibrium test annotation can only be used from the Variant Annotator and requires a valid ped file be passed in."); } } final Map toRet = new HashMap(1); - final HashSet mvsToTest = new HashSet(); + final HashSet triosToTest = new HashSet(); - for( final MendelianViolation mv : fullMVSet ) { - final boolean hasAppropriateGenotypes = vc.hasGenotype(mv.getSampleChild()) && vc.getGenotype(mv.getSampleChild()).hasLikelihoods() && - vc.hasGenotype(mv.getSampleDad()) && vc.getGenotype(mv.getSampleDad()).hasLikelihoods() && - vc.hasGenotype(mv.getSampleMom()) && vc.getGenotype(mv.getSampleMom()).hasLikelihoods(); + for( final Sample child : trios) { + final boolean hasAppropriateGenotypes = vc.hasGenotype(child.getID()) && vc.getGenotype(child.getID()).hasLikelihoods() && + vc.hasGenotype(child.getPaternalID()) && vc.getGenotype(child.getPaternalID()).hasLikelihoods() && + vc.hasGenotype(child.getMaternalID()) && vc.getGenotype(child.getMaternalID()).hasLikelihoods(); if ( hasAppropriateGenotypes ) { - mvsToTest.add(mv); + triosToTest.add(child); } } - toRet.put("TDT", calculateTDT( vc, mvsToTest )); + toRet.put("TDT", calculateTDT( vc, triosToTest )); return toRet; } @@ -72,27 +61,27 @@ public class TransmissionDisequilibriumTest extends InfoFieldAnnotation implemen public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine("TDT", 1, VCFHeaderLineType.Float, "Test statistic from Wittkowski transmission disequilibrium test.")); } // Following derivation in http://en.wikipedia.org/wiki/Transmission_disequilibrium_test#A_modified_version_of_the_TDT - private double calculateTDT( final VariantContext vc, final Set mvsToTest ) { + private double calculateTDT( final VariantContext vc, final Set triosToTest ) { - final double nABGivenABandBB = calculateNChildren(vc, mvsToTest, HET, HET, HOM); - final double nBBGivenABandBB = calculateNChildren(vc, mvsToTest, HOM, HET, HOM); - final double nAAGivenABandAB = calculateNChildren(vc, mvsToTest, REF, HET, HET); - final double nBBGivenABandAB = calculateNChildren(vc, mvsToTest, HOM, HET, HET); - final double nAAGivenAAandAB = calculateNChildren(vc, mvsToTest, REF, REF, HET); - final double nABGivenAAandAB = calculateNChildren(vc, mvsToTest, HET, REF, HET); + final double nABGivenABandBB = calculateNChildren(vc, triosToTest, HET, HET, HOM); + final double nBBGivenABandBB = calculateNChildren(vc, triosToTest, HOM, HET, HOM); + final double nAAGivenABandAB = calculateNChildren(vc, triosToTest, REF, HET, HET); + final double nBBGivenABandAB = calculateNChildren(vc, triosToTest, HOM, HET, HET); + final double nAAGivenAAandAB = calculateNChildren(vc, triosToTest, REF, REF, HET); + final double nABGivenAAandAB = calculateNChildren(vc, triosToTest, HET, REF, HET); final double numer = (nABGivenABandBB - nBBGivenABandBB) + 2.0 * (nAAGivenABandAB - nBBGivenABandAB) + (nAAGivenAAandAB - nABGivenAAandAB); final double denom = (nABGivenABandBB + nBBGivenABandBB) + 4.0 * (nAAGivenABandAB + nBBGivenABandAB) + (nAAGivenAAandAB + nABGivenAAandAB); return (numer * numer) / denom; } - private double calculateNChildren( final VariantContext vc, final Set mvsToTest, final int childIdx, final int momIdx, final int dadIdx ) { - final double likelihoodVector[] = new double[mvsToTest.size() * 2]; + private double calculateNChildren( final VariantContext vc, final Set triosToTest, final int childIdx, final int momIdx, final int dadIdx ) { + final double likelihoodVector[] = new double[triosToTest.size() * 2]; int iii = 0; - for( final MendelianViolation mv : mvsToTest ) { - final double[] momGL = vc.getGenotype(mv.getSampleMom()).getLikelihoods().getAsVector(); - final double[] dadGL = vc.getGenotype(mv.getSampleDad()).getLikelihoods().getAsVector(); - final double[] childGL = vc.getGenotype(mv.getSampleChild()).getLikelihoods().getAsVector(); + for( final Sample child : triosToTest ) { + final double[] momGL = vc.getGenotype(child.getMaternalID()).getLikelihoods().getAsVector(); + final double[] dadGL = vc.getGenotype(child.getPaternalID()).getLikelihoods().getAsVector(); + final double[] childGL = vc.getGenotype(child.getID()).getLikelihoods().getAsVector(); likelihoodVector[iii++] = momGL[momIdx] + dadGL[dadIdx] + childGL[childIdx]; likelihoodVector[iii++] = momGL[dadIdx] + dadGL[momIdx] + childGL[childIdx]; } From 1d5d200790866c635df9fd29fc6597c9f1d13d66 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 30 Nov 2011 15:30:30 +0100 Subject: [PATCH 204/380] Cleaned up unused import statements --- .../org/broadinstitute/sting/utils/MendelianViolation.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index e140575c0..b9c209e69 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -1,15 +1,10 @@ package org.broadinstitute.sting.utils; import org.broadinstitute.sting.gatk.samples.Sample; -import org.broadinstitute.sting.utils.codecs.vcf.VCFHeaderLineType; -import org.broadinstitute.sting.utils.codecs.vcf.VCFInfoHeaderLine; -import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * User: carneiro / lfran From b65db6a8541c99b2c1e03381a8125f739a4b3b23 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Wed, 30 Nov 2011 13:13:16 -0500 Subject: [PATCH 205/380] First draft of a test script for I/O performance with the new asynchronous I/O processing. Also includes convenience parameters for specifying the IO/CPU threading balance outside of a tag. Will be killed when Queue gets better support for tagged arguments (hopefully soon). --- .../sting/gatk/GenomeAnalysisEngine.java | 20 +++++++++++++++++-- .../arguments/GATKArgumentCollection.java | 19 ++++++++++++++++++ .../gatk/datasources/reads/SAMDataSource.java | 4 +++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index f2e0b5d0c..7cc8e9e29 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -280,8 +280,24 @@ public class GenomeAnalysisEngine { */ 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; + + // TODO: Kill this complicated logic once Queue supports arbitrary tagged parameters. + Integer numCPUThreads = null; + if(tags.containsKey("cpu") && argCollection.numberOfCPUThreads != null) + throw new UserException("Number of CPU threads specified both directly on the command-line and as a tag to the nt argument. Please specify only one or the other."); + else if(tags.containsKey("cpu")) + numCPUThreads = Integer.parseInt(tags.getValue("cpu")); + else if(argCollection.numberOfCPUThreads != null) + numCPUThreads = argCollection.numberOfCPUThreads; + + Integer numIOThreads = null; + if(tags.containsKey("io") && argCollection.numberOfIOThreads != null) + throw new UserException("Number of IO threads specified both directly on the command-line and as a tag to the nt argument. Please specify only one or the other."); + else if(tags.containsKey("io")) + numIOThreads = Integer.parseInt(tags.getValue("io")); + else if(argCollection.numberOfIOThreads != null) + numIOThreads = argCollection.numberOfIOThreads; + this.threadAllocation = new ThreadAllocation(argCollection.numberOfThreads,numCPUThreads,numIOThreads); } 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 64b63dcd2..08d2c1ad1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -198,6 +198,17 @@ public class GATKArgumentCollection { @Argument(fullName = "num_threads", shortName = "nt", doc = "How many threads should be allocated to running this analysis.", required = false) public Integer numberOfThreads = 1; + /** + * The following two arguments (num_cpu_threads, num_io_threads are TEMPORARY since Queue cannot currently support arbitrary tagged data types. + * TODO: Kill this when I can do a tagged integer in Queue. + */ + @Argument(fullName="num_cpu_threads", shortName = "nct", doc="How many of the given threads should be allocated to the CPU", required = false) + @Hidden + public Integer numberOfCPUThreads = null; + @Argument(fullName="num_io_threads", shortName = "nit", doc="How many of the given threads should be allocated to IO", required = false) + @Hidden + public Integer numberOfIOThreads = null; + @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; @@ -369,6 +380,14 @@ public class GATKArgumentCollection { if (!other.numberOfThreads.equals(this.numberOfThreads)) { return false; } + if ((this.numberOfCPUThreads == null && other.numberOfCPUThreads != null) || + this.numberOfCPUThreads.equals(other.numberOfCPUThreads) ) { + return false; + } + if ((this.numberOfIOThreads == null && other.numberOfIOThreads != null) || + this.numberOfIOThreads.equals(other.numberOfIOThreads) ) { + return false; + } if ((other.numberOfBAMFileHandles == null && this.numberOfBAMFileHandles != null) || (other.numberOfBAMFileHandles != null && !other.numberOfBAMFileHandles.equals(this.numberOfBAMFileHandles))) { return false; 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 0a1eb0563..0ace6fde2 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 @@ -242,8 +242,10 @@ public class SAMDataSource { this.threadAllocation = threadAllocation; // TODO: Consider a borrowed-thread dispatcher implementation. - if(this.threadAllocation.getNumIOThreads() > 0) + if(this.threadAllocation.getNumIOThreads() > 0) { + logger.info("Running in asynchronous I/O mode; number of threads = " + this.threadAllocation.getNumIOThreads()); dispatcher = new BGZFBlockLoadingDispatcher(this.threadAllocation.getNumIOThreads(), numFileHandles != null ? numFileHandles : 1); + } else dispatcher = null; From 3060a4a15ebc340512144d45a27aa393b27282d7 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 30 Nov 2011 17:05:16 -0500 Subject: [PATCH 206/380] Support for list of known CNVs in VariantEval -- VariantSummary now includes novelty of CNVs by reciprocal overlap detection using the standard variant eval -knownCNVs argument -- Genericizes loading for intervals into interval tree by chromosome -- GenomeLoc methods for reciprocal overlap detection, with unit tests --- .../varianteval/VariantEvalWalker.java | 44 +++++- .../evaluators/VariantSummary.java | 139 ++++++++++++------ .../IntervalStratification.java | 21 ++- .../broadinstitute/sting/utils/GenomeLoc.java | 25 ++++ .../org/broadinstitute/sting/BaseTest.java | 6 +- .../sting/utils/GenomeLocUnitTest.java | 61 ++++++++ 6 files changed, 233 insertions(+), 63 deletions(-) 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 10d4651b7..1504f5baf 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 @@ -1,10 +1,12 @@ package org.broadinstitute.sting.gatk.walkers.varianteval; import net.sf.picard.reference.IndexedFastaSequenceFile; +import net.sf.picard.util.IntervalTree; import net.sf.samtools.SAMSequenceRecord; import org.apache.log4j.Logger; import org.broad.tribble.Feature; import org.broadinstitute.sting.commandline.*; +import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.arguments.DbsnpArgumentCollection; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; @@ -30,6 +32,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.VCFUtils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.interval.IntervalUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; @@ -189,6 +192,13 @@ public class VariantEvalWalker extends RodWalker implements Tr @Input(fullName="stratIntervals", shortName="stratIntervals", doc="File containing tribble-readable features for the IntervalStratificiation", required=false) public IntervalBinding intervalsFile = null; + /** + * File containing tribble-readable features containing known CNVs. For use with VariantSummary table. + */ + @Input(fullName="knownCNVs", shortName="knownCNVs", doc="File containing tribble-readable features describing a known list of copy number variants", required=false) + public IntervalBinding knownCNVsFile = null; + Map> knownCNVsByContig = Collections.emptyMap(); + // Variables private Set jexlExpressions = new TreeSet(); @@ -295,6 +305,28 @@ public class VariantEvalWalker extends RodWalker implements Tr throw new ReviewedStingException(String.format("The ancestral alignments file, '%s', could not be found", ancestralAlignmentsFile.getAbsolutePath())); } } + + + // initialize CNVs + if ( knownCNVsFile != null ) { + knownCNVsByContig = createIntervalTreeByContig(knownCNVsFile); + } + } + + public final Map> createIntervalTreeByContig(final IntervalBinding intervals) { + final Map> byContig = new HashMap>(); + + final List locs = intervals.getIntervals(getToolkit()); + + // set up the map from contig -> interval tree + for ( final String contig : getContigNames() ) + byContig.put(contig, new IntervalTree()); + + for ( final GenomeLoc loc : locs ) { + byContig.get(loc.getContig()).put(loc.getStart(), loc.getStop(), loc); + } + + return byContig; } /** @@ -549,14 +581,6 @@ public class VariantEvalWalker extends RodWalker implements Tr public Set getJexlExpressions() { return jexlExpressions; } - public List getIntervals() { - if ( intervalsFile == null ) - throw new UserException.MissingArgument("stratIntervals", "Must be provided when IntervalStratification is enabled"); - - return intervalsFile.getIntervals(getToolkit()); - } - - public Set getContigNames() { final TreeSet contigs = new TreeSet(); for( final SAMSequenceRecord r : getToolkit().getReferenceDataSource().getReference().getSequenceDictionary().getSequences()) { @@ -568,4 +592,8 @@ public class VariantEvalWalker extends RodWalker implements Tr public GenomeLocParser getGenomeLocParser() { return getToolkit().getGenomeLocParser(); } + + public GenomeAnalysisEngine getToolkit() { + return super.getToolkit(); + } } \ No newline at end of file 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 index 503cb8ff4..b74af9f91 100644 --- 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 @@ -24,25 +24,38 @@ package org.broadinstitute.sting.gatk.walkers.varianteval.evaluators; +import net.sf.picard.util.IntervalTree; +import org.apache.log4j.Logger; 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.GenomeLoc; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.interval.IntervalUtils; 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; +import java.util.*; @Analysis(description = "1000 Genomes Phase I summary of variants table") public class VariantSummary extends VariantEvaluator implements StandardEval { + final protected static Logger logger = Logger.getLogger(VariantSummary.class); + + private final static int MAX_INDEL_LENGTH = 50; + private final static double MIN_CNV_OVERLAP = 0.5; + private VariantEvalWalker walker; + + public enum Type { + SNP, INDEL, CNV + } + + Map> knownCNVs = null; + // basic counts on various rates found @DataPoint(description = "Number of samples") public long nSamples = 0; @@ -86,10 +99,10 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { private final static String ALL = "ALL"; - private class TypeSampleMap extends EnumMap> { + private class TypeSampleMap extends EnumMap> { public TypeSampleMap(final Collection samples) { - super(VariantContext.Type.class); - for ( VariantContext.Type type : VariantContext.Type.values() ) { + super(Type.class); + for ( Type type : Type.values() ) { Map bySample = new HashMap(samples.size()); for ( final String sample : samples ) { bySample.put(sample, 0); @@ -99,16 +112,16 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { } } - public final void inc(final VariantContext.Type type, final String sample) { + public final void inc(final 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) { + public final int all(Type type) { return get(type).get(ALL); } - public final int meanValue(VariantContext.Type type) { + public final int meanValue(Type type) { long sum = 0; int n = 0; for ( final Map.Entry pair : get(type).entrySet() ) { @@ -120,7 +133,7 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { return (int)(Math.round(sum / (1.0 * n))); } - public final double ratioValue(VariantContext.Type type, TypeSampleMap denoms, boolean allP) { + public final double ratioValue(Type type, TypeSampleMap denoms, boolean allP) { double sum = 0; int n = 0; for ( final String sample : get(type).keySet() ) { @@ -137,6 +150,8 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { public void initialize(VariantEvalWalker walker) { + this.walker = walker; + nSamples = walker.getSampleNamesForEvaluation().size(); countsPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); transitionsPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); @@ -144,6 +159,13 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { allVariantCounts = new TypeSampleMap(walker.getSampleNamesForEvaluation()); knownVariantCounts = new TypeSampleMap(walker.getSampleNamesForEvaluation()); depthPerSample = new TypeSampleMap(walker.getSampleNamesForEvaluation()); + + if ( walker.knownCNVsFile != null ) { + knownCNVs = walker.createIntervalTreeByContig(walker.knownCNVsFile); + final List locs = walker.knownCNVsFile.getIntervals(walker.getToolkit()); + logger.info(String.format("Creating known CNV list %s containing %d intervals covering %d bp", + walker.knownCNVsFile.getSource(), locs.size(), IntervalUtils.intervalSize(locs))); + } } @Override public boolean enabled() { return true; } @@ -156,44 +178,77 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { nProcessedLoci += context.getSkippedBases() + (ref == null ? 0 : 1); } + private final Type getType(VariantContext vc) { + switch (vc.getType()) { + case SNP: + return Type.SNP; + case INDEL: + for ( int l : vc.getIndelLengths() ) + if ( l > MAX_INDEL_LENGTH ) + return Type.CNV; + return Type.INDEL; + case SYMBOLIC: + return Type.CNV; + default: + throw new UserException.BadInput("Unexpected variant context type: " + vc); + } + } + + private final boolean overlapsKnownCNV(VariantContext cnv) { + final GenomeLoc loc = walker.getGenomeLocParser().createGenomeLoc(cnv, true); + IntervalTree intervalTree = knownCNVs.get(loc.getContig()); + + final Iterator> nodeIt = intervalTree.overlappers(loc.getStart(), loc.getStop()); + while ( nodeIt.hasNext() ) { + final double overlapP = loc.reciprocialOverlapFraction(nodeIt.next().getValue()); + if ( overlapP > MIN_CNV_OVERLAP ) + return true; + } + + return false; + } + public String update2(VariantContext eval, VariantContext comp, RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { if ( eval == null || eval.isMonomorphicInSamples() ) return null; + final Type type = getType(eval); + 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); + // update DP, if possible + if ( eval.hasAttribute(VCFConstants.DEPTH_KEY) ) + depthPerSample.inc(type, ALL); + + // update counts + allVariantCounts.inc(type, ALL); + + // type specific calculations + if ( type == Type.SNP ) { + titvTable = VariantContextUtils.isTransition(eval) ? transitionsPerSample : transversionsPerSample; + titvTable.inc(type, ALL); } + // novelty calculation + if ( comp != null || (type == Type.CNV && overlapsKnownCNV(eval))) + knownVariantCounts.inc(type, ALL); + // per sample metrics for (final Genotype g : eval.getGenotypes()) { if ( ! g.isNoCall() && ! g.isHomRef() ) { - countsPerSample.inc(eval.getType(), g.getSampleName()); + countsPerSample.inc(type, g.getSampleName()); // update transition / transversion ratio - if ( titvTable != null ) titvTable.inc(eval.getType(), g.getSampleName()); + if ( titvTable != null ) titvTable.inc(type, g.getSampleName()); if ( g.hasAttribute(VCFConstants.DEPTH_KEY) ) - depthPerSample.inc(eval.getType(), g.getSampleName()); + depthPerSample.inc(type, g.getSampleName()); } } return null; // we don't capture any interesting sites } - private final String noveltyRate(VariantContext.Type type) { + private final String noveltyRate(Type type) { final int all = allVariantCounts.all(type); final int known = knownVariantCounts.all(type); final int novel = all - known; @@ -202,22 +257,22 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { } public void finalizeEvaluation() { - nSNPs = allVariantCounts.all(VariantContext.Type.SNP); - nIndels = allVariantCounts.all(VariantContext.Type.INDEL); - nSVs = allVariantCounts.all(VariantContext.Type.SYMBOLIC); + nSNPs = allVariantCounts.all(Type.SNP); + nIndels = allVariantCounts.all(Type.INDEL); + nSVs = allVariantCounts.all(Type.CNV); - TiTvRatio = transitionsPerSample.ratioValue(VariantContext.Type.SNP, transversionsPerSample, true); - TiTvRatioPerSample = transitionsPerSample.ratioValue(VariantContext.Type.SNP, transversionsPerSample, false); + TiTvRatio = transitionsPerSample.ratioValue(Type.SNP, transversionsPerSample, true); + TiTvRatioPerSample = transitionsPerSample.ratioValue(Type.SNP, transversionsPerSample, false); - nSNPsPerSample = countsPerSample.meanValue(VariantContext.Type.SNP); - nIndelsPerSample = countsPerSample.meanValue(VariantContext.Type.INDEL); - nSVsPerSample = countsPerSample.meanValue(VariantContext.Type.SYMBOLIC); + nSNPsPerSample = countsPerSample.meanValue(Type.SNP); + nIndelsPerSample = countsPerSample.meanValue(Type.INDEL); + nSVsPerSample = countsPerSample.meanValue(Type.CNV); - SNPNoveltyRate = noveltyRate(VariantContext.Type.SNP); - IndelNoveltyRate = noveltyRate(VariantContext.Type.INDEL); - SVNoveltyRate = noveltyRate(VariantContext.Type.SYMBOLIC); + SNPNoveltyRate = noveltyRate(Type.SNP); + IndelNoveltyRate = noveltyRate(Type.INDEL); + SVNoveltyRate = noveltyRate(Type.CNV); - SNPDPPerSample = depthPerSample.meanValue(VariantContext.Type.SNP); - IndelDPPerSample = depthPerSample.meanValue(VariantContext.Type.INDEL); + SNPDPPerSample = depthPerSample.meanValue(Type.SNP); + IndelDPPerSample = depthPerSample.meanValue(Type.INDEL); } } \ No newline at end of file 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 00a656cc6..d91422a7e 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 @@ -54,26 +54,23 @@ import java.util.*; */ public class IntervalStratification extends VariantStratifier { final protected static Logger logger = Logger.getLogger(IntervalStratification.class); - final Map> intervalTreeByContig = new HashMap>(); + Map> intervalTreeByContig = null; @Override public void initialize() { - final List locs = getVariantEvalWalker().getIntervals(); + if ( getVariantEvalWalker().intervalsFile == null ) + throw new UserException.MissingArgument("stratIntervals", "Must be provided when IntervalStratification is enabled"); + + final List locs = getVariantEvalWalker().intervalsFile.getIntervals(getVariantEvalWalker().getToolkit()); if ( locs.isEmpty() ) throw new UserException.BadArgumentValue("stratIntervals", "Contains no intervals. Perhaps the file is malformed or empty?"); + intervalTreeByContig = getVariantEvalWalker().createIntervalTreeByContig(getVariantEvalWalker().intervalsFile); + 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() ) - intervalTreeByContig.put(contig, new IntervalTree()); - - for ( final GenomeLoc loc : locs ) { - intervalTreeByContig.get(loc.getContig()).put(loc.getStart(), loc.getStop(), true); - } - states = new ArrayList(Arrays.asList("all", "overlaps.intervals", "outside.intervals")); } @@ -82,8 +79,8 @@ public class IntervalStratification extends VariantStratifier { if (eval != null) { final GenomeLoc loc = getVariantEvalWalker().getGenomeLocParser().createGenomeLoc(eval, true); - IntervalTree intervalTree = intervalTreeByContig.get(loc.getContig()); - IntervalTree.Node node = intervalTree.minOverlapper(loc.getStart(), loc.getStop()); + IntervalTree intervalTree = intervalTreeByContig.get(loc.getContig()); + IntervalTree.Node node = intervalTree.minOverlapper(loc.getStart(), loc.getStop()); //logger.info(String.format("Overlap %s found %s", loc, node)); relevantStates.add( node != null ? "overlaps.intervals" : "outside.intervals"); } diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java index c1479bc69..345161416 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java @@ -440,4 +440,29 @@ public class GenomeLoc implements Comparable, Serializable, HasGenome return stop - start + 1; } + /** + * reciprocialOverlap: what is the min. percent of gl1 and gl2 covered by both + * + * gl1.s ---------- gk1.e + * gl2.s ---------- gl2.e + * 100% + * + * gl1.s ---------- gk1.e + * gl2.s ---------- gl2.e + * 50% + * + * gl1.s ---------- gk1.e + * gl2.s -------------------- gl2.e + * 25% (50% for gl1 but only 25% for gl2) + */ + public final double reciprocialOverlapFraction(final GenomeLoc o) { + if ( overlapsP(o) ) + return Math.min(overlapPercent(this, o), overlapPercent(o, this)); + else + return 0.0; + } + + private final static double overlapPercent(final GenomeLoc gl1, final GenomeLoc gl2) { + return (1.0 * gl1.intersect(gl2).size()) / gl1.size(); + } } diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java index f99a105ae..5032a7810 100755 --- a/public/java/test/org/broadinstitute/sting/BaseTest.java +++ b/public/java/test/org/broadinstitute/sting/BaseTest.java @@ -134,7 +134,7 @@ public abstract class BaseTest { */ public static class TestDataProvider { private static final Map> tests = new HashMap>(); - private final String name; + private String name; /** * Create a new TestDataProvider instance bound to the class variable C @@ -151,6 +151,10 @@ public abstract class BaseTest { this(c, ""); } + public void setName(final String name) { + this.name = name; + } + /** * Return all of the data providers in the form expected by TestNG of type class C * @param c diff --git a/public/java/test/org/broadinstitute/sting/utils/GenomeLocUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/GenomeLocUnitTest.java index 29c085b70..49778a4d8 100644 --- a/public/java/test/org/broadinstitute/sting/utils/GenomeLocUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/GenomeLocUnitTest.java @@ -9,6 +9,7 @@ import org.broadinstitute.sting.utils.interval.IntervalMergingRule; import org.broadinstitute.sting.utils.interval.IntervalUtils; import org.testng.Assert; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; @@ -150,4 +151,64 @@ public class GenomeLocUnitTest extends BaseTest { Assert.assertEquals(twoUnmappedMixed.size(),2,"Wrong number of elements in list."); Assert.assertEquals(twoUnmappedMixed,Arrays.asList(chr1,unmapped),"List sorted in wrong order"); } + + // ------------------------------------------------------------------------------------- + // + // testing overlap detection + // + // ------------------------------------------------------------------------------------- + + private class ReciprocalOverlapProvider extends TestDataProvider { + GenomeLoc gl1, gl2; + int overlapSize; + double overlapFraction; + + private ReciprocalOverlapProvider(int start1, int stop1, int start2, int stop2) { + super(ReciprocalOverlapProvider.class); + gl1 = genomeLocParser.createGenomeLoc("chr1", start1, stop1); + gl2 = genomeLocParser.createGenomeLoc("chr1", start2, stop2); + + int shared = 0; + for ( int i = start1; i <= stop1; i++ ) { + if ( i >= start2 && i <= stop2 ) + shared++; + } + + this.overlapSize = shared; + this.overlapFraction = Math.min((1.0*shared)/gl1.size(), (1.0*shared)/gl2.size()); + super.setName(String.format("%d-%d / %d-%d overlap=%d / %.2f", start1, stop1, start2, stop2, overlapSize, overlapFraction)); + } + } + + @DataProvider(name = "ReciprocalOverlapProvider") + public Object[][] makeReciprocalOverlapProvider() { + for ( int start1 = 1; start1 <= 10; start1++ ) { + for ( int stop1 = start1; stop1 <= 10; stop1++ ) { + new ReciprocalOverlapProvider(start1, stop1, 1, 10); + new ReciprocalOverlapProvider(start1, stop1, 5, 10); + new ReciprocalOverlapProvider(start1, stop1, 5, 7); + new ReciprocalOverlapProvider(start1, stop1, 5, 15); + new ReciprocalOverlapProvider(start1, stop1, 11, 20); + + new ReciprocalOverlapProvider(1, 10, start1, stop1); + new ReciprocalOverlapProvider(5, 10, start1, stop1); + new ReciprocalOverlapProvider(5, 7, start1, stop1); + new ReciprocalOverlapProvider(5, 15, start1, stop1); + new ReciprocalOverlapProvider(11, 20, start1, stop1); + } + } + + return ReciprocalOverlapProvider.getTests(ReciprocalOverlapProvider.class); + } + + @Test(dataProvider = "ReciprocalOverlapProvider") + public void testReciprocalOverlapProvider(ReciprocalOverlapProvider cfg) { + if ( cfg.overlapSize == 0 ) { + Assert.assertFalse(cfg.gl1.overlapsP(cfg.gl2)); + } else { + Assert.assertTrue(cfg.gl1.overlapsP(cfg.gl2)); + Assert.assertEquals(cfg.gl1.intersect(cfg.gl2).size(), cfg.overlapSize); + Assert.assertEquals(cfg.gl1.reciprocialOverlapFraction(cfg.gl2), cfg.overlapFraction); + } + } } From c9eae32f6e607be91a92e5a99988d803b9d4de53 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Wed, 30 Nov 2011 22:42:21 -0500 Subject: [PATCH 207/380] Revving Tribble to actually close file handles when close() is called. --- .../{tribble-42.jar => tribble-46.jar} | Bin 301131 -> 301252 bytes .../{tribble-42.xml => tribble-46.xml} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename settings/repository/org.broad/{tribble-42.jar => tribble-46.jar} (92%) rename settings/repository/org.broad/{tribble-42.xml => tribble-46.xml} (51%) diff --git a/settings/repository/org.broad/tribble-42.jar b/settings/repository/org.broad/tribble-46.jar similarity index 92% rename from settings/repository/org.broad/tribble-42.jar rename to settings/repository/org.broad/tribble-46.jar index 2c798364cee483735ed679f6504ce6a8c5845e4b..401fcfc3a92c558e6975e80ed948831447bb6bc2 100644 GIT binary patch delta 6986 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 PqJzz%MAui-FiG%#;h(Hm delta 7105 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 HFO~lT4kdi$ diff --git a/settings/repository/org.broad/tribble-42.xml b/settings/repository/org.broad/tribble-46.xml similarity index 51% rename from settings/repository/org.broad/tribble-42.xml rename to settings/repository/org.broad/tribble-46.xml index 1c03ce1b1..bb8df5c87 100644 --- a/settings/repository/org.broad/tribble-42.xml +++ b/settings/repository/org.broad/tribble-46.xml @@ -1,3 +1,3 @@ - + From d014c7faf984a866753263f0a4031848a888f35f Mon Sep 17 00:00:00 2001 From: David Roazen Date: Mon, 28 Nov 2011 13:35:14 -0500 Subject: [PATCH 210/380] Queue now properly escapes all shell arguments in generated shell scripts This has implications for both Qscript authors and CommandLineFunction authors. Qscript authors: You no longer need to (and in fact must not) manually escape String values to avoid interpretation by the shell when setting up Walker parameters. Queue will safely escape all of your Strings for you so that they'll be interpreted literally. Eg., Old way: filterSNPs.filterExpression = List("\"QD<2.0\"", "\"MQ<40.0\"", "\"HaplotypeScore>13.0\"") New way: filterSNPs.filterExpression = List("QD<2.0", "MQ<40.0", "HaplotypeScore>13.0") CommandLineFunction authors: If you're writing a one-off CommandLineFunction in a Qscript and don't really care about quoting issues, just keep doing things the direct, simple way: def commandLine = "cat %s | grep -v \"#\" > %s".format(files, out) If you're writing a CommandLineFunction that will become part of Queue and will be used by other QScripts, however, it's advisable to do things the newer, safer way, ie.: When you construct your commandLine, you should do so ONLY using the API methods required(), optional(), conditional(), and repeat(). These will manage quoting and whitespace separation for you, so you shouldn't insert quotes/extraneous whitespace in your Strings. By default you get both (quoting and whitespace separation), but you can disable either of these via parameters. Eg., override def commandLine = super.commandLine + required("eff") + conditional(verbose, "-v") + optional("-c", config) + required("-i", "vcf") + required("-o", "vcf") + required(genomeVersion) + required(inVcf) + required(">", escape=false) + // This will be shell-interpreted required(outVcf) I've ported the Picard/Samtools/SnpEff CommandLineFunction classes to the new system, so you'll get free shell escaping when you use those in Qscripts just like with walkers. --- .../gatk/ArgumentDefinitionField.java | 20 +- .../gatk/GATKExtensionsGenerator.java | 3 + .../queue/extensions/gatk/RodBindField.java | 129 ----------- .../queue/qscripts/GATKResourcesBundle.scala | 2 +- .../MethodsDevelopmentCallingPipeline.scala | 4 +- .../examples/ExampleUnifiedGenotyper.scala | 2 +- .../sting/queue/extensions/gatk/RodBind.scala | 17 +- .../queue/extensions/gatk/TaggedFile.scala | 18 +- .../picard/AddOrReplaceReadGroups.scala | 14 +- .../extensions/picard/MarkDuplicates.scala | 10 +- .../extensions/picard/MergeSamFiles.scala | 8 +- .../extensions/picard/PicardBamFunction.scala | 19 +- .../queue/extensions/picard/ReorderSam.scala | 6 +- .../queue/extensions/picard/RevertSam.scala | 12 +- .../queue/extensions/picard/SamToFastq.scala | 26 +-- .../extensions/picard/ValidateSamFile.scala | 14 +- .../samtools/SamtoolsIndexFunction.scala | 5 +- .../samtools/SamtoolsMergeFunction.scala | 8 +- .../queue/extensions/snpeff/SnpEff.scala | 17 +- .../queue/function/CommandLineFunction.scala | 209 ++++++++++++++---- .../function/JavaCommandLineFunction.scala | 34 +-- .../sting/queue/util/ShellUtils.scala | 36 +++ .../CommandLineFunctionUnitTest.scala | 171 ++++++++++++++ .../sting/queue/util/ShellUtilsUnitTest.scala | 57 +++++ 24 files changed, 545 insertions(+), 296 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/queue/extensions/gatk/RodBindField.java create mode 100644 public/scala/src/org/broadinstitute/sting/queue/util/ShellUtils.scala create mode 100644 public/scala/test/org/broadinstitute/sting/queue/function/CommandLineFunctionUnitTest.scala create mode 100644 public/scala/test/org/broadinstitute/sting/queue/util/ShellUtilsUnitTest.scala diff --git a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/ArgumentDefinitionField.java b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/ArgumentDefinitionField.java index cb5bad4ae..cdfc329e8 100644 --- a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/ArgumentDefinitionField.java +++ b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/ArgumentDefinitionField.java @@ -83,10 +83,10 @@ public abstract class ArgumentDefinitionField extends ArgumentField { getShortFieldSetter()); } - protected static final String REQUIRED_TEMPLATE = " + \" %1$s \" + %2$s.format(%3$s)"; - protected static final String REPEAT_TEMPLATE = " + repeat(\" %1$s \", %3$s, format=formatValue(%2$s))"; - protected static final String OPTIONAL_TEMPLATE = " + optional(\" %1$s \", %3$s, format=formatValue(%2$s))"; - protected static final String FLAG_TEMPLATE = " + (if (%3$s) \" %1$s\" else \"\")"; + protected static final String REQUIRED_TEMPLATE = " + required(\"%1$s\", %3$s, spaceSeparated=true, escape=true, format=%2$s)"; + protected static final String REPEAT_TEMPLATE = " + repeat(\"%1$s\", %3$s, spaceSeparated=true, escape=true, format=%2$s)"; + protected static final String OPTIONAL_TEMPLATE = " + optional(\"%1$s\", %3$s, spaceSeparated=true, escape=true, format=%2$s)"; + protected static final String FLAG_TEMPLATE = " + conditional(%3$s, \"%1$s\", escape=true, format=%2$s)"; public final String getCommandLineAddition() { return String.format(getCommandLineTemplate(), getCommandLineParam(), getCommandLineFormat(), getFieldName()); @@ -136,7 +136,7 @@ public abstract class ArgumentDefinitionField extends ArgumentField { new IntervalStringArgumentField(argumentDefinition)); // ROD Bindings are set by the RodBindField - } else if (RodBindField.ROD_BIND_FIELD.equals(argumentDefinition.fullName) && argumentDefinition.ioType == ArgumentIOType.INPUT) { + } else if (RodBindArgumentField.ROD_BIND_FIELD.equals(argumentDefinition.fullName) && argumentDefinition.ioType == ArgumentIOType.INPUT) { // TODO: Once everyone is using @Allows and @Requires correctly, we can stop blindly allowing Triplets return Arrays.asList(new RodBindArgumentField(argumentDefinition), new InputIndexesArgumentField(argumentDefinition, Tribble.STANDARD_INDEX_EXTENSION)); //return Collections.emptyList(); @@ -337,6 +337,8 @@ public abstract class ArgumentDefinitionField extends ArgumentField { // Allows the user to specify the track name, track type, and the file. public static class RodBindArgumentField extends ArgumentDefinitionField { + public static final String ROD_BIND_FIELD = "rodBind"; + public RodBindArgumentField(ArgumentDefinition argumentDefinition) { super(argumentDefinition); } @@ -344,7 +346,7 @@ public abstract class ArgumentDefinitionField extends ArgumentField { @Override protected String getFieldType() { return "List[RodBind]"; } @Override protected String getDefaultValue() { return "Nil"; } @Override protected String getCommandLineTemplate() { - return " + repeat(\"\", %3$s, format=RodBind.formatCommandLine(\"%1$s\"))"; + return " + repeat(\"%1$s\", %3$s, formatPrefix=RodBind.formatCommandLineParameter, spaceSeparated=true, escape=true, format=%2$s)"; } } @@ -358,11 +360,11 @@ public abstract class ArgumentDefinitionField extends ArgumentField { @Override protected String getDefaultValue() { return argumentDefinition.isMultiValued ? "Nil" : "_"; } @Override protected String getCommandLineTemplate() { if (argumentDefinition.isMultiValued) { - return " + repeat(\"\", %3$s, format=TaggedFile.formatCommandLine(\"%1$s\"))"; + return " + repeat(\"%1$s\", %3$s, formatPrefix=TaggedFile.formatCommandLineParameter, spaceSeparated=true, escape=true, format=%2$s)"; } else if (!argumentDefinition.required) { - return " + optional(\"\", %3$s, format=TaggedFile.formatCommandLine(\"%1$s\"))"; + return " + optional(TaggedFile.formatCommandLineParameter(\"%1$s\", %3$s), %3$s, spaceSeparated=true, escape=true, format=%2$s)"; } else { - return " + TaggedFile.formatCommandLine(\"%1$s\")(\"\", %3$s, \"\")"; + return " + required(TaggedFile.formatCommandLineParameter(\"%1$s\", %3$s), %3$s, spaceSeparated=true, escape=true, format=%2$s)"; } } } diff --git a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java index 9578eda84..67010c4d5 100644 --- a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java +++ b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java @@ -374,6 +374,9 @@ public class GATKExtensionsGenerator extends CommandLineProgram { if (isGather) importSet.add("import org.broadinstitute.sting.commandline.Gather"); + // Needed for ShellUtils.escapeShellArgument() + importSet.add("import org.broadinstitute.sting.queue.util.ShellUtils"); + // Sort the imports so that the are always in the same order. List sortedImports = new ArrayList(importSet); Collections.sort(sortedImports); diff --git a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/RodBindField.java b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/RodBindField.java deleted file mode 100644 index baf083575..000000000 --- a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/RodBindField.java +++ /dev/null @@ -1,129 +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.queue.extensions.gatk; - -import org.broadinstitute.sting.commandline.Input; -import org.broadinstitute.sting.gatk.WalkerManager; -import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrackBuilder; -import org.broadinstitute.sting.gatk.walkers.RMD; -import org.broadinstitute.sting.gatk.walkers.Walker; - -import java.io.File; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.List; - -/** - * Allows user to specify the rod file but locks in the track name and the track type. - */ -public class RodBindField extends ArgumentField { - public static final String ROD_BIND_FIELD = "rodBind"; - - private final String trackName; - private final String typeName; - private final List relatedFields; - private final boolean isRequired; - - public RodBindField(String trackName, String typeName, List relatedFields, boolean isRequired) { - this.trackName = trackName; - this.typeName = typeName; - this.relatedFields = relatedFields; - this.isRequired = isRequired; - } - - @SuppressWarnings("unchecked") - @Override protected Class getAnnotationIOClass() { return Input.class; } - @Override protected Class getInnerType() { return File.class; } - @Override protected String getFullName() { return escape(getRawFieldName()); } - @Override protected String getFieldType() { return "File"; } - @Override protected String getDefaultValue() { return "_"; } - @Override protected String getRawFieldName() { return this.trackName + this.typeName; } - @Override protected String getDoc() { return escape(this.typeName + " " + this.trackName); } - @Override protected boolean isRequired() { return this.isRequired; } - - @Override public String getCommandLineAddition() { - // TODO: Stop allowing the generic "rodBind" triplets to satisfy the requirement after @Requires are fixed. - return String.format(" + optional(\" -B:%s,%s \", %s)", - /* - return String.format(this.useOption() - ? " + optional(\" -B:%s,%s \", %s)" - : " + \" -B:%s,%s \" + %s", - */ - this.trackName, this.typeName, getFieldName()); - } - - private boolean useOption() { - return !this.isRequired || (relatedFields.size() > 1); - } - - @Override protected String getExclusiveOf() { - StringBuilder exclusiveOf = new StringBuilder(); - // TODO: Stop allowing the generic "rodBind" triplets to satisfy the requirement after @Requires are fixed. - if (this.isRequired) - exclusiveOf.append(ROD_BIND_FIELD); - for (RodBindField relatedField: relatedFields) - if (relatedField != this) { - if (exclusiveOf.length() > 0) - exclusiveOf.append(","); - exclusiveOf.append(relatedField.getFieldName()); - } - return exclusiveOf.toString(); - } -// -// public static List getRodArguments(Class walkerClass, RMDTrackBuilder trackBuilder) { -// List argumentFields = new ArrayList(); -// -// List requires = WalkerManager.getRequiredMetaData(walkerClass); -// List allows = WalkerManager.getAllowsMetaData(walkerClass); -// -// for (RMD required: requires) { -// List fields = new ArrayList(); -// String trackName = required.name(); -// if ("*".equals(trackName)) { -// // TODO: Add the field triplet for name=* after @Allows and @Requires are fixed on walkers -// //fields.add(new RodBindArgumentField(argumentDefinition, true)); -// } else { -// for (String typeName: trackBuilder.getFeatureManager().getTrackRecordTypeNames(required.type())) -// fields.add(new RodBindField(trackName, typeName, fields, true)); -// } -// argumentFields.addAll(fields); -// } -// -// for (RMD allowed: allows) { -// List fields = new ArrayList(); -// String trackName = allowed.name(); -// if ("*".equals(trackName)) { -// // TODO: Add the field triplet for name=* after @Allows and @Requires are fixed on walkers -// //fields.add(new RodBindArgumentField(argumentDefinition, false)); -// } else { -// for (String typeName: trackBuilder.getFeatureManager().getTrackRecordTypeNames(allowed.type())) -// fields.add(new RodBindField(trackName, typeName, fields, true)); -// } -// argumentFields.addAll(fields); -// } -// -// return argumentFields; -// } -} diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala index 036a77b58..8c9063c29 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala @@ -314,7 +314,7 @@ class GATKResourcesBundle extends QScript { class MakeDBSNP129(@Input dbsnp: File, @Input ref: File, @Output dbsnp129: File) extends SelectVariants with UNIVERSAL_GATK_ARGS { this.variant = dbsnp - this.select ++= List("\"dbSNPBuildID <= 129\"") + this.select ++= List("dbSNPBuildID <= 129") this.reference_sequence = ref this.out = dbsnp129 } diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala index da02c8ac5..c06601a2d 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala @@ -248,10 +248,10 @@ class MethodsDevelopmentCallingPipeline extends QScript { this.V = t.rawIndelVCF this.out = t.filteredIndelVCF this.filterName ++= List("IndelQD", "IndelReadPosRankSum", "IndelFS") - this.filterExpression ++= List("\"QD < 2.0\"", "\"ReadPosRankSum < -20.0\"", "\"FS > 200.0\"") + this.filterExpression ++= List("QD < 2.0", "ReadPosRankSum < -20.0", "FS > 200.0") if (t.nSamples >= 10) { this.filterName ++= List("IndelInbreedingCoeff") - this.filterExpression ++= List("\"InbreedingCoeff < -0.8\"") + this.filterExpression ++= List("InbreedingCoeff < -0.8") } this.analysisName = t.name + "_VF" this.jobName = queueLogDir + t.name + ".indelfilter" diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala index 9bddfd97c..2f4aea755 100644 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala @@ -62,7 +62,7 @@ class ExampleUnifiedGenotyper extends QScript { variantFilter.variant = genotyper.out variantFilter.out = swapExt(qscript.bamFile, "bam", "filtered.vcf") variantFilter.filterName = filterNames - variantFilter.filterExpression = filterExpressions.map("\"" + _ + "\"") + variantFilter.filterExpression = filterExpressions evalFiltered.eval :+= variantFilter.out evalFiltered.out = swapExt(variantFilter.out, "vcf", "eval") diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/RodBind.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/RodBind.scala index 9af4d9bcf..deb83bf5a 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/RodBind.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/RodBind.scala @@ -28,21 +28,14 @@ object RodBind { def apply(trackName: String, trackType: String, file: File, tag: String) = new RodBind(trackName, trackType, file, tag) def apply(trackName: String, trackType: String, file: File) = new RodBind(trackName, trackType, file, null) - /** - * Formats the rod binding on the command line. - * Used for optional and repeat. - * @param cmdLineParam command line parameter, ex: -B - * @param prefix unused - * @param value RodBind to add. - * @param suffix unused - * @return The command line addition. - */ - def formatCommandLine(cmdLineParam: String)(prefix: String, value: Any, suffix: String) = { + def formatCommandLineParameter( cmdLineParam: String, value: Any ) = { value match { case rodBind: RodBind if (rodBind.tag != null) => - " %s:%s,%s,%s %s".format(cmdLineParam, rodBind.trackName, rodBind.trackType, rodBind.tag, rodBind.getPath) + "%s:%s,%s,%s".format(cmdLineParam, rodBind.trackName, rodBind.trackType, rodBind.tag) case rodBind: RodBind => - " %s:%s,%s %s".format(cmdLineParam, rodBind.trackName, rodBind.trackType, rodBind.getPath) + "%s:%s,%s".format(cmdLineParam, rodBind.trackName, rodBind.trackType) + case x => + "" } } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/TaggedFile.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/TaggedFile.scala index 295199993..940985f92 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/TaggedFile.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/TaggedFile.scala @@ -2,6 +2,7 @@ package org.broadinstitute.sting.queue.extensions.gatk import java.io.File import org.broadinstitute.sting.utils.io.FileExtension +import org.broadinstitute.sting.queue.util.ShellUtils /** * Used to provide tagged -I input_file arguments to the GATK. @@ -19,21 +20,14 @@ object TaggedFile { def apply(path: String, tag: String) = new TaggedFile(path, tag) def apply(file: File, tag: String) = new TaggedFile(file, tag) - /** - * Formats the rod binding on the command line. - * Used for optional and repeat. - * @param cmdLineParam command line parameter, ex: -I - * @param prefix unused - * @param value TaggedFile to add. - * @param suffix unused - * @return The command line addition. - */ - def formatCommandLine(cmdLineParam: String)(prefix: String, value: Any, suffix: String) = { + def formatCommandLineParameter(cmdLineParam: String, value: Any) = { value match { case taggedFile: TaggedFile if (taggedFile.tag != null) => - " %s:%s %s".format(cmdLineParam, taggedFile.tag, taggedFile.getPath) + "%s:%s".format(cmdLineParam, taggedFile.tag) case file: File => - " %s %s".format(cmdLineParam, file.getPath) + cmdLineParam + case x => + "" } } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/AddOrReplaceReadGroups.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/AddOrReplaceReadGroups.scala index 5456ed02c..93735e4ac 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/AddOrReplaceReadGroups.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/AddOrReplaceReadGroups.scala @@ -55,11 +55,11 @@ class AddOrReplaceReadGroups extends org.broadinstitute.sting.queue.function.Jav override def outputBam = output this.createIndex = Some(true) override def commandLine = super.commandLine + - " RGID=" + RGID + - " RGLB=" + RGLB + - " RGPL=" + RGPL + - " RGPU=" + RGPU + - " RGSM=" + RGSM + - conditionalParameter(RGCN != null && !RGCN.isEmpty, " RGCN=" + RGCN) + - conditionalParameter(RGDS != null && !RGDS.isEmpty, " RGDS=" + RGDS) + required("RGID=" + RGID) + + required("RGLB=" + RGLB) + + required("RGPL=" + RGPL) + + required("RGPU=" + RGPU) + + required("RGSM=" + RGSM) + + conditional(RGCN != null && !RGCN.isEmpty, "RGCN=" + RGCN) + + conditional(RGDS != null && !RGDS.isEmpty, "RGDS=" + RGDS) } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MarkDuplicates.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MarkDuplicates.scala index d44d5e004..d73c556af 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MarkDuplicates.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MarkDuplicates.scala @@ -47,10 +47,8 @@ class MarkDuplicates extends org.broadinstitute.sting.queue.function.JavaCommand this.sortOrder = null this.createIndex = Some(true) override def commandLine = super.commandLine + - " M=" + metrics + - conditionalParameter(REMOVE_DUPLICATES, " REMOVE_DUPLICATES=true") + - conditionalParameter(MAX_FILE_HANDLES_FOR_READ_ENDS_MAP > 0, " MAX_FILE_HANDLES_FOR_READ_ENDS_MAP=" + MAX_FILE_HANDLES_FOR_READ_ENDS_MAP.toString) + - conditionalParameter(SORTING_COLLECTION_SIZE_RATIO > 0, " SORTING_COLLECTION_SIZE_RATIO=" + SORTING_COLLECTION_SIZE_RATIO.toString) - - + required("M=" + metrics) + + conditional(REMOVE_DUPLICATES, "REMOVE_DUPLICATES=true") + + conditional(MAX_FILE_HANDLES_FOR_READ_ENDS_MAP > 0, "MAX_FILE_HANDLES_FOR_READ_ENDS_MAP=" + MAX_FILE_HANDLES_FOR_READ_ENDS_MAP.toString) + + conditional(SORTING_COLLECTION_SIZE_RATIO > 0, "SORTING_COLLECTION_SIZE_RATIO=" + SORTING_COLLECTION_SIZE_RATIO.toString) } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MergeSamFiles.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MergeSamFiles.scala index fd107890e..036932cc6 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MergeSamFiles.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/MergeSamFiles.scala @@ -44,9 +44,7 @@ class MergeSamFiles extends org.broadinstitute.sting.queue.function.JavaCommandL override def outputBam = output this.createIndex = Some(true) override def commandLine = super.commandLine + - conditionalParameter(MERGE_SEQUENCE_DICTIONARIES, " MERGE_SEQUENCE_DICTIONARIES=true") + - conditionalParameter(USE_THREADING, " USE_THREADING=true") + - conditionalParameter(COMMENT != null && !COMMENT.isEmpty, " COMMENT=" + COMMENT) - - + conditional(MERGE_SEQUENCE_DICTIONARIES, "MERGE_SEQUENCE_DICTIONARIES=true") + + conditional(USE_THREADING, "USE_THREADING=true") + + conditional(COMMENT != null && !COMMENT.isEmpty, "COMMENT=" + COMMENT) } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/PicardBamFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/PicardBamFunction.scala index 427c09f82..76856dc36 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/PicardBamFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/PicardBamFunction.scala @@ -48,14 +48,13 @@ trait PicardBamFunction extends JavaCommandLineFunction { protected def outputBam: File abstract override def commandLine = super.commandLine + - Array( - repeat(" INPUT=", inputBams), - " TMP_DIR=" + jobTempDir, - optional(" OUTPUT=", outputBam), - optional(" COMPRESSION_LEVEL=", compressionLevel), - optional(" VALIDATION_STRINGENCY=", validationStringency), - optional(" SO=", sortOrder), - optional(" MAX_RECORDS_IN_RAM=", maxRecordsInRam), - optional(" ASSUME_SORTED=", assumeSorted), - optional(" CREATE_INDEX=", createIndex)).mkString + repeat("INPUT=", inputBams, spaceSeparated=false) + + required("TMP_DIR=" + jobTempDir) + + optional("OUTPUT=", outputBam, spaceSeparated=false) + + optional("COMPRESSION_LEVEL=", compressionLevel, spaceSeparated=false) + + optional("VALIDATION_STRINGENCY=", validationStringency, spaceSeparated=false) + + optional("SO=", sortOrder, spaceSeparated=false) + + optional("MAX_RECORDS_IN_RAM=", maxRecordsInRam, spaceSeparated=false) + + optional("ASSUME_SORTED=", assumeSorted, spaceSeparated=false) + + optional("CREATE_INDEX=", createIndex, spaceSeparated=false) } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ReorderSam.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ReorderSam.scala index 72489dc87..b1968bee5 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ReorderSam.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ReorderSam.scala @@ -42,7 +42,7 @@ class ReorderSam extends org.broadinstitute.sting.queue.function.JavaCommandLine this.createIndex = Some(true) this.sortOrder = null override def commandLine = super.commandLine + - " REFERENCE=" + sortReference + - optional(" ALLOW_INCOMPLETE_DICT_CONCORDANCE=", ALLOW_INCOMPLETE_DICT_CONCORDANCE) - optional(" ALLOW_CONTIG_LENGTH_DISCORDANCE=", ALLOW_CONTIG_LENGTH_DISCORDANCE) + required("REFERENCE=" + sortReference) + + optional("ALLOW_INCOMPLETE_DICT_CONCORDANCE=", ALLOW_INCOMPLETE_DICT_CONCORDANCE, spaceSeparated=false) + optional("ALLOW_CONTIG_LENGTH_DISCORDANCE=", ALLOW_CONTIG_LENGTH_DISCORDANCE, spaceSeparated=false) } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/RevertSam.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/RevertSam.scala index 746ce609e..60d8bfaf8 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/RevertSam.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/RevertSam.scala @@ -52,10 +52,10 @@ class RevertSam extends org.broadinstitute.sting.queue.function.JavaCommandLineF override def outputBam = output this.createIndex = Some(true) override def commandLine = super.commandLine + - conditionalParameter(!restoreOriginalQualities, " RESTORE_ORIGINAL_QUALITIES=false") + - conditionalParameter(!removeDuplicateInformation, " REMOVE_DUPLICATE_INFORMATION=false") + - conditionalParameter(!removeAlignmentInformation, " REMOVE_ALIGNMENT_INFORMATION=false") + - conditionalParameter(!attributesToClear.isEmpty, repeat(" ATTRIBUTE_TO_CLEAR=", attributesToClear)) + - conditionalParameter(sampleAlias != null, " SAMPLE_ALIAS=" + sampleAlias) + - conditionalParameter(libraryName != null, " LIBRARY_NAME=" + libraryName) + conditional(!restoreOriginalQualities, "RESTORE_ORIGINAL_QUALITIES=false") + + conditional(!removeDuplicateInformation, "REMOVE_DUPLICATE_INFORMATION=false") + + conditional(!removeAlignmentInformation, "REMOVE_ALIGNMENT_INFORMATION=false") + + repeat("ATTRIBUTE_TO_CLEAR=", attributesToClear, spaceSeparated=false) + // repeat() returns "" for null/empty list + conditional(sampleAlias != null, "SAMPLE_ALIAS=" + sampleAlias) + + conditional(libraryName != null, "LIBRARY_NAME=" + libraryName) } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/SamToFastq.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/SamToFastq.scala index 3a4217e60..3eb4e8e06 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/SamToFastq.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/SamToFastq.scala @@ -61,17 +61,17 @@ class SamToFastq extends org.broadinstitute.sting.queue.function.JavaCommandLine this.sortOrder = null override def commandLine = super.commandLine + - " FASTQ=" + fastq + - optional(" SECOND_END_FASTQ=", secondEndFastQ) + - conditionalParameter(outputPerReadGroup, optional(" OUTPUT_PER_RG=", outputPerReadGroup)) + - optional(" OUTPUT_DIR=", outputDir) + - conditionalParameter(!reReverse, optional(" RE_REVERSE=", reReverse)) + - conditionalParameter(includeNonPFReads, optional(" INCLUDE_NON_PF_READS=", includeNonPFReads)) + - optional(" CLIPPING_ATTRIBUTE=", clippingAttribute) + - optional(" CLIPPING_ACTION=", clippingAction) + - conditionalParameter (readOneTrim >= 0, optional(" READ1_TRIM=", readOneTrim)) + - conditionalParameter (readOneMaxBasesToWrite >= 0, optional(" READ1_MAX_BASES_TO_WRITE=", readOneMaxBasesToWrite)) + - conditionalParameter (readTwoTrim >= 0, optional(" READ2_TRIM=", readTwoTrim)) + - conditionalParameter (readTwoMaxBasesToWrite >=0, optional(" READ2_MAX_BASES_TO_WRITE=", readTwoMaxBasesToWrite)) + - conditionalParameter (includeNonPrimaryAlignments, optional(" INCLUDE_NON_PRIMARY_ALIGNMENTS=", includeNonPrimaryAlignments)) + required("FASTQ=" + fastq) + + optional("SECOND_END_FASTQ=", secondEndFastQ, spaceSeparated=false) + + conditional(outputPerReadGroup, "OUTPUT_PER_RG=" + outputPerReadGroup) + + optional("OUTPUT_DIR=", outputDir, spaceSeparated=false) + + conditional(!reReverse, "RE_REVERSE=" + reReverse) + + conditional(includeNonPFReads, "INCLUDE_NON_PF_READS=" + includeNonPFReads) + + optional("CLIPPING_ATTRIBUTE=", clippingAttribute, spaceSeparated=false) + + optional("CLIPPING_ACTION=", clippingAction, spaceSeparated=false) + + conditional(readOneTrim >= 0, "READ1_TRIM=" + readOneTrim) + + conditional(readOneMaxBasesToWrite >= 0, "READ1_MAX_BASES_TO_WRITE=" + readOneMaxBasesToWrite) + + conditional(readTwoTrim >= 0, "READ2_TRIM=" + readTwoTrim) + + conditional(readTwoMaxBasesToWrite >= 0, "READ2_MAX_BASES_TO_WRITE=" + readTwoMaxBasesToWrite) + + conditional(includeNonPrimaryAlignments, "INCLUDE_NON_PRIMARY_ALIGNMENTS=" + includeNonPrimaryAlignments) } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ValidateSamFile.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ValidateSamFile.scala index 2c8fbc6d9..030e4b07d 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ValidateSamFile.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/ValidateSamFile.scala @@ -50,11 +50,11 @@ class ValidateSamFile extends org.broadinstitute.sting.queue.function.JavaComman override def inputBams = input override def outputBam = output override def commandLine = super.commandLine + - " MODE=" + MODE + - " MAX_OUTPUT=" + MAX_OUTPUT + - " MAX_OPEN_TEMP_FILES=" + MAX_OPEN_TEMP_FILES + - conditionalParameter(!VALIDATE_INDEX, " VALIDATE_INDEX=false") + - conditionalParameter(IGNORE_WARNINGS, " IGNORE_WARNINGS=true") + - conditionalParameter(IS_BISULFITE_SEQUENCED, " IS_BISULFITE_SEQUENCED=true") + - conditionalParameter(IGNORE != null && !IGNORE.isEmpty, repeat(" IGNORE=", IGNORE)) + required("MODE=" + MODE) + + required("MAX_OUTPUT=" + MAX_OUTPUT) + + required("MAX_OPEN_TEMP_FILES=" + MAX_OPEN_TEMP_FILES) + + conditional(!VALIDATE_INDEX, "VALIDATE_INDEX=false") + + conditional(IGNORE_WARNINGS, "IGNORE_WARNINGS=true") + + conditional(IS_BISULFITE_SEQUENCED, "IS_BISULFITE_SEQUENCED=true") + + repeat("IGNORE=", IGNORE, spaceSeparated=false) // repeat() returns "" for null/empty list } \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsIndexFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsIndexFunction.scala index 801a152ec..83a03b904 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsIndexFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsIndexFunction.scala @@ -48,7 +48,10 @@ class SamtoolsIndexFunction extends SamtoolsCommandLineFunction { bamFileIndex = new File(bamFile.getPath + ".bai") } - def commandLine = "%s index %s %s".format(samtools, bamFile, bamFileIndex) + def commandLine = required(samtools) + + required("index") + + required(bamFile) + + required(bamFileIndex) override def dotString = "Index: %s".format(bamFile.getName) } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsMergeFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsMergeFunction.scala index 2b864def6..aff9a25c0 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsMergeFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/samtools/SamtoolsMergeFunction.scala @@ -55,7 +55,9 @@ class SamtoolsMergeFunction extends SamtoolsCommandLineFunction { )) } - def commandLine = "%s merge%s %s%s".format( - samtools, optional(" -R ", region), - outputBam, repeat(" ", inputBams)) + def commandLine = required(samtools) + + required("merge") + + optional("-R", region) + + required(outputBam) + + repeat(inputBams) } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/snpeff/SnpEff.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/snpeff/SnpEff.scala index 62f66ec06..259856c17 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/snpeff/SnpEff.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/snpeff/SnpEff.scala @@ -50,11 +50,14 @@ class SnpEff extends JavaCommandLineFunction { @Output(doc="snp eff output") var outVcf: File = _ - override def commandLine = Array( - super.commandLine, - " eff", - if (verbose) " -v" else "", - optional(" -c ", config), - " -i vcf -o vcf %s %s > %s".format(genomeVersion, inVcf, outVcf) - ).mkString + override def commandLine = super.commandLine + + required("eff") + + conditional(verbose, "-v") + + optional("-c", config) + + required("-i", "vcf") + + required("-o", "vcf") + + required(genomeVersion) + + required(inVcf) + + required(">", escape=false) + + required(outVcf) } diff --git a/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala index ff77503ac..b08832f22 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala @@ -110,61 +110,178 @@ trait CommandLineFunction extends QFunction with Logging { } /** - * Repeats parameters with a prefix/suffix if they are set otherwise returns "". - * Skips null, Nil, None. Unwraps Some(x) to x. Everything else is called with x.toString. - * @param prefix Command line prefix per parameter. - * @param params Traversable parameters. - * @param suffix Optional suffix per parameter. - * @param separator Optional separator per parameter. - * @param format Format function if the value has a value - * @return The generated string + * Safely construct a full required command-line argument with consistent quoting, whitespace separation, etc. + * + * @param prefix Prefix to insert before the argument value (eg., "-f") + * @param param The argument value itself + * @param suffix Suffix to append after the argument value + * @param spaceSeparated If true, insert a space between the prefix, param, and suffix + * @param escape If true, quote the generated argument to avoid interpretation by the shell + * @param format Format String used to convert param to a String + * @return The combined and formatted argument, surrounded by whitespace */ - protected def repeat(prefix: String, params: Traversable[_], suffix: String = "", separator: String = "", - format: (String, Any, String) => String = formatValue("%s")) = - if (params == null) - "" - else - params.filter(param => hasValue(param)).map(param => format(prefix, param, suffix)).mkString(separator) + protected def required( prefix: String, param: Any, suffix: String = "", spaceSeparated: Boolean = true, + escape: Boolean = true, format: String = "%s" ): String = { + " %s ".format(formatArgument(prefix, param, suffix, spaceSeparated, escape, format)) + } /** - * Returns parameter with a prefix/suffix if it is set otherwise returns "". - * Does not output null, Nil, None. Unwraps Some(x) to x. Everything else is called with x.toString. - * @param prefix Command line prefix per parameter. - * @param param Parameter to check for a value. - * @param suffix Optional suffix per parameter. - * @param format Format function if the value has a value - * @return The generated string + * Safely construct a one-token required command-line argument with quoting + * + * @param param The command-line argument value + * @return The argument value quoted and surrounded by whitespace */ - protected def optional(prefix: String, param: Any, suffix: String = "", - format: (String, Any, String) => String = formatValue("%s")) = - if (hasValue(param)) format(prefix, param, suffix) else "" + protected def required( param: Any ): String = { + required("", param) + } /** - * Returns "" if the value is null or an empty collection, otherwise return the value.toString. - * @param format Format string if the value has a value - * @param prefix Command line prefix per parameter. - * @param param Parameter to check for a value. - * @param suffix Optional suffix per parameter. - * @return "" if the value is null, or "" if the collection is empty, otherwise the value.toString. + * Safely construct a one-token required command-line argument, and specify whether you want quoting + * + * @param param The command-line argument value + * @param escape If true, quote the generated argument to avoid interpretation by the shell + * @return The argument value, quoted if quoting was requested, and surrounded by whitespace */ - protected def formatValue(format: String)(prefix: String, param: Any, suffix: String): String = - if (CollectionUtils.isNullOrEmpty(param)) - "" - else - prefix + (param match { - case Some(x) => format.format(x) - case x => format.format(x) - }) + suffix + protected def required( param: Any, escape: Boolean ): String = { + required("", param, escape=escape) + } /** - * Returns the parameter if the condition is true. Useful for long string of parameters - * @param condition the condition to validate - * @param param the string to be returned in case condition is true - * @return param if condition is true, "" otherwise + * Safely construct a full optional command-line argument with consistent quoting, whitespace separation, etc. + * If the argument has no value, returns an empty String. + * + * @param prefix Prefix to insert before the argument value (eg., "-f") + * @param param The argument value itself (if null/empty, the method returns empty String) + * @param suffix Suffix to append after the argument value + * @param spaceSeparated If true, insert a space between the prefix, param, and suffix + * @param escape If true, quote the generated argument to avoid interpretation by the shell + * @param format Format String used to convert param to a String + * @return The combined and formatted argument, surrounded by whitespace, or an empty String + * if the argument has no value */ - protected def conditionalParameter(condition: Boolean, param: String): String = - if (condition == true) - param - else + protected def optional( prefix: String, param: Any, suffix: String = "", spaceSeparated: Boolean = true, + escape: Boolean = true, format: String = "%s" ): String = { + if ( hasValue(param) ) " %s ".format(formatArgument(prefix, param, suffix, spaceSeparated, escape, format)) else "" + } + + /** + * Safely construct a one-token optional command-line argument with quoting. + * If the argument has no value, returns an empty String. + * + * @param param The command-line argument value + * @return The argument value quoted and surrounded by whitespace, or an empty String + * if the argument has no value + */ + protected def optional( param: Any ): String = { + optional("", param) + } + + /** + * Safely construct a one-token conditional command-line argument. If the provided condition + * is false, an empty String is returned. + * + * @param condition The condition to check + * @param param The command-line argument value + * @param escape If true, quote the generated argument to avoid interpretation by the shell + * @param format Format String used to convert param to a String + * @return The command-line argument value, quoted if quoting was requested and surrounded + * by whitespace, or an empty String if the argument has no value. + */ + protected def conditional( condition: Boolean, param: Any, escape: Boolean = true, format: String = "%s" ): String = { + if ( condition ) { + " %s ".format(formatArgument("", param, "", false, escape, format)) + } + else { "" + } + } + + /** + * Safely construct a series of full command-line arguments with consistent quoting, whitespace separation, etc. + * + * Each argument value is preceded by a prefix/suffix if they are set. A function can be provided to vary + * each prefix for each argument value (eg., -f:tag1 file1 -f:tag2 file2) -- the default is to use + * the same prefix for all arguments. + * + * @param prefix Prefix to insert before each argument value (eg., "-f") + * @param params The collection of argument values + * @param suffix Suffix to append after each argument value + * @param separator Specifies how to separate the various arguments from each other + * (eg., what should go between '-f' 'file1' and '-f' 'file2'?) + * Default is one space character. + * @param spaceSeparated If true, insert a space between each individual prefix, param, and suffix + * @param escape If true, quote the generated argument to avoid interpretation by the shell + * @param format Format String used to convert each individual param within params to a String + * @param formatPrefix Function mapping (prefix, argumentValue) pairs to prefixes. Can be used to + * vary each prefix depending on the argument value (useful for tags, etc.). + * Default is to use the same prefix for all argument values. + * @return The series of command-line arguments, quoted and whitespace-delimited as requested, + * or an empty String if params was null/Nil/None. + */ + protected def repeat(prefix: String, params: Traversable[_], suffix: String = "", separator: String = " ", + spaceSeparated: Boolean = true, escape: Boolean = true, format: String = "%s", + formatPrefix: (String, Any) => String = (prefix, value) => prefix): String = { + if (CollectionUtils.isNullOrEmpty(params)) + "" + else + " %s ".format(params.filter(param => hasValue(param)).map(param => formatArgument(formatPrefix(prefix, param), param, suffix, spaceSeparated, escape, format)).mkString(separator)) + } + + /** + * Safely construct a series of one-token command-line arguments with quoting and space separation. + * + * @param params The collection of argument values + * @return The argument values quoted and space-delimited, or an empty String if params was null/Nil/None + */ + protected def repeat( params: Traversable[_] ): String = { + repeat("", params) + } + + /** + * Given an (optional) prefix, an argument value, and an (optional) suffix, formats a command-line + * argument with the specified level of quoting and space-separation. + * + * Helper method for required(), optional(), conditional(), and repeat() -- do not use this + * method directly! + * + * @param prefix Prefix to insert before the argument value (eg., "-f"). Ignored if empty/null. + * @param param The argument value itself. If this is Some(x), it is unwrapped to x before processing. + * @param suffix Suffix to append after the argument value. Ignored if empty/null. + * @param spaceSeparated If true, insert a space between the prefix, param, and suffix + * @param escape If true, quote the generated argument to avoid interpretation by the shell + * @param paramFormat Format string used to convert param to a String + * @return The combined and formatted argument, NOT surrounded by any whitespace. + * Returns an empty String if param was null/empty. + */ + protected def formatArgument( prefix: String, param: Any, suffix: String, spaceSeparated: Boolean, escape: Boolean, + paramFormat: String ): String = { + if (CollectionUtils.isNullOrEmpty(param)) { + return "" + } + + // Trim leading and trailing whitespace off our three tokens, and unwrap Some(x) to x for the param + val trimmedValues : List[String] = List((if ( prefix != null ) prefix.trim else ""), + (param match { + case Some(x) => paramFormat.format(x).trim + case x => paramFormat.format(x).trim + }), + (if ( suffix != null ) suffix.trim else "")) + var joinedArgument : String = null + + // If the user requested space-separation, join the tokens with a space, and escape individual + // NON-EMPTY tokens if escaping was requested (eg., ("-f", "foo", "") -> "'-f' 'foo'") + if ( spaceSeparated ) { + joinedArgument = trimmedValues.map(x => if ( x.length > 0 && escape ) ShellUtils.escapeShellArgument(x) else x).mkString(" ").trim() + } + + // Otherwise join the tokens without any intervening whitespace, and if quoting was requested + // quote the entire concatenated value (eg., ("-Xmx", "4", "G") -> "'-Xmx4G'") + else { + joinedArgument = if ( escape ) ShellUtils.escapeShellArgument(trimmedValues.mkString("")) else trimmedValues.mkString("") + } + + // If the user requested escaping and we ended up with an empty String after joining, quote the empty + // String to preserve the command line token. Otherwise just return the joined argument + if ( joinedArgument.length == 0 && escape ) ShellUtils.escapeShellArgument(joinedArgument) else joinedArgument + } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala index 4a50a72ac..8cbc2c577 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala @@ -49,18 +49,6 @@ trait JavaCommandLineFunction extends CommandLineFunction { */ var javaMemoryLimit: Option[Double] = None - /** - * Returns the java executable to run. - */ - def javaExecutable: String = { - if (jarFile != null) - "-jar " + jarFile - else if (javaMainClass != null) - "-cp \"%s\" %s".format(javaClasspath.mkString(File.pathSeparator), javaMainClass) - else - null - } - override def freezeFieldValues() { super.freezeFieldValues() @@ -71,11 +59,25 @@ trait JavaCommandLineFunction extends CommandLineFunction { javaClasspath = JavaCommandLineFunction.currentClasspath } - def javaOpts = "%s -Djava.io.tmpdir=%s" - .format(optional(" -Xmx", javaMemoryLimit.map(gb => (gb * 1024).ceil.toInt), "m"), jobTempDir) + /** + * Returns the java executable to run. + */ + def javaExecutable: String = { + if (jarFile != null) + required("-jar", jarFile) + else if (javaMainClass != null) + required("-cp", javaClasspath.mkString(File.pathSeparator)) + + required(javaMainClass) + else + null + } - def commandLine = "java%s %s" - .format(javaOpts, javaExecutable) + def javaOpts = required("-Xmx", javaMemoryLimit.map(gb => (gb * 1024).ceil.toInt), "m", spaceSeparated=false) + + required("-Djava.io.tmpdir=", jobTempDir, spaceSeparated=false) + + def commandLine = required("java") + + javaOpts + + javaExecutable } object JavaCommandLineFunction { diff --git a/public/scala/src/org/broadinstitute/sting/queue/util/ShellUtils.scala b/public/scala/src/org/broadinstitute/sting/queue/util/ShellUtils.scala new file mode 100644 index 000000000..3cb1a705a --- /dev/null +++ b/public/scala/src/org/broadinstitute/sting/queue/util/ShellUtils.scala @@ -0,0 +1,36 @@ +package org.broadinstitute.sting.queue.util + +import java.lang.IllegalArgumentException + +object ShellUtils { + + /** + * Escapes the String it's passed so that it will be interpreted literally when + * parsed by sh/bash. Can correctly escape all characters except \0, \r, and \n + * + * Replaces all instances of ' with '\'', and then surrounds the resulting String + * with single quotes. + * + * Examples: + * ab -> 'ab' + * a'b -> 'a'\''b' + * '' -> ''\'''\''' + * + * Since \' is not supported inside single quotes in the shell (ie., '\'' does not work), + * whenever we encounter a single quote we need to terminate the existing single-quoted + * string, place the \' outside of single quotes, and then start a new single-quoted + * string. As long as we don't insert spaces between the separate strings, the shell will + * concatenate them together into a single argument value for us. + * + * @param str the String to escape + * @return the same String quoted so that it will be interpreted literally when + * parsed by sh/bash + */ + def escapeShellArgument ( str : String ) : String = { + if ( str == null ) { + throw new IllegalArgumentException("escapeShellArgument() was passed a null String") + } + + "'" + str.replaceAll("'", "'\\\\''") + "'" + } +} \ No newline at end of file diff --git a/public/scala/test/org/broadinstitute/sting/queue/function/CommandLineFunctionUnitTest.scala b/public/scala/test/org/broadinstitute/sting/queue/function/CommandLineFunctionUnitTest.scala new file mode 100644 index 000000000..eb50c3a2e --- /dev/null +++ b/public/scala/test/org/broadinstitute/sting/queue/function/CommandLineFunctionUnitTest.scala @@ -0,0 +1,171 @@ +package org.broadinstitute.sting.queue.function + +import org.testng.Assert +import org.testng.annotations.{DataProvider, Test} + +// Since "protected" in Scala is subclass-only and doesn't allow package-level access, we need to +// extend a class if we want to test it +class CommandLineFunctionUnitTest extends CommandLineFunction { + def commandLine = "" + + @DataProvider( name="formatArgumentTestData" ) + def formatArgumentDataProvider = { + Array(Array("", "argvalue", "", true, true, "'argvalue'"), + Array("", "argvalue", "", true, false, "argvalue"), + Array("", "argvalue", "", false, true, "'argvalue'"), + Array("", "argvalue", "", false, false, "argvalue"), + Array("-arg", "argvalue", "", true, true, "'-arg' 'argvalue'"), + Array("-arg", "argvalue", "", true, false, "-arg argvalue"), + Array("ARGNAME=", "ARGVALUE", "", false, true, "'ARGNAME=ARGVALUE'"), + Array("ARGNAME=", "ARGVALUE", "", false, false, "ARGNAME=ARGVALUE"), + Array("-Xmx", "4", "G", true, true, "'-Xmx' '4' 'G'"), + Array("-Xmx", "4", "G", true, false, "-Xmx 4 G"), + Array("-Xmx", "4", "G", false, true, "'-Xmx4G'"), + Array("-Xmx", "4", "G", false, false, "-Xmx4G"), + Array("", "", "", true, true, "''"), + Array("", "", "", true, false, ""), + Array("", "", "", false, true, "''"), + Array("", "", "", false, false, ""), + Array("", null, "", true, true, ""), + Array("", Nil, "", true, true, ""), + Array("", None, "", true, true, ""), + Array(null, null, null, true, true, ""), + Array("", Some("argvalue"), "", true, true, "'argvalue'") + ) + } + + @Test( dataProvider="formatArgumentTestData" ) + def testFormatArgument( prefix: String, param: Any, suffix: String, spaceSeparated: Boolean, escape: Boolean, expectedReturnValue: String ) { + Assert.assertEquals(formatArgument(prefix, param, suffix, spaceSeparated, escape, "%s"), + expectedReturnValue) + } + + @Test + def testFormatArgumentCustomFormatString() { + Assert.assertEquals(formatArgument("", "argvalue", "", true, true, "%.3s"), "'arg'") + } + + @DataProvider( name = "requiredTestData" ) + def requiredDataProvider = { + Array(Array("", "argvalue", "", true, true, " 'argvalue' "), + Array("", "argvalue", "", true, false, " argvalue "), + Array("", "argvalue", "", false, true, " 'argvalue' "), + Array("", "argvalue", "", false, false, " argvalue "), + Array("-arg", "argvalue", "", true, true, " '-arg' 'argvalue' "), + Array("-arg", "argvalue", "", true, false, " -arg argvalue "), + Array("ARGNAME=", "ARGVALUE", "", false, true, " 'ARGNAME=ARGVALUE' "), + Array("ARGNAME=", "ARGVALUE", "", false, false, " ARGNAME=ARGVALUE "), + Array("-Xmx", "4", "G", true, true, " '-Xmx' '4' 'G' "), + Array("-Xmx", "4", "G", true, false, " -Xmx 4 G "), + Array("-Xmx", "4", "G", false, true, " '-Xmx4G' "), + Array("-Xmx", "4", "G", false, false, " -Xmx4G "), + Array("", "", "", true, true, " '' "), + Array("", "", "", true, false, " "), + Array("", "", "", false, true, " '' "), + Array("", "", "", false, false, " "), + Array("", null, "", true, true, " "), + Array("", Nil, "", true, true, " "), + Array("", None, "", true, true, " ") + ) + } + + @Test( dataProvider="requiredTestData" ) + def testRequired( prefix: String, param: Any, suffix: String, spaceSeparated: Boolean, escape: Boolean, expectedReturnValue: String ) { + Assert.assertEquals(required(prefix, param, suffix, spaceSeparated, escape), + expectedReturnValue) + } + + @DataProvider( name = "optionalTestData" ) + def optionalDataProvider = { + Array(Array("-arg", "argvalue", "", true, true, " '-arg' 'argvalue' "), + Array("-arg", null, "", true, true, ""), + Array("-arg", Nil, "", true, true, ""), + Array("-arg", None, "", true, true, ""), + Array("-arg", "", "", true, true, " '-arg' ") + ) + } + + @Test( dataProvider="optionalTestData" ) + def testOptional( prefix: String, param: Any, suffix: String, spaceSeparated: Boolean, escape: Boolean, expectedReturnValue: String ) { + Assert.assertEquals(optional(prefix, param, suffix, spaceSeparated, escape), + expectedReturnValue) + } + + @DataProvider( name = "conditionalTestData" ) + def conditionalDataProvider = { + Array(Array(true, "-FLAG", true, " '-FLAG' "), + Array(true, "-FLAG", false, " -FLAG "), + Array(false, "-FLAG", true, ""), + Array(false, "-FLAG", false, ""), + Array(true, null, true, " "), + Array(true, Nil, true, " "), + Array(true, None, true, " "), + Array(false, null, true, ""), + Array(false, Nil, true, ""), + Array(false, None, true, "") + ) + } + + @Test( dataProvider="conditionalTestData" ) + def testConditional( condition: Boolean, param: Any, escape: Boolean, expectedReturnValue: String ) { + Assert.assertEquals(conditional(condition, param, escape), + expectedReturnValue) + } + + @DataProvider( name = "repeatTestData" ) + def repeatDataProvider = { + Array(Array("", List("a", "bc", "d"), "", " ", true, true, " 'a' 'bc' 'd' "), + Array("", List("a", "bc", "d"), "", " ", true, false, " a bc d "), + Array("", List("a", "bc", "d"), "", "", true, true, " 'a''bc''d' "), + Array("", List("a", "bc", "d"), "", "", true, false, " abcd "), + Array("-f", List("file1", "file2", "file3"), "", " ", true, true, " '-f' 'file1' '-f' 'file2' '-f' 'file3' "), + Array("-f", List("file1", "file2", "file3"), "", " ", true, false, " -f file1 -f file2 -f file3 "), + Array("-f", List("file1", "file2", "file3"), "", " ", false, true, " '-ffile1' '-ffile2' '-ffile3' "), + Array("-f", List("file1", "file2", "file3"), "", " ", false, false, " -ffile1 -ffile2 -ffile3 "), + Array("-f", List("file1", "file2", "file3"), "", "", false, true, " '-ffile1''-ffile2''-ffile3' "), + Array("-f", List("file1", "file2", "file3"), "", "", false, false, " -ffile1-ffile2-ffile3 "), + Array("-f", List("file1", "file2", "file3"), "suffix", " ", true, true, " '-f' 'file1' 'suffix' '-f' 'file2' 'suffix' '-f' 'file3' 'suffix' "), + Array("-f", List("file1", "file2", "file3"), "suffix", " ", true, false, " -f file1 suffix -f file2 suffix -f file3 suffix "), + Array("-f", List("file1", "file2", "file3"), "suffix", " ", false, true, " '-ffile1suffix' '-ffile2suffix' '-ffile3suffix' "), + Array("-f", List("file1", "file2", "file3"), "suffix", " ", false, false, " -ffile1suffix -ffile2suffix -ffile3suffix "), + Array("-f", null, "", " ", true, true, ""), + Array("-f", Nil, "", " ", true, true, "") + ) + } + + @Test( dataProvider="repeatTestData" ) + def testRepeat( prefix: String, params: Traversable[_], suffix: String, separator: String, + spaceSeparated: Boolean, escape: Boolean, expectedReturnValue: String ) { + Assert.assertEquals(repeat(prefix, params, suffix, separator, spaceSeparated, escape), + expectedReturnValue) + } + + // Need to test None separately due to implicit conversion issues when using None in a TestNG data provider + @Test + def testRepeatNone() { + testRepeat("", None, "", " ", true, true, "") + } + + @DataProvider( name = "repeatWithPrefixFormattingTestData" ) + def repeatWithPrefixFormattingDataProvider = { + Array(Array("-f", List("file1", "file2", "file3"), "", " ", true, true, (prefix: String, value: Any) => "%s:tag%s".format(prefix, value), + " '-f:tagfile1' 'file1' '-f:tagfile2' 'file2' '-f:tagfile3' 'file3' "), + Array("-f", List("file1", "file2", "file3"), "", " ", true, false, (prefix: String, value: Any) => "%s:tag%s".format(prefix, value), + " -f:tagfile1 file1 -f:tagfile2 file2 -f:tagfile3 file3 "), + Array("", List("file1", "file2", "file3"), "", " ", true, true, (prefix: String, value: Any) => "-%s".format(value), + " '-file1' 'file1' '-file2' 'file2' '-file3' 'file3' "), + Array("-f", null, "", " ", true, true, (prefix: String, value: Any) => "%s:tag%s".format(prefix, value), + ""), + Array("-f", Nil, "", " ", true, true, (prefix: String, value: Any) => "%s:tag%s".format(prefix, value), + "") + ) + } + + @Test( dataProvider = "repeatWithPrefixFormattingTestData" ) + def testRepeatWithPrefixFormatting( prefix: String, params: Traversable[_], suffix: String, separator: String, + spaceSeparated: Boolean, escape: Boolean, formatPrefix: (String, Any) => String, + expectedReturnValue: String ) { + Assert.assertEquals(repeat(prefix, params, suffix, separator, spaceSeparated, escape, "%s", formatPrefix), + expectedReturnValue) + } +} \ No newline at end of file diff --git a/public/scala/test/org/broadinstitute/sting/queue/util/ShellUtilsUnitTest.scala b/public/scala/test/org/broadinstitute/sting/queue/util/ShellUtilsUnitTest.scala new file mode 100644 index 000000000..76585d207 --- /dev/null +++ b/public/scala/test/org/broadinstitute/sting/queue/util/ShellUtilsUnitTest.scala @@ -0,0 +1,57 @@ +package org.broadinstitute.sting.queue.util + +import org.testng.annotations.Test +import org.testng.Assert +import java.io.{InputStreamReader, BufferedReader} + +class ShellUtilsUnitTest { + + @Test + def testEscapeShellArgumentOneCharSequences() { + // Test all ASCII characters except \0, \n, and \r, which we do not support escaping + for ( asciiCode <- 1 to 127 if asciiCode != 10 && asciiCode != 13 ) { + val originalString: String = "%c".format(asciiCode.toChar) + val quotedString: String = ShellUtils.escapeShellArgument(originalString) + + val child : Process = new ProcessBuilder("/bin/sh", "-c", "printf \"%s\" " + quotedString).start() + val childReader : BufferedReader = new BufferedReader(new InputStreamReader(child.getInputStream)) + val childOutputBuffer : StringBuilder = new StringBuilder + + val childReaderThread : Thread = new Thread(new Runnable() { + def run() { + var line : String = childReader.readLine() + + while ( line != null ) { + childOutputBuffer.append(line) + line = childReader.readLine() + } + } + }) + childReaderThread.start() + + val childReturnValue = child.waitFor() + childReaderThread.join() + + childReader.close() + val childOutput = childOutputBuffer.toString() + + if ( childReturnValue != 0 ) { + Assert.fail("With character ASCII %d, sh child process returned: %d".format(asciiCode, childReturnValue)) + } + else if ( ! originalString.equals(childOutput) ) { + Assert.fail("With character ASCII %d, sh child process output \"%s\" instead of the expected \"%s\"".format( + asciiCode, childOutput, originalString)) + } + } + } + + @Test(expectedExceptions = Array(classOf[IllegalArgumentException])) + def testEscapeShellArgumentNullString() { + ShellUtils.escapeShellArgument(null) + } + + @Test + def testEscapeShellArgumentEmptyString() { + Assert.assertEquals(ShellUtils.escapeShellArgument(""), "''") + } +} \ No newline at end of file From 71f793b71bdead2353e911d8e55640936f6da9f1 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 2 Dec 2011 14:13:14 -0500 Subject: [PATCH 211/380] First partially working version of the multi-allelic version of the Exact AF calculation --- .../genotyper/ExactAFCalculationModel.java | 256 +++++++++++++++++- .../genotyper/UnifiedArgumentCollection.java | 5 + 2 files changed, 257 insertions(+), 4 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 5d0b6f0a7..ae7c2f5c1 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 @@ -28,6 +28,7 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.*; @@ -44,8 +45,12 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { 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); + private final boolean USE_MULTI_ALLELIC_CALCULATION; + + protected ExactAFCalculationModel(UnifiedArgumentCollection UAC, int N, Logger logger, PrintStream verboseWriter) { super(UAC, N, logger, verboseWriter); + USE_MULTI_ALLELIC_CALCULATION = UAC.MULTI_ALLELIC; } public void getLog10PNonRef(GenotypesContext GLs, List alleles, @@ -60,9 +65,9 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { for (int k=1; k < numAlleles; k++) { // 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. + // compute indices for AA,AB,BB for current allele - genotype log10Likelihoods are a linear vector that can be thought of + // as a row-wise upper triangular matrix of log10Likelihoods. + // So, for example, with 2 alt alleles, log10Likelihoods have AA,AB,AC,BB,BC,CC. // 3 alt alleles: AA,AB,AC,AD BB BC BD CC CD DD final int idxAA = 0; @@ -74,7 +79,9 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final int idxBB = idxDiag; idxDiag += incr--; - final int lastK = linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, idxAA, idxAB, idxBB); + final int lastK = USE_MULTI_ALLELIC_CALCULATION ? + linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, false) : + linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, idxAA, idxAB, idxBB); if (numAlleles > 2) { posteriorCache[k-1] = log10AlleleFrequencyPosteriors.clone(); @@ -221,6 +228,16 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return lastK; } + final static double approximateLog10SumLog10(double[] vals) { + if ( vals.length < 2 ) + throw new ReviewedStingException("Passing array with fewer than 2 values when computing approximateLog10SumLog10"); + + double approx = approximateLog10SumLog10(vals[0], vals[1]); + for ( int i = 2; i < vals.length; i++ ) + approx = approximateLog10SumLog10(approx, vals[i]); + return approx; + } + final static double approximateLog10SumLog10(double a, double b, double c) { //return softMax(new double[]{a, b, c}); return approximateLog10SumLog10(approximateLog10SumLog10(a, b), c); @@ -256,6 +273,237 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } + // ------------------------------------------------------------------------------------- + // + // Multi-allelic implementation. + // + // ------------------------------------------------------------------------------------- + + private static final int HOM_REF_INDEX = 0; // AA likelihoods are always first + private static final int AC_ZERO_INDEX = 0; // ExactACset index for k=0 over all k + + // This class represents a column in the Exact AC calculation matrix + private static final class ExactACset { + final int[] ACcounts; + final double[] log10Likelihoods; + final HashMap ACsetIndexToPLIndex = new HashMap(); + final ArrayList dependentACsetsToDelete = new ArrayList(); + + private int index = -1; + + public ExactACset(int size, int[] ACcounts) { + this.ACcounts = ACcounts; + log10Likelihoods = new double[size]; + } + + public int getIndex() { + if ( index == -1 ) + index = generateIndex(ACcounts, log10Likelihoods.length); + return index; + } + + public static int generateIndex(int[] ACcounts, int multiplier) { + int index = 0; + for ( int i = 0; i < ACcounts.length; i++ ) + index += Math.pow(multiplier, i) * ACcounts[i]; + return index; + } + + public int getACsum() { + int sum = 0; + for ( int count : ACcounts ) + sum += count; + return sum; + } + } + + public int linearExactMultiAllelic(GenotypesContext GLs, + int numAlternateAlleles, + double[] log10AlleleFrequencyPriors, + double[] log10AlleleFrequencyPosteriors, + boolean preserveData) { + + final ArrayList genotypeLikelihoods = getGLs(GLs); + final int numSamples = genotypeLikelihoods.size()-1; + final int numChr = 2*numSamples; + + // queue of AC conformations to process + final Queue ACqueue = new LinkedList(); + + // mapping of ExactACset indexes to the objects + final HashMap indexesToACset = new HashMap(numChr+1); + + // add AC=0 to the queue + int[] zeroCounts = new int[numAlternateAlleles]; + ExactACset zeroSet = new ExactACset(numSamples+1, zeroCounts); + ACqueue.add(zeroSet); + indexesToACset.put(0, zeroSet); + + // keep processing while we have AC conformations that need to be calculated + double maxLog10L = Double.NEGATIVE_INFINITY; + while ( !ACqueue.isEmpty() ) { + // compute log10Likelihoods + final ExactACset set = ACqueue.remove(); + final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPosteriors, log10AlleleFrequencyPriors); + + // adjust max likelihood seen if needed + maxLog10L = Math.max(maxLog10L, log10LofKs); + } + + // TODO -- finish me + + return 0; + } + + private static double calculateAlleleCountConformation(final ExactACset set, + final ArrayList genotypeLikelihoods, + final double maxLog10L, + final int numChr, + final boolean preserveData, + final Queue ACqueue, + final HashMap indexesToACset, + double[] log10AlleleFrequencyPriors, + double[] log10AlleleFrequencyPosteriors) { + + // compute the log10Likelihoods + computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPosteriors, log10AlleleFrequencyPriors); + + // clean up memory + if ( !preserveData ) { + for ( int index : set.dependentACsetsToDelete ) + indexesToACset.put(index, null); + } + + final double log10LofK = set.log10Likelihoods[set.log10Likelihoods.length-1]; + + // can we abort early because the log10Likelihoods are so small? + if ( log10LofK < maxLog10L - MAX_LOG10_ERROR_TO_STOP_EARLY ) { + if ( DEBUG ) System.out.printf(" *** breaking early ks=%d log10L=%.2f maxLog10L=%.2f%n", set.index, log10LofK, maxLog10L); + return log10LofK; + } + + // iterate over higher frequencies if possible + int ACwiggle = numChr - set.getACsum(); + if ( ACwiggle == 0 ) // all alternate alleles already sum to 2N + return log10LofK; + + ExactACset lastSet = null; + int numAltAlleles = set.ACcounts.length; + + // genotype log10Likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. + // So e.g. with 2 alt alleles the log10Likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. + + // do it for the k+1 case + int PLindex = 0; + for ( int allele = 0; allele < numAltAlleles; allele++ ) { + int[] ACcountsClone = set.ACcounts.clone(); + ACcountsClone[allele]++; + lastSet = updateACset(ACcountsClone, numChr, set.getIndex(), ++PLindex, ACqueue, indexesToACset); + } + + // do it for the k+2 case if it makes sense; note that the 2 alleles may be the same or different + if ( ACwiggle > 1 ) { + for ( int allele_i = 0; allele_i < numAltAlleles; allele_i++ ) { + for ( int allele_j = allele_i; allele_j < numAltAlleles; allele_j++ ) { + int[] ACcountsClone = set.ACcounts.clone(); + ACcountsClone[allele_i]++; + ACcountsClone[allele_j]++; + lastSet = updateACset(ACcountsClone, numChr,set.getIndex(), ++PLindex , ACqueue, indexesToACset); + } + } + } + + if ( lastSet == null ) + throw new ReviewedStingException("No new AC sets were added or updated but the AC still hasn't reached 2N"); + lastSet.dependentACsetsToDelete.add(set.index); + + return log10LofK; + } + + private static ExactACset updateACset(int[] ACcounts, + int numChr, + final int callingSetIndex, + final int PLsetIndex, + final Queue ACqueue, + final HashMap indexesToACset) { + final int index = ExactACset.generateIndex(ACcounts, numChr+1); + if ( !indexesToACset.containsKey(index) ) { + ExactACset set = new ExactACset(numChr/2 +1, ACcounts); + indexesToACset.put(index, set); + ACqueue.add(set); + } + + // add the given dependency to the set + ExactACset set = indexesToACset.get(index); + set.ACsetIndexToPLIndex.put(callingSetIndex, PLsetIndex); + return set; + } + + private static void computeLofK(ExactACset set, + ArrayList genotypeLikelihoods, + final HashMap indexesToACset, + double[] log10AlleleFrequencyPriors, + double[] log10AlleleFrequencyPosteriors) { + + set.log10Likelihoods[0] = 0.0; // the zero case + int totalK = set.getACsum(); + + // special case for k = 0 over all k + if ( set.getIndex() == AC_ZERO_INDEX ) { + for ( int j = 1; j < set.log10Likelihoods.length; j++ ) + set.log10Likelihoods[j] = set.log10Likelihoods[j-1] + genotypeLikelihoods.get(j)[HOM_REF_INDEX]; + } + // k > 0 for at least one k + else { + // all possible likelihoods for a given cell from which to choose the max + final int numPaths = set.ACsetIndexToPLIndex.size() + 1; + final double[] log10ConformationLikelihoods = new double[numPaths]; + + for ( int j = 1; j < set.log10Likelihoods.length; j++ ) { + final double[] gl = genotypeLikelihoods.get(j); + final double logDenominator = MathUtils.log10Cache[2*j] + MathUtils.log10Cache[2*j-1]; + + for ( int i = 0; i < numPaths; i++ ) + log10ConformationLikelihoods[i] = Double.NEGATIVE_INFINITY; + + // deal with the AA case first + if ( totalK < 2*j-1 ) + log10ConformationLikelihoods[0] = MathUtils.log10Cache[2*j-totalK] + MathUtils.log10Cache[2*j-totalK-1] + set.log10Likelihoods[j-1] + gl[HOM_REF_INDEX]; + + // deal with the other possible conformations now + if ( totalK < 2*j ) { + int conformationIndex = 1; + for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) + log10ConformationLikelihoods[conformationIndex++] = + determineCoefficient(mapping.getValue(), j, totalK) + indexesToACset.get(mapping.getKey()).log10Likelihoods[j-1] + gl[mapping.getValue()]; + } + + double log10Max = approximateLog10SumLog10(log10ConformationLikelihoods); + + // finally, update the L(j,k) value + set.log10Likelihoods[j] = log10Max - logDenominator; + } + } + + // update the posteriors vector + final double log10LofK = set.log10Likelihoods[set.log10Likelihoods.length-1]; + + // TODO -- this needs to be fixed; hard-coding in the biallelic case + log10AlleleFrequencyPosteriors[totalK] = log10LofK + log10AlleleFrequencyPriors[totalK]; + } + + private static double determineCoefficient(int PLindex, int j, int totalK) { + + // TODO -- the math here needs to be fixed and checked; hard-coding in the biallelic case + //AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. + + double coeff; + if ( PLindex == 1 ) + coeff = MathUtils.log10Cache[2*totalK] + MathUtils.log10Cache[2*j-totalK]; + else + coeff = MathUtils.log10Cache[totalK] + MathUtils.log10Cache[totalK-1]; + return coeff; + } /** * Can be overridden by concrete subclasses diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index 62218416d..d7101da6b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java @@ -153,6 +153,10 @@ public class UnifiedArgumentCollection { @Argument(fullName = "ignoreSNPAlleles", shortName = "ignoreSNPAlleles", doc = "expt", required = false) public boolean IGNORE_SNP_ALLELES = false; + @Hidden + @Argument(fullName = "multiallelic", shortName = "multiallelic", doc = "Allow multiple alleles in discovery", required = false) + public boolean MULTI_ALLELIC = false; + // Developers must remember to add any newly added arguments to the list here as well otherwise they won't get changed from their default value! public UnifiedArgumentCollection clone() { @@ -180,6 +184,7 @@ public class UnifiedArgumentCollection { // todo- arguments to remove uac.IGNORE_SNP_ALLELES = IGNORE_SNP_ALLELES; uac.BANDED_INDEL_COMPUTATION = BANDED_INDEL_COMPUTATION; + uac.MULTI_ALLELIC = MULTI_ALLELIC; return uac; } From 29662be3d771718e41828d188132f679c0dc01d1 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sat, 3 Dec 2011 23:12:04 -0500 Subject: [PATCH 212/380] Fixed bug where k=2N case wasn't properly being computed. Added optimization for BB genotype case not in old model. At this point, integration tests pass except for 1 case where QUALs differ by 0.01 (this is okay because I occasionally need to compute extra cells in the matrix which affects the approximations) and 2 cases where multi-allelic indels are being genotyped (some work still needs to be done to support them). --- .../genotyper/ExactAFCalculationModel.java | 15 +++++++-------- .../UnifiedGenotyperIntegrationTest.java | 2 +- 2 files changed, 8 insertions(+), 9 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 ae7c2f5c1..26c777296 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 @@ -317,11 +317,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - public int linearExactMultiAllelic(GenotypesContext GLs, - int numAlternateAlleles, - double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors, - boolean preserveData) { + static public int linearExactMultiAllelic(GenotypesContext GLs, + int numAlternateAlleles, + double[] log10AlleleFrequencyPriors, + double[] log10AlleleFrequencyPosteriors, + boolean preserveData) { final ArrayList genotypeLikelihoods = getGLs(GLs); final int numSamples = genotypeLikelihoods.size()-1; @@ -350,8 +350,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { maxLog10L = Math.max(maxLog10L, log10LofKs); } - // TODO -- finish me - + // TODO -- why do we need to return anything here? return 0; } @@ -471,7 +470,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { log10ConformationLikelihoods[0] = MathUtils.log10Cache[2*j-totalK] + MathUtils.log10Cache[2*j-totalK-1] + set.log10Likelihoods[j-1] + gl[HOM_REF_INDEX]; // deal with the other possible conformations now - if ( totalK < 2*j ) { + if ( totalK <= 2*j ) { // skip impossible conformations int conformationIndex = 1; for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) log10ConformationLikelihoods[conformationIndex++] = 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 34e1ad30e..11e086db8 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("286f0de92e4ce57986ba861390c6019d")); + Arrays.asList("b70732a2f63f8409b61e41fa53eaae3e")); executeTest("test MultiSample Pilot1", spec); } From eab2b76c9b9baba726eb86a1b6992e6576ca77fe Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sat, 3 Dec 2011 23:54:42 -0500 Subject: [PATCH 213/380] Added loads of comments for future reference --- .../genotyper/ExactAFCalculationModel.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 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 26c777296..c7d91f524 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 @@ -284,11 +284,21 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // This class represents a column in the Exact AC calculation matrix private static final class ExactACset { + + // the counts of the various alternate alleles which this column represents final int[] ACcounts; + + // the column of the matrix final double[] log10Likelihoods; + + // mapping of column index for those columns upon which this one depends to the index into the PLs which is used as the transition to this column; + // for example, in the biallelic case, the transition from k=0 to k=1 would be AB while the transition to k=2 would be BB. final HashMap ACsetIndexToPLIndex = new HashMap(); + + // to minimize memory consumption, we know we can delete any sets in this list because no further sets will depend on them final ArrayList dependentACsetsToDelete = new ArrayList(); + // index used to represent this set in the global hashmap: (numSamples^0 * allele_1) + (numSamples^1 * allele_2) + (numSamples^2 * allele_3) + ... private int index = -1; public ExactACset(int size, int[] ACcounts) { @@ -309,6 +319,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return index; } + // sum of all the non-reference alleles public int getACsum() { int sum = 0; for ( int count : ACcounts ) @@ -361,8 +372,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final boolean preserveData, final Queue ACqueue, final HashMap indexesToACset, - double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors) { + final double[] log10AlleleFrequencyPriors, + final double[] log10AlleleFrequencyPosteriors) { // compute the log10Likelihoods computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPosteriors, log10AlleleFrequencyPriors); @@ -383,16 +394,16 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // iterate over higher frequencies if possible int ACwiggle = numChr - set.getACsum(); - if ( ACwiggle == 0 ) // all alternate alleles already sum to 2N + if ( ACwiggle == 0 ) // all alternate alleles already sum to 2N so we cannot possibly go to higher frequencies return log10LofK; - ExactACset lastSet = null; + ExactACset lastSet = null; // keep track of the last set placed in the queue so that we can tell it to clean us up when done processing int numAltAlleles = set.ACcounts.length; - // genotype log10Likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. - // So e.g. with 2 alt alleles the log10Likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. + // genotype likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. + // so e.g. with 2 alt alleles the likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. - // do it for the k+1 case + // add conformations for the k+1 case int PLindex = 0; for ( int allele = 0; allele < numAltAlleles; allele++ ) { int[] ACcountsClone = set.ACcounts.clone(); @@ -400,7 +411,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { lastSet = updateACset(ACcountsClone, numChr, set.getIndex(), ++PLindex, ACqueue, indexesToACset); } - // do it for the k+2 case if it makes sense; note that the 2 alleles may be the same or different + // add conformations for the k+2 case if it makes sense; note that the 2 new alleles may be the same or different if ( ACwiggle > 1 ) { for ( int allele_i = 0; allele_i < numAltAlleles; allele_i++ ) { for ( int allele_j = allele_i; allele_j < numAltAlleles; allele_j++ ) { @@ -419,6 +430,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return log10LofK; } + // adds the ExactACset represented by the ACcounts to the ACqueue if not already there (creating it if needed) and + // also adds it as a dependency to the given callingSetIndex. private static ExactACset updateACset(int[] ACcounts, int numChr, final int callingSetIndex, @@ -462,6 +475,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final double[] gl = genotypeLikelihoods.get(j); final double logDenominator = MathUtils.log10Cache[2*j] + MathUtils.log10Cache[2*j-1]; + // initialize for ( int i = 0; i < numPaths; i++ ) log10ConformationLikelihoods[i] = Double.NEGATIVE_INFINITY; From a7cb941417598afd75cce4e3e6ead5db8f50e574 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sun, 4 Dec 2011 13:02:53 -0500 Subject: [PATCH 214/380] The posteriors vector is now 2 dimensional so that it supports multiple alleles (although the UG is still hard-coded to use only array[0] for now); the exact model now collapses probabilities for all conformations over a given AC into the posteriors array (in the appropriate dimension). Fixed a bug where the priors and posteriors were being passed in swapped. --- .../AlleleFrequencyCalculationModel.java | 6 +- .../genotyper/ExactAFCalculationModel.java | 105 +++++++----------- .../genotyper/UnifiedGenotyperEngine.java | 47 ++++---- 3 files changed, 67 insertions(+), 91 deletions(-) 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 a8ce98945..c2f950ef5 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 @@ -52,7 +52,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { protected enum GenotypeType { AA, AB, BB } - protected static final double VALUE_NOT_CALCULATED = -1.0 * Double.MAX_VALUE; + protected static final double VALUE_NOT_CALCULATED = Double.NEGATIVE_INFINITY; protected AlleleFrequencyCalculationModel(UnifiedArgumentCollection UAC, int N, Logger logger, PrintStream verboseWriter) { this.N = N; @@ -69,7 +69,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { */ protected abstract void getLog10PNonRef(GenotypesContext GLs, List Alleles, double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors); + double[][] log10AlleleFrequencyPosteriors); /** * Can be overridden by concrete subclasses @@ -80,6 +80,6 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * @return calls */ protected abstract GenotypesContext assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, + 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 c7d91f524..0fa311303 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 @@ -41,9 +41,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // private final static boolean DEBUG = false; 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); + + private static final boolean SIMPLE_GREEDY_GENOTYPER = false; + private static final List NO_CALL_ALLELES = Arrays.asList(Allele.NO_CALL, Allele.NO_CALL); private final boolean USE_MULTI_ALLELIC_CALCULATION; @@ -55,48 +56,13 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public void getLog10PNonRef(GenotypesContext GLs, List alleles, double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors) { + double[][] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); - final double[][] posteriorCache = numAlleles > 2 ? new double[numAlleles-1][] : null; - final double[] bestAFguess = numAlleles > 2 ? new double[numAlleles-1] : null; - int idxDiag = numAlleles; - int incr = numAlleles - 1; - for (int k=1; k < numAlleles; k++) { - // multi-allelic approximation, part 1: Ideally - // for each alt allele compute marginal (suboptimal) posteriors - - // compute indices for AA,AB,BB for current allele - genotype log10Likelihoods are a linear vector that can be thought of - // as a row-wise upper triangular matrix of log10Likelihoods. - // So, for example, with 2 alt alleles, log10Likelihoods have AA,AB,AC,BB,BC,CC. - // 3 alt alleles: AA,AB,AC,AD BB BC BD CC CD DD - - final int idxAA = 0; - final int idxAB = k; - // yy is always element on the diagonal. - // 2 alleles: BBelement 2 - // 3 alleles: BB element 3. CC element 5 - // 4 alleles: - final int idxBB = idxDiag; - idxDiag += incr--; - - final int lastK = USE_MULTI_ALLELIC_CALCULATION ? - linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, false) : - linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, idxAA, idxAB, idxBB); - - if (numAlleles > 2) { - posteriorCache[k-1] = log10AlleleFrequencyPosteriors.clone(); - bestAFguess[k-1] = (double)MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors); - } - } - - if (numAlleles > 2) { - // multiallelic approximation, part 2: - // report posteriors for allele that has highest estimated AC - int mostLikelyAlleleIdx = MathUtils.maxElementIndex(bestAFguess); - for (int k=0; k < log10AlleleFrequencyPosteriors.length-1; k++) - log10AlleleFrequencyPosteriors[k] = (posteriorCache[mostLikelyAlleleIdx][k]); - - } + if ( USE_MULTI_ALLELIC_CALCULATION ) + linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, false); + else + linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); } private static final ArrayList getGLs(GenotypesContext GLs) { @@ -161,7 +127,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public int linearExact(GenotypesContext GLs, double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors, int idxAA, int idxAB, int idxBB) { + double[][] log10AlleleFrequencyPosteriors) { final ArrayList genotypeLikelihoods = getGLs(GLs); final int numSamples = genotypeLikelihoods.size()-1; final int numChr = 2*numSamples; @@ -178,7 +144,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { if ( k == 0 ) { // special case for k = 0 for ( int j=1; j <= numSamples; j++ ) { - kMinus0[j] = kMinus0[j-1] + genotypeLikelihoods.get(j)[idxAA]; + kMinus0[j] = kMinus0[j-1] + genotypeLikelihoods.get(j)[0]; } } else { // k > 0 final double[] kMinus1 = logY.getkMinus1(); @@ -191,14 +157,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { double aa = Double.NEGATIVE_INFINITY; double ab = Double.NEGATIVE_INFINITY; if (k < 2*j-1) - aa = MathUtils.log10Cache[2*j-k] + MathUtils.log10Cache[2*j-k-1] + kMinus0[j-1] + gl[idxAA]; + aa = MathUtils.log10Cache[2*j-k] + MathUtils.log10Cache[2*j-k-1] + kMinus0[j-1] + gl[0]; if (k < 2*j) - ab = MathUtils.log10Cache[2*k] + MathUtils.log10Cache[2*j-k]+ kMinus1[j-1] + gl[idxAB]; + ab = MathUtils.log10Cache[2*k] + MathUtils.log10Cache[2*j-k]+ kMinus1[j-1] + gl[1]; double log10Max; if (k > 1) { - final double bb = MathUtils.log10Cache[k] + MathUtils.log10Cache[k-1] + kMinus2[j-1] + gl[idxBB]; + final double bb = MathUtils.log10Cache[k] + MathUtils.log10Cache[k-1] + kMinus2[j-1] + gl[2]; log10Max = approximateLog10SumLog10(aa, ab, bb); } else { // we know we aren't considering the BB case, so we can use an optimized log10 function @@ -212,7 +178,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // update the posteriors vector final double log10LofK = kMinus0[numSamples]; - log10AlleleFrequencyPosteriors[k] = log10LofK + log10AlleleFrequencyPriors[k]; + log10AlleleFrequencyPosteriors[0][k] = log10LofK + log10AlleleFrequencyPriors[k]; // can we abort early? lastK = k; @@ -239,7 +205,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } final static double approximateLog10SumLog10(double a, double b, double c) { - //return softMax(new double[]{a, b, c}); return approximateLog10SumLog10(approximateLog10SumLog10(a, b), c); } @@ -328,11 +293,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - static public int linearExactMultiAllelic(GenotypesContext GLs, - int numAlternateAlleles, - double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors, - boolean preserveData) { + public static void linearExactMultiAllelic(GenotypesContext GLs, + int numAlternateAlleles, + double[] log10AlleleFrequencyPriors, + double[][] log10AlleleFrequencyPosteriors, + boolean preserveData) { final ArrayList genotypeLikelihoods = getGLs(GLs); final int numSamples = genotypeLikelihoods.size()-1; @@ -355,14 +320,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { while ( !ACqueue.isEmpty() ) { // compute log10Likelihoods final ExactACset set = ACqueue.remove(); - final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPosteriors, log10AlleleFrequencyPriors); + final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); // adjust max likelihood seen if needed maxLog10L = Math.max(maxLog10L, log10LofKs); } - - // TODO -- why do we need to return anything here? - return 0; } private static double calculateAlleleCountConformation(final ExactACset set, @@ -373,10 +335,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final Queue ACqueue, final HashMap indexesToACset, final double[] log10AlleleFrequencyPriors, - final double[] log10AlleleFrequencyPosteriors) { + final double[][] log10AlleleFrequencyPosteriors) { // compute the log10Likelihoods - computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPosteriors, log10AlleleFrequencyPriors); + computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); // clean up memory if ( !preserveData ) { @@ -455,7 +417,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { ArrayList genotypeLikelihoods, final HashMap indexesToACset, double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors) { + double[][] log10AlleleFrequencyPosteriors) { set.log10Likelihoods[0] = 0.0; // the zero case int totalK = set.getACsum(); @@ -501,8 +463,21 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // update the posteriors vector final double log10LofK = set.log10Likelihoods[set.log10Likelihoods.length-1]; - // TODO -- this needs to be fixed; hard-coding in the biallelic case - log10AlleleFrequencyPosteriors[totalK] = log10LofK + log10AlleleFrequencyPriors[totalK]; + // determine the power of theta to use + int nonRefAlleles = 0; + for ( int i = 0; i < set.ACcounts.length; i++ ) { + if ( set.ACcounts[i] > 0 ) + nonRefAlleles++; + } + if ( nonRefAlleles == 0 ) // for k=0 we still want to use a power of 1 + nonRefAlleles++; + + // update the posteriors vector which is a collapsed view of each of the various ACs + for ( int i = 0; i < set.ACcounts.length; i++ ) { + // TODO -- double check the math and then cache these values for efficiency + double prior = Math.pow(log10AlleleFrequencyPriors[totalK], nonRefAlleles); + log10AlleleFrequencyPosteriors[i][set.ACcounts[i]] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][set.ACcounts[i]], log10LofK + prior); + } } private static double determineCoefficient(int PLindex, int j, int totalK) { @@ -521,18 +496,16 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { /** * Can be overridden by concrete subclasses * @param vc variant context with genotype likelihoods - * @param log10AlleleFrequencyPosteriors allele frequency results * @param AFofMaxLikelihood allele frequency of max likelihood * * @return calls */ public GenotypesContext assignGenotypes(VariantContext vc, - double[] log10AlleleFrequencyPosteriors, + 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()); - GenotypesContext GLs = vc.getGenotypes(); double[][] pathMetricArray = new double[GLs.size()+1][AFofMaxLikelihood+1]; int[][] tracebackArray = new int[GLs.size()+1][AFofMaxLikelihood+1]; 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 c861af1a2..148313f43 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 @@ -77,7 +77,7 @@ public class UnifiedGenotyperEngine { private final double[] log10AlleleFrequencyPriorsIndels; // the allele frequency likelihoods (allocated once as an optimization) - private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); + private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); // the priors object private final GenotypePriors genotypePriorsSNPs; @@ -295,7 +295,7 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyPosteriors.set(new double[N+1]); + log10AlleleFrequencyPosteriors.set(new double[1][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } @@ -310,10 +310,10 @@ public class UnifiedGenotyperEngine { afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); // find the most likely frequency - int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()); + int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[0]); // calculate p(f>0) - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()); + double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()[0]); double sum = 0.0; for (int i = 1; i <= N; i++) sum += normalizedPosteriors[i]; @@ -323,15 +323,15 @@ public class UnifiedGenotyperEngine { if ( bestAFguess != 0 || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { phredScaledConfidence = QualityUtils.phredScaleErrorRate(normalizedPosteriors[0]); if ( Double.isInfinite(phredScaledConfidence) ) - phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0]; + phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0][0]; } else { phredScaledConfidence = QualityUtils.phredScaleErrorRate(PofF); if ( Double.isInfinite(phredScaledConfidence) ) { sum = 0.0; for (int i = 1; i <= N; i++) { - if ( log10AlleleFrequencyPosteriors.get()[i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) + if ( log10AlleleFrequencyPosteriors.get()[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) break; - sum += log10AlleleFrequencyPosteriors.get()[i]; + sum += log10AlleleFrequencyPosteriors.get()[0][i]; } phredScaledConfidence = (MathUtils.compareDoubles(sum, 0.0) == 0 ? 0 : -10.0 * sum); } @@ -367,7 +367,7 @@ public class UnifiedGenotyperEngine { clearAFarray(log10AlleleFrequencyPosteriors.get()); afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); //double overallLog10PofNull = log10AlleleFrequencyPosteriors.get()[0]; - double overallLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get(), 1); + double overallLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); //if ( DEBUG_SLOD ) System.out.println("overallLog10PofF=" + overallLog10PofF); // the forward lod @@ -375,8 +375,8 @@ public class UnifiedGenotyperEngine { clearAFarray(log10AlleleFrequencyPosteriors.get()); afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); //double[] normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); - double forwardLog10PofNull = log10AlleleFrequencyPosteriors.get()[0]; - double forwardLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get(), 1); + double forwardLog10PofNull = log10AlleleFrequencyPosteriors.get()[0][0]; + double forwardLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); //if ( DEBUG_SLOD ) System.out.println("forwardLog10PofNull=" + forwardLog10PofNull + ", forwardLog10PofF=" + forwardLog10PofF); // the reverse lod @@ -384,8 +384,8 @@ public class UnifiedGenotyperEngine { clearAFarray(log10AlleleFrequencyPosteriors.get()); afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); //normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); - double reverseLog10PofNull = log10AlleleFrequencyPosteriors.get()[0]; - double reverseLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get(), 1); + double reverseLog10PofNull = log10AlleleFrequencyPosteriors.get()[0][0]; + double reverseLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); //if ( DEBUG_SLOD ) System.out.println("reverseLog10PofNull=" + reverseLog10PofNull + ", reverseLog10PofF=" + reverseLog10PofF); double forwardLod = forwardLog10PofF + reverseLog10PofNull - overallLog10PofF; @@ -440,7 +440,7 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyPosteriors.set(new double[N+1]); + log10AlleleFrequencyPosteriors.set(new double[1][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } @@ -453,10 +453,10 @@ public class UnifiedGenotyperEngine { afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); // find the most likely frequency - int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()); + int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[0]); // calculate p(f>0) - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()); + double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()[0]); double sum = 0.0; for (int i = 1; i <= N; i++) sum += normalizedPosteriors[i]; @@ -466,15 +466,15 @@ public class UnifiedGenotyperEngine { if ( bestAFguess != 0 || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { phredScaledConfidence = QualityUtils.phredScaleErrorRate(normalizedPosteriors[0]); if ( Double.isInfinite(phredScaledConfidence) ) - phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0]; + phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0][0]; } else { phredScaledConfidence = QualityUtils.phredScaleErrorRate(PofF); if ( Double.isInfinite(phredScaledConfidence) ) { sum = 0.0; for (int i = 1; i <= N; i++) { - if ( log10AlleleFrequencyPosteriors.get()[i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) + if ( log10AlleleFrequencyPosteriors.get()[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) break; - sum += log10AlleleFrequencyPosteriors.get()[i]; + sum += log10AlleleFrequencyPosteriors.get()[0][i]; } phredScaledConfidence = (MathUtils.compareDoubles(sum, 0.0) == 0 ? 0 : -10.0 * sum); } @@ -604,9 +604,12 @@ public class UnifiedGenotyperEngine { return stratifiedContexts; } - protected static void clearAFarray(double[] AFs) { - for ( int i = 0; i < AFs.length; i++ ) - AFs[i] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; + protected static void clearAFarray(double[][] AFs) { + for ( int i = 0; i < AFs.length; i++ ) { + for ( int j = 0; j < AFs[i].length; j++ ) { + AFs[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; + } + } } private final static double[] binomialProbabilityDepthCache = new double[10000]; @@ -676,7 +679,7 @@ public class UnifiedGenotyperEngine { AFline.append(i + "/" + N + "\t"); AFline.append(String.format("%.2f\t", ((float)i)/N)); AFline.append(String.format("%.8f\t", getAlleleFrequencyPriors(model)[i])); - if ( log10AlleleFrequencyPosteriors.get()[i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED) + if ( log10AlleleFrequencyPosteriors.get()[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED) AFline.append("0.00000000\t"); else AFline.append(String.format("%.8f\t", log10AlleleFrequencyPosteriors.get()[i])); From 1ba03a5e7234f6e5a656801aaf31b7a2fa931500 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Mon, 5 Dec 2011 14:06:00 -0500 Subject: [PATCH 215/380] Use optional() instead of required() to construct javaMemoryLimit argument in JavaCommandLineFunction --- .../sting/queue/function/JavaCommandLineFunction.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala index 8cbc2c577..5b19cf9b6 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/function/JavaCommandLineFunction.scala @@ -72,7 +72,7 @@ trait JavaCommandLineFunction extends CommandLineFunction { null } - def javaOpts = required("-Xmx", javaMemoryLimit.map(gb => (gb * 1024).ceil.toInt), "m", spaceSeparated=false) + + def javaOpts = optional("-Xmx", javaMemoryLimit.map(gb => (gb * 1024).ceil.toInt), "m", spaceSeparated=false) + required("-Djava.io.tmpdir=", jobTempDir, spaceSeparated=false) def commandLine = required("java") + From 7fac4afab3adc5d11670ca0f1a1dcb63afd05f92 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 5 Dec 2011 15:57:25 -0500 Subject: [PATCH 216/380] Fixed priors (now initialized upon engine startup in a multi-dimensional array) and cell coefficients (properly handles the generalized closed form representation for multiple alleles). --- .../AlleleFrequencyCalculationModel.java | 2 +- .../genotyper/ExactAFCalculationModel.java | 98 ++++++++++++------- .../genotyper/UnifiedGenotyperEngine.java | 51 +++++----- 3 files changed, 89 insertions(+), 62 deletions(-) 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 c2f950ef5..2808e6968 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 @@ -68,7 +68,7 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store results */ protected abstract void getLog10PNonRef(GenotypesContext GLs, List Alleles, - double[] log10AlleleFrequencyPriors, + 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 0fa311303..d801885c6 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 @@ -55,14 +55,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } public void getLog10PNonRef(GenotypesContext GLs, List alleles, - double[] log10AlleleFrequencyPriors, + double[][] log10AlleleFrequencyPriors, double[][] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); if ( USE_MULTI_ALLELIC_CALCULATION ) linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, false); else - linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); + linearExact(GLs, log10AlleleFrequencyPriors[0], log10AlleleFrequencyPosteriors); } private static final ArrayList getGLs(GenotypesContext GLs) { @@ -266,7 +266,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // index used to represent this set in the global hashmap: (numSamples^0 * allele_1) + (numSamples^1 * allele_2) + (numSamples^2 * allele_3) + ... private int index = -1; - public ExactACset(int size, int[] ACcounts) { + public ExactACset(final int size, final int[] ACcounts) { this.ACcounts = ACcounts; log10Likelihoods = new double[size]; } @@ -277,7 +277,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return index; } - public static int generateIndex(int[] ACcounts, int multiplier) { + public static int generateIndex(final int[] ACcounts, final int multiplier) { int index = 0; for ( int i = 0; i < ACcounts.length; i++ ) index += Math.pow(multiplier, i) * ACcounts[i]; @@ -293,11 +293,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - public static void linearExactMultiAllelic(GenotypesContext GLs, - int numAlternateAlleles, - double[] log10AlleleFrequencyPriors, - double[][] log10AlleleFrequencyPosteriors, - boolean preserveData) { + public static void linearExactMultiAllelic(final GenotypesContext GLs, + final int numAlternateAlleles, + final double[][] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyPosteriors, + final boolean preserveData) { final ArrayList genotypeLikelihoods = getGLs(GLs); final int numSamples = genotypeLikelihoods.size()-1; @@ -334,7 +334,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final boolean preserveData, final Queue ACqueue, final HashMap indexesToACset, - final double[] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyPriors, final double[][] log10AlleleFrequencyPosteriors) { // compute the log10Likelihoods @@ -355,12 +355,12 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } // iterate over higher frequencies if possible - int ACwiggle = numChr - set.getACsum(); + final int ACwiggle = numChr - set.getACsum(); if ( ACwiggle == 0 ) // all alternate alleles already sum to 2N so we cannot possibly go to higher frequencies return log10LofK; ExactACset lastSet = null; // keep track of the last set placed in the queue so that we can tell it to clean us up when done processing - int numAltAlleles = set.ACcounts.length; + final int numAltAlleles = set.ACcounts.length; // genotype likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. // so e.g. with 2 alt alleles the likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. @@ -368,7 +368,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // add conformations for the k+1 case int PLindex = 0; for ( int allele = 0; allele < numAltAlleles; allele++ ) { - int[] ACcountsClone = set.ACcounts.clone(); + final int[] ACcountsClone = set.ACcounts.clone(); ACcountsClone[allele]++; lastSet = updateACset(ACcountsClone, numChr, set.getIndex(), ++PLindex, ACqueue, indexesToACset); } @@ -377,7 +377,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { if ( ACwiggle > 1 ) { for ( int allele_i = 0; allele_i < numAltAlleles; allele_i++ ) { for ( int allele_j = allele_i; allele_j < numAltAlleles; allele_j++ ) { - int[] ACcountsClone = set.ACcounts.clone(); + final int[] ACcountsClone = set.ACcounts.clone(); ACcountsClone[allele_i]++; ACcountsClone[allele_j]++; lastSet = updateACset(ACcountsClone, numChr,set.getIndex(), ++PLindex , ACqueue, indexesToACset); @@ -394,8 +394,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // adds the ExactACset represented by the ACcounts to the ACqueue if not already there (creating it if needed) and // also adds it as a dependency to the given callingSetIndex. - private static ExactACset updateACset(int[] ACcounts, - int numChr, + private static ExactACset updateACset(final int[] ACcounts, + final int numChr, final int callingSetIndex, final int PLsetIndex, final Queue ACqueue, @@ -408,19 +408,19 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } // add the given dependency to the set - ExactACset set = indexesToACset.get(index); + final ExactACset set = indexesToACset.get(index); set.ACsetIndexToPLIndex.put(callingSetIndex, PLsetIndex); return set; } - private static void computeLofK(ExactACset set, - ArrayList genotypeLikelihoods, + private static void computeLofK(final ExactACset set, + final ArrayList genotypeLikelihoods, final HashMap indexesToACset, - double[] log10AlleleFrequencyPriors, - double[][] log10AlleleFrequencyPosteriors) { + final double[][] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyPosteriors) { set.log10Likelihoods[0] = 0.0; // the zero case - int totalK = set.getACsum(); + final int totalK = set.getACsum(); // special case for k = 0 over all k if ( set.getIndex() == AC_ZERO_INDEX ) { @@ -450,10 +450,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { int conformationIndex = 1; for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) log10ConformationLikelihoods[conformationIndex++] = - determineCoefficient(mapping.getValue(), j, totalK) + indexesToACset.get(mapping.getKey()).log10Likelihoods[j-1] + gl[mapping.getValue()]; + determineCoefficient(mapping.getValue(), j, set.ACcounts, totalK) + indexesToACset.get(mapping.getKey()).log10Likelihoods[j-1] + gl[mapping.getValue()]; } - double log10Max = approximateLog10SumLog10(log10ConformationLikelihoods); + final double log10Max = approximateLog10SumLog10(log10ConformationLikelihoods); // finally, update the L(j,k) value set.log10Likelihoods[j] = log10Max - logDenominator; @@ -469,27 +469,53 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { if ( set.ACcounts[i] > 0 ) nonRefAlleles++; } - if ( nonRefAlleles == 0 ) // for k=0 we still want to use a power of 1 - nonRefAlleles++; // update the posteriors vector which is a collapsed view of each of the various ACs for ( int i = 0; i < set.ACcounts.length; i++ ) { - // TODO -- double check the math and then cache these values for efficiency - double prior = Math.pow(log10AlleleFrequencyPriors[totalK], nonRefAlleles); + // for k=0 we still want to use theta + final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][set.ACcounts[i]]; log10AlleleFrequencyPosteriors[i][set.ACcounts[i]] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][set.ACcounts[i]], log10LofK + prior); } } - private static double determineCoefficient(int PLindex, int j, int totalK) { + private static double determineCoefficient(int PLindex, final int j, final int[] ACcounts, final int totalK) { - // TODO -- the math here needs to be fixed and checked; hard-coding in the biallelic case - //AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. + // the closed form representation generalized for multiple alleles is as follows: + // AA: (2j - totalK) * (2j - totalK - 1) + // AB: 2k_b * (2j - totalK) + // AC: 2k_c * (2j - totalK) + // BB: k_b * (k_b - 1) + // BC: 2 * k_b * k_c + // CC: k_c * (k_c - 1) + + final int numAltAlleles = ACcounts.length; + + // the AX het case + if ( PLindex <= numAltAlleles ) + return MathUtils.log10Cache[2*ACcounts[PLindex-1]] + MathUtils.log10Cache[2*j-totalK]; + + int subtractor = numAltAlleles+1; + int subtractions = 0; + do { + PLindex -= subtractor; + subtractor--; + subtractions++; + } + while ( PLindex >= subtractor ); + + final int k_i = ACcounts[subtractions-1]; + + // the hom var case (e.g. BB, CC, DD) + final double coeff; + if ( PLindex == 0 ) { + coeff = MathUtils.log10Cache[k_i] + MathUtils.log10Cache[k_i - 1]; + } + // the het non-ref case (e.g. BC, BD, CD) + else { + final int k_j = ACcounts[subtractions+PLindex-1]; + coeff = MathUtils.log10Cache[2] + MathUtils.log10Cache[k_i] + MathUtils.log10Cache[k_j]; + } - double coeff; - if ( PLindex == 1 ) - coeff = MathUtils.log10Cache[2*totalK] + MathUtils.log10Cache[2*j-totalK]; - else - coeff = MathUtils.log10Cache[totalK] + MathUtils.log10Cache[totalK-1]; return coeff; } 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 148313f43..f13bbdcd4 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 @@ -73,12 +73,15 @@ public class UnifiedGenotyperEngine { private ThreadLocal afcm = new ThreadLocal(); // because the allele frequency priors are constant for a given i, we cache the results to avoid having to recompute everything - private final double[] log10AlleleFrequencyPriorsSNPs; - private final double[] log10AlleleFrequencyPriorsIndels; + private final double[][] log10AlleleFrequencyPriorsSNPs; + private final double[][] log10AlleleFrequencyPriorsIndels; // the allele frequency likelihoods (allocated once as an optimization) private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); + // the maximum number of alternate alleles for genotyping supported by the genotyper; we fix this here so that the AF priors and posteriors can be initialized at startup + private static final int MAX_NUMBER_OF_ALTERNATE_ALLELES = 5; + // the priors object private final GenotypePriors genotypePriorsSNPs; private final GenotypePriors genotypePriorsIndels; @@ -122,10 +125,10 @@ public class UnifiedGenotyperEngine { this.annotationEngine = engine; N = 2 * this.samples.size(); - log10AlleleFrequencyPriorsSNPs = new double[N+1]; - log10AlleleFrequencyPriorsIndels = new double[N+1]; - computeAlleleFrequencyPriors(N, log10AlleleFrequencyPriorsSNPs, GenotypeLikelihoodsCalculationModel.Model.SNP); - computeAlleleFrequencyPriors(N, log10AlleleFrequencyPriorsIndels, GenotypeLikelihoodsCalculationModel.Model.INDEL); + log10AlleleFrequencyPriorsSNPs = new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]; + log10AlleleFrequencyPriorsIndels = new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]; + computeAlleleFrequencyPriors(N, log10AlleleFrequencyPriorsSNPs, UAC.heterozygosity); + computeAlleleFrequencyPriors(N, log10AlleleFrequencyPriorsIndels, UAC.INDEL_HETEROZYGOSITY); genotypePriorsSNPs = createGenotypePriors(GenotypeLikelihoodsCalculationModel.Model.SNP); genotypePriorsIndels = createGenotypePriors(GenotypeLikelihoodsCalculationModel.Model.INDEL); @@ -295,7 +298,7 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyPosteriors.set(new double[1][N+1]); + log10AlleleFrequencyPosteriors.set(new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } @@ -440,7 +443,7 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyPosteriors.set(new double[1][N+1]); + log10AlleleFrequencyPosteriors.set(new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } @@ -747,27 +750,25 @@ public class UnifiedGenotyperEngine { return null; } - protected void computeAlleleFrequencyPriors(int N, final double[] priors, final GenotypeLikelihoodsCalculationModel.Model model) { - // calculate the allele frequency priors for 1-N - double sum = 0.0; - double heterozygosity; + protected static void computeAlleleFrequencyPriors(final int N, final double[][] priors, final double theta) { - if (model == GenotypeLikelihoodsCalculationModel.Model.INDEL) - heterozygosity = UAC.INDEL_HETEROZYGOSITY; - else - heterozygosity = UAC.heterozygosity; - - for (int i = 1; i <= N; i++) { - double value = heterozygosity / (double)i; - priors[i] = Math.log10(value); - sum += value; + // the dimension here is the number of alternate alleles; with e.g. 2 alternate alleles the prior will be theta^2 / i + for (int alleles = 1; alleles <= priors.length; alleles++) { + double sum = 0.0; + + // for each i + for (int i = 1; i <= N; i++) { + double value = Math.pow(theta, alleles) / (double)i; + priors[alleles-1][i] = Math.log10(value); + sum += value; + } + + // null frequency for AF=0 is (1 - sum(all other frequencies)) + priors[alleles-1][0] = Math.log10(1.0 - sum); } - - // null frequency for AF=0 is (1 - sum(all other frequencies)) - priors[0] = Math.log10(1.0 - sum); } - protected double[] getAlleleFrequencyPriors( final GenotypeLikelihoodsCalculationModel.Model model ) { + protected double[][] getAlleleFrequencyPriors( final GenotypeLikelihoodsCalculationModel.Model model ) { switch( model ) { case SNP: return log10AlleleFrequencyPriorsSNPs; From 7a0f6feda45fbf6f2b7ce4c080e241e39b126782 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 5 Dec 2011 16:18:52 -0500 Subject: [PATCH 217/380] Make sure that too many alternate alleles aren't being passed to the genotyper (10 for now) and exit with a UserError if there are. --- .../walkers/genotyper/UnifiedGenotyperEngine.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 f13bbdcd4..beba865dd 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 @@ -38,6 +38,7 @@ import org.broadinstitute.sting.utils.*; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; @@ -80,7 +81,7 @@ public class UnifiedGenotyperEngine { private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); // the maximum number of alternate alleles for genotyping supported by the genotyper; we fix this here so that the AF priors and posteriors can be initialized at startup - private static final int MAX_NUMBER_OF_ALTERNATE_ALLELES = 5; + private static final int MAX_NUMBER_OF_ALTERNATE_ALLELES = 10; // the priors object private final GenotypePriors genotypePriorsSNPs; @@ -302,6 +303,10 @@ public class UnifiedGenotyperEngine { afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } + // don't try to genotype too many alternate alleles + if ( vc.getAlternateAlleles().size() > MAX_NUMBER_OF_ALTERNATE_ALLELES ) + throw new UserException("the Unified Genotyper is currently not equipped to genotype more than " + MAX_NUMBER_OF_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles"); + // estimate our confidence in a reference call and return if ( vc.getNSamples() == 0 ) return (UAC.OutputMode != OUTPUT_MODE.EMIT_ALL_SITES ? @@ -447,6 +452,10 @@ public class UnifiedGenotyperEngine { afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } + // don't try to genotype too many alternate alleles + if ( vc.getAlternateAlleles().size() > MAX_NUMBER_OF_ALTERNATE_ALLELES ) + throw new UserException("the Unified Genotyper is currently not equipped to genotype more than " + MAX_NUMBER_OF_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles"); + // estimate our confidence in a reference call and return if ( vc.getNSamples() == 0 ) return null; From 677bea0abdb1010da084818a0b8903f4e6ea1dbf Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Mon, 5 Dec 2011 23:04:33 -0500 Subject: [PATCH 218/380] Right aligning GATKReport numeric columns and updated MD5s in tests. PreQC parses file with spaces in sample names by using tabs only. PostQC allows passing the file names for the evals so that flanks can be evaled. BaseTest's network temp dir now adds the user name to the path so files aren't created in the root. HybridSelectionPipeline: - Updated to latest versions of reference data. - Refactored Picard parsing code replacing YAML. --- .../sting/gatk/report/GATKReportColumn.java | 44 +++++-- .../report/GATKReportColumnFormat.java} | 50 ++++---- .../sting/gatk/report/GATKReportTable.java | 11 +- .../diffengine/GATKReportDiffableReader.java | 4 +- .../sting/pipeline/Pipeline.java | 62 ---------- .../sting/pipeline/PipelineProject.java | 115 ------------------ .../sting/utils/io/IOUtils.java | 23 ++++ .../utils/yaml/FieldOrderComparator.java | 52 -------- .../utils/yaml/StingYamlRepresenter.java | 88 -------------- .../sting/utils/yaml/YamlUtils.java | 107 ---------------- .../org/broadinstitute/sting/BaseTest.java | 3 +- .../sting/gatk/report/GATKReportUnitTest.java | 28 +++++ .../DiffObjectsIntegrationTest.java | 4 +- .../VariantEvalIntegrationTest.java | 44 +++---- .../VCFStreamingIntegrationTest.java | 2 +- .../sting/pipeline/PipelineUnitTest.java | 88 -------------- 16 files changed, 143 insertions(+), 582 deletions(-) rename public/java/src/org/broadinstitute/sting/{pipeline/PipelineSample.java => gatk/report/GATKReportColumnFormat.java} (55%) delete mode 100644 public/java/src/org/broadinstitute/sting/pipeline/Pipeline.java delete mode 100644 public/java/src/org/broadinstitute/sting/pipeline/PipelineProject.java delete mode 100644 public/java/src/org/broadinstitute/sting/utils/yaml/FieldOrderComparator.java delete mode 100644 public/java/src/org/broadinstitute/sting/utils/yaml/StingYamlRepresenter.java delete mode 100644 public/java/src/org/broadinstitute/sting/utils/yaml/YamlUtils.java delete mode 100644 public/java/test/org/broadinstitute/sting/pipeline/PipelineUnitTest.java 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 6452c7b2b..5a6490afe 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumn.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumn.java @@ -1,6 +1,8 @@ package org.broadinstitute.sting.gatk.report; -import java.util.TreeMap; +import org.apache.commons.lang.math.NumberUtils; + +import java.util.*; /** * Holds values for a column in a GATK report table @@ -16,12 +18,9 @@ public class GATKReportColumn extends TreeMap { * * @param columnName the name of the column * @param defaultValue the default value of the column - * @param display if true, the column will be displayed in the final output + * @param display if true, the column will be displayed in the final output + * @param format format string */ - 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; @@ -77,22 +76,47 @@ public class GATKReportColumn extends TreeMap { /** * Get the display width for this column. This allows the entire column to be displayed with the appropriate, fixed width. - * @return the width of this column + * @return the format string for this column */ - public int getColumnWidth() { + public GATKReportColumnFormat getColumnFormat() { int maxWidth = columnName.length(); + GATKReportColumnFormat.Alignment alignment = GATKReportColumnFormat.Alignment.RIGHT; for (Object obj : this.values()) { if (obj != null) { - int width = formatValue(obj).length(); + String formatted = formatValue(obj); + int width = formatted.length(); if (width > maxWidth) { maxWidth = width; } + + if (alignment == GATKReportColumnFormat.Alignment.RIGHT) { + if (!isRightAlign(formatted)) { + alignment = GATKReportColumnFormat.Alignment.LEFT; + } + } } } - return maxWidth; + return new GATKReportColumnFormat(maxWidth, alignment); + } + + private static final Collection RIGHT_ALIGN_STRINGS = Arrays.asList( + "null", + "NA", + String.valueOf(Double.POSITIVE_INFINITY), + String.valueOf(Double.NEGATIVE_INFINITY), + String.valueOf(Double.NaN)); + + /** + * Check if the value can be right aligned. Does not trim the values before checking if numeric since it assumes + * the spaces mean that the value is already padded. + * @param value to check + * @return true if the value is a right alignable + */ + protected static boolean isRightAlign(String value) { + return value == null || RIGHT_ALIGN_STRINGS.contains(value) || NumberUtils.isNumber(value); } /** diff --git a/public/java/src/org/broadinstitute/sting/pipeline/PipelineSample.java b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumnFormat.java similarity index 55% rename from public/java/src/org/broadinstitute/sting/pipeline/PipelineSample.java rename to public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumnFormat.java index 7cd25fed5..6d19a83aa 100644 --- a/public/java/src/org/broadinstitute/sting/pipeline/PipelineSample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumnFormat.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 @@ -22,41 +22,41 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.pipeline; - -import java.io.File; -import java.util.Map; -import java.util.TreeMap; +package org.broadinstitute.sting.gatk.report; /** - * Java bean defining a sample for a pipeline. + * Column width and left/right alignment. */ -public class PipelineSample { - private String id; - private Map bamFiles = new TreeMap(); - private Map tags = new TreeMap(); +public class GATKReportColumnFormat { + public static enum Alignment { LEFT, RIGHT } + public int width; + public Alignment alignment; - public String getId() { - return id; + public GATKReportColumnFormat(int width, Alignment alignment) { + this.width = width; + this.alignment = alignment; } - public void setId(String id) { - this.id = id; + public int getWidth() { + return width; } - public Map getBamFiles() { - return bamFiles; + public Alignment getAlignment() { + return alignment; } - public void setBamFiles(Map bamFiles) { - this.bamFiles = bamFiles; + public String getNameFormat() { + return "%-" + width + "s"; } - public Map getTags() { - return tags; - } - - public void setTags(Map tags) { - this.tags = tags; + public String getValueFormat() { + switch (alignment) { + case LEFT: + return "%-" + width + "s"; + case RIGHT: + return "%" + width + "s"; + default: + throw new UnsupportedOperationException("Unknown alignment: " + alignment); + } } } 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 95c2a14fc..b72b20e0b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java @@ -608,12 +608,9 @@ public class GATKReportTable { */ public void write(PrintStream out) { // Get the column widths for everything - HashMap columnWidths = new HashMap(); + HashMap columnFormats = new HashMap(); for (String columnName : columns.keySet()) { - int width = columns.get(columnName).getColumnWidth(); - String format = "%-" + String.valueOf(width) + "s"; - - columnWidths.put(columnName, format); + columnFormats.put(columnName, columns.get(columnName).getColumnFormat()); } String primaryKeyFormat = "%-" + getPrimaryKeyColumnWidth() + "s"; @@ -630,7 +627,7 @@ public class GATKReportTable { for (String columnName : columns.keySet()) { if (columns.get(columnName).isDisplayable()) { if (needsPadding) { out.printf(" "); } - out.printf(columnWidths.get(columnName), columnName); + out.printf(columnFormats.get(columnName).getNameFormat(), columnName); needsPadding = true; } @@ -650,7 +647,7 @@ public class GATKReportTable { if (columns.get(columnName).isDisplayable()) { if (needsPadding) { out.printf(" "); } String value = columns.get(columnName).getStringValue(primaryKey); - out.printf(columnWidths.get(columnName), value); + out.printf(columnFormats.get(columnName).getValueFormat(), value); needsPadding = true; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/GATKReportDiffableReader.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/GATKReportDiffableReader.java index ef47ee33c..41b17cc7b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/GATKReportDiffableReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/GATKReportDiffableReader.java @@ -31,7 +31,6 @@ import org.broadinstitute.sting.gatk.report.GATKReportTable; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.util.Map; /** @@ -68,7 +67,8 @@ public class GATKReportDiffableReader implements DiffableReader { for ( GATKReportColumn column : table.getColumns().values() ) { DiffNode columnRoot = DiffNode.empty(column.getColumnName(), tableRoot); - columnRoot.add("Width", column.getColumnWidth()); + columnRoot.add("Width", column.getColumnFormat().getWidth()); + // NOTE: as the values are trimmed during parsing left/right alignment is not currently preserved columnRoot.add("Displayable", column.isDisplayable()); int n = 1; diff --git a/public/java/src/org/broadinstitute/sting/pipeline/Pipeline.java b/public/java/src/org/broadinstitute/sting/pipeline/Pipeline.java deleted file mode 100644 index e0e75c353..000000000 --- a/public/java/src/org/broadinstitute/sting/pipeline/Pipeline.java +++ /dev/null @@ -1,62 +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.pipeline; - -import java.util.ArrayList; -import java.util.List; - -/** - * Java bean for storing a list of samples for a pipeline. - * - * NOTE: This class is used in a very similar way to the classes in - * org.broadinstitute.sting.gatk.datasources.sample. - * - * Both store / load sample information from the file system as YAML. - * - * This package will likely be refactored to share common functionality - * with the other at a future date as requirements coalesce. - * - * - kshakir September 22, 2010 - */ -public class Pipeline { - private PipelineProject project = new PipelineProject(); - private List samples = new ArrayList(); - - public PipelineProject getProject() { - return project; - } - - public void setProject(PipelineProject project) { - this.project = project; - } - - public List getSamples() { - return samples; - } - - public void setSamples(List samples) { - this.samples = samples; - } -} diff --git a/public/java/src/org/broadinstitute/sting/pipeline/PipelineProject.java b/public/java/src/org/broadinstitute/sting/pipeline/PipelineProject.java deleted file mode 100644 index 8d33047bf..000000000 --- a/public/java/src/org/broadinstitute/sting/pipeline/PipelineProject.java +++ /dev/null @@ -1,115 +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.pipeline; - -import java.io.File; -import java.util.Map; -import java.util.TreeMap; - -/** - * Java bean defining the project for a pipeline. - */ -public class PipelineProject { - private String name; - private File referenceFile; - private File intervalList; - private File genotypeDbsnp; - private File evalDbsnp; - private File refseqTable; - private Map tags = new TreeMap(); - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public File getIntervalList() { - return intervalList; - } - - public void setIntervalList(File intervalList) { - this.intervalList = intervalList; - } - - public File getReferenceFile() { - return referenceFile; - } - - public void setReferenceFile(File referenceFile) { - this.referenceFile = referenceFile; - } - - public File getGenotypeDbsnp() { - return genotypeDbsnp; - } - - public void setGenotypeDbsnp(File genotypeDbsnp) { - this.genotypeDbsnp = genotypeDbsnp; - } - - public String getGenotypeDbsnpType() { - return getDbsnpType(genotypeDbsnp); - } - - public File getEvalDbsnp() { - return evalDbsnp; - } - - public void setEvalDbsnp(File evalDbsnp) { - this.evalDbsnp = evalDbsnp; - } - - public String getEvalDbsnpType() { - return getDbsnpType(evalDbsnp); - } - - public File getRefseqTable() { - return refseqTable; - } - - public void setRefseqTable(File refseqTable) { - this.refseqTable = refseqTable; - } - - public Map getTags() { - return tags; - } - - public void setTags(Map tags) { - this.tags = tags; - } - - private String getDbsnpType(File file) { - if (file == null) - return null; - else if (file.getName().toLowerCase().endsWith(".vcf")) - return "vcf"; - else - return "dbsnp"; - } -} diff --git a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java index 94c2d4c0b..6f0573b02 100644 --- a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java @@ -362,4 +362,27 @@ public class IOUtils { org.apache.commons.io.IOUtils.closeQuietly(outputStream); } } + + /** + * Returns a file throwing a UserException if the file cannot be read. + * @param path File path + * @return LineIterator + */ + public static LineIterator lineIterator(String path) { + return lineIterator(new File(path)); + } + + /** + * Returns a file throwing a UserException if the file cannot be read. + * @param file File + * @return LineIterator + */ + public static LineIterator lineIterator(File file) { + try { + return FileUtils.lineIterator(file); + } catch (IOException e) { + throw new UserException.CouldNotReadInputFile(file, e); + } + + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/yaml/FieldOrderComparator.java b/public/java/src/org/broadinstitute/sting/utils/yaml/FieldOrderComparator.java deleted file mode 100644 index 2a043466a..000000000 --- a/public/java/src/org/broadinstitute/sting/utils/yaml/FieldOrderComparator.java +++ /dev/null @@ -1,52 +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.yaml; - -import org.yaml.snakeyaml.introspector.Property; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -/** - * Orders properties based on the order of the fields in the Java Bean. - */ -class FieldOrderComparator implements Comparator { - private final List propertyOrder; - - public FieldOrderComparator(Class clazz) { - propertyOrder = new ArrayList(); - for (Field field : clazz.getDeclaredFields()) - propertyOrder.add(field.getName()); - } - - @Override - public int compare(Property one, Property two) { - Integer index1 = propertyOrder.indexOf(one.getName()); - Integer index2 = propertyOrder.indexOf(two.getName()); - return index1.compareTo(index2); - } -} diff --git a/public/java/src/org/broadinstitute/sting/utils/yaml/StingYamlRepresenter.java b/public/java/src/org/broadinstitute/sting/utils/yaml/StingYamlRepresenter.java deleted file mode 100644 index 157b1ce27..000000000 --- a/public/java/src/org/broadinstitute/sting/utils/yaml/StingYamlRepresenter.java +++ /dev/null @@ -1,88 +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.yaml; - -import org.yaml.snakeyaml.introspector.Property; -import org.yaml.snakeyaml.nodes.*; -import org.yaml.snakeyaml.representer.Represent; -import org.yaml.snakeyaml.representer.Representer; - -import java.beans.IntrospectionException; -import java.io.File; -import java.util.Set; -import java.util.TreeSet; - -/** - * A representer with Sting prefered settings. - * - Fields are ordered in the order of the class declaration, instead of alphabetically. - * - Empty maps and sequences are not output. - * - Files are converted to their absolute paths. - */ -public class StingYamlRepresenter extends Representer { - - public StingYamlRepresenter() { - super(); - this.representers.put(File.class, new RepresentFile()); - } - - @Override - protected Set getProperties(Class type) throws IntrospectionException { - TreeSet properties = new TreeSet(new FieldOrderComparator(type)); - properties.addAll(super.getProperties(type)); - return properties; - } - - @Override - protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, - Object propertyValue, Tag customTag) { - NodeTuple tuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); - Node valueNode = tuple.getValueNode(); - if (Tag.NULL.equals(valueNode.getTag())) { - return null;// skip 'null' values - } - if (valueNode instanceof CollectionNode) { - if (Tag.SEQ.equals(valueNode.getTag())) { - SequenceNode seq = (SequenceNode) valueNode; - if (seq.getValue().isEmpty()) { - return null;// skip empty lists - } - } - if (Tag.MAP.equals(valueNode.getTag())) { - MappingNode seq = (MappingNode) valueNode; - if (seq.getValue().isEmpty()) { - return null;// skip empty maps - } - } - } - return tuple; - } - - private class RepresentFile implements Represent { - @Override - public Node representData(Object o) { - return StingYamlRepresenter.this.representScalar(Tag.STR, ((File)o).getPath()); - } - } -} diff --git a/public/java/src/org/broadinstitute/sting/utils/yaml/YamlUtils.java b/public/java/src/org/broadinstitute/sting/utils/yaml/YamlUtils.java deleted file mode 100644 index 715c71efc..000000000 --- a/public/java/src/org/broadinstitute/sting/utils/yaml/YamlUtils.java +++ /dev/null @@ -1,107 +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.yaml; - -import org.broadinstitute.sting.utils.exceptions.UserException; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.nodes.Tag; -import org.yaml.snakeyaml.representer.Representer; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; - -/** - * A collection of utilities for operating on YAML. - * Uses the FLOW style of writing YAML, versus the BLOCK style. - * By default uses a representer that prunes empty lists and maps. - */ -public class YamlUtils { - private static Representer representer = new StingYamlRepresenter(); - private static DumperOptions options = new DumperOptions(); - - static { - options.setCanonical(false); - options.setExplicitRoot(Tag.MAP); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.FLOW); - options.setPrettyFlow(true); - } - - /** - * Serialize an object to the file system. - * @param o Object to serialize. - * @param file Path to write the serialized YAML. - */ - public static void dump(Object o, File file) { - dump(o, file, representer); - } - - /** - * Serialize an object to the file system. - * @param o Object to serialize. - * @param file Path to write the serialized YAML. - * @param representer Custom representer with rules on how to serialize YAML. - */ - public static void dump(Object o, File file, Representer representer) { - Constructor constructor = new Constructor(o.getClass()); - Yaml yaml = new Yaml(constructor, representer, options); - try { - yaml.dump(o, new FileWriter(file)); - } catch (IOException ioe) { - throw new UserException.CouldNotCreateOutputFile(file, ioe); - } - } - - /** - * Deserialize an object from the file system. - * @param clazz Clazz to deserialize. - * @param file Path to read the deserialized YAML. - * @return Object deserialized from the file system. - */ - public static T load(Class clazz, File file) { - return load(clazz, file, representer); - } - - /** - * Deserialize an object from the file system. - * @param clazz Clazz to deserialize. - * @param file Path to read the deserialized YAML. - * @param representer Custom representer with rules on how to deserialize YAML. - * @return Object deserialized from the file system. - */ - @SuppressWarnings("unchecked") - public static T load(Class clazz, File file, Representer representer) { - Constructor constructor = new Constructor(clazz); - Yaml yaml = new Yaml(constructor, representer, options); - try { - return (T) yaml.load(new FileReader(file)); - } catch (IOException ioe) { - throw new UserException.CouldNotReadInputFile(file, ioe); - } - } -} diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java index f99a105ae..87c03321a 100755 --- a/public/java/test/org/broadinstitute/sting/BaseTest.java +++ b/public/java/test/org/broadinstitute/sting/BaseTest.java @@ -78,7 +78,7 @@ public abstract class BaseTest { public static final String hg19Intervals = intervalsLocation + "whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.interval_list"; public static final String hg19Chr20Intervals = intervalsLocation + "whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.chr20.interval_list"; - public static final String networkTempDir = "/broad/shptmp/"; + public static final String networkTempDir = "/broad/shptmp/" + System.getProperty("user.name") + "/"; public static final File networkTempDirFile = new File(networkTempDir); public static final File testDirFile = new File("public/testdata/"); @@ -235,6 +235,7 @@ public abstract class BaseTest { */ public static File createNetworkTempFile(String name, String extension) { try { + FileUtils.forceMkdir(networkTempDirFile); File file = File.createTempFile(name, extension, networkTempDirFile); file.deleteOnExit(); return file; 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 f7be1d845..c9b81a9d3 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/report/GATKReportUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/report/GATKReportUnitTest.java @@ -26,6 +26,7 @@ package org.broadinstitute.sting.gatk.report; import org.broadinstitute.sting.BaseTest; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class GATKReportUnitTest extends BaseTest { @@ -45,4 +46,31 @@ public class GATKReportUnitTest extends BaseTest { Object validationReportPK = countVariants.getPrimaryKey("none.eval.none.known"); Assert.assertEquals(validationReport.get(validationReportPK, "sensitivity"), "NaN"); } + + @DataProvider(name = "rightAlignValues") + public Object[][] getRightAlignValues() { + return new Object[][] { + new Object[] {null, true}, + new Object[] {"null", true}, + new Object[] {"NA", true}, + new Object[] {"0", true}, + new Object[] {"0.0", true}, + new Object[] {"-0", true}, + new Object[] {"-0.0", true}, + new Object[] {String.valueOf(Long.MAX_VALUE), true}, + new Object[] {String.valueOf(Long.MIN_VALUE), true}, + new Object[] {String.valueOf(Float.MIN_NORMAL), true}, + new Object[] {String.valueOf(Double.MAX_VALUE), true}, + new Object[] {String.valueOf(Double.MIN_VALUE), true}, + new Object[] {String.valueOf(Double.POSITIVE_INFINITY), true}, + new Object[] {String.valueOf(Double.NEGATIVE_INFINITY), true}, + new Object[] {String.valueOf(Double.NaN), true}, + new Object[] {"hello", false} + }; + } + + @Test(dataProvider = "rightAlignValues") + public void testIsRightAlign(String value, boolean expected) { + Assert.assertEquals(GATKReportColumn.isRightAlign(value), expected, "right align of '" + value + "'"); + } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsIntegrationTest.java index c8a25c97b..9b79653c6 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsIntegrationTest.java @@ -50,8 +50,8 @@ public class DiffObjectsIntegrationTest extends WalkerTest { @DataProvider(name = "data") public Object[][] createData() { - new TestParams(testDir + "diffTestMaster.vcf", testDir + "diffTestTest.vcf", "ed377322c615abc7dceb97025076078d"); - new TestParams(testDir + "exampleBAM.bam", testDir + "exampleBAM.simple.bam", "02e46f5d2ebb3d49570850595b3f792e"); + new TestParams(testDir + "diffTestMaster.vcf", testDir + "diffTestTest.vcf", "da3dc85a0e35a9aade5520591891b4fa"); + new TestParams(testDir + "exampleBAM.bam", testDir + "exampleBAM.simple.bam", "7dc8200730313e6753237a696296fb73"); return TestParams.getTests(TestParams.class); } 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 403ecce78..1555b56d5 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("abe943d1aac120d7e75b9b9e5dac2399") + Arrays.asList("f909fd8374f663e983b9b3fda4cf5cf1") ); executeTest("testFunctionClassWithSnpeff", spec); } @@ -50,7 +50,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("5fd9624c7a35ffb79d0feb1e233fc757") + Arrays.asList("081fcaa532c7ba8f23da739389e6f7c3") ); executeTest("testStratifySamplesAndExcludeMonomorphicSites", spec); } @@ -70,7 +70,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("4a8765cd02d36e63f6d0f0c10a6c674b") + Arrays.asList("b3852f84d07c270b8a12874083c3e31b") ); executeTest("testFundamentalsCountVariantsSNPsandIndels", spec); } @@ -91,7 +91,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("4106ab8f742ad1c3138c29220151503c") + Arrays.asList("cf70468b5ebaec408419da69b0a7fcb9") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithNovelty", spec); } @@ -113,7 +113,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("6cee3a8d68307a118944f2df5401ac89") + Arrays.asList("5e3b8b85acfc41365c8208c23abf746b") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithNoveltyAndFilter", spec); } @@ -134,7 +134,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("af5dd27354d5dfd0d2fe03149af09b55") + Arrays.asList("ccdbc50d30ece6d0d3b199c397f03ed3") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithCpG", spec); } @@ -155,7 +155,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("062a231e203671e19aa9c6507710d762") + Arrays.asList("95c690d5af8ed51573eb2f0503dcd9c2") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithFunctionalClass", spec); } @@ -176,7 +176,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("75abdd2b17c0a5e04814b6969a3d4d7e") + Arrays.asList("8e8547eb38b34bec0095b0500fd9641d") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithDegeneracy", spec); } @@ -197,7 +197,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("bdbb5f8230a4a193058750c5e506c733") + Arrays.asList("158a4651a656aea7f84c79548f6fe519") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithSample", spec); } @@ -220,7 +220,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("f076120da22930294840fcc396f5f141") + Arrays.asList("76c8a0b28d2993644120f7afa5833ab2") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithJexlExpression", spec); } @@ -245,7 +245,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("69201f4a2a7a44b38805a4aeeb8830b6") + Arrays.asList("34682193f458b93b39efac00b4fc6723") ); executeTest("testFundamentalsCountVariantsSNPsandIndelsWithMultipleJexlExpressions", spec); } @@ -264,7 +264,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("c3bd3cb6cfb21a8c2b4d5f69104bf6c2") + Arrays.asList("52f6655f1532bcea24b402010d93ce73") ); 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("861f94e3237d62bd5bc00757319241f7")); + 1, Arrays.asList("6fa6e77f149de3d13c31d410a98043a0")); executeTestParallel("testSelect1", spec); } @@ -287,14 +287,14 @@ public class VariantEvalIntegrationTest extends WalkerTest { WalkerTestSpec spec = new WalkerTestSpec(cmdRoot + " -ST CpG --eval:VCF3 " + validationDataLocation + vcfFile + " --comp:VCF3 " + validationDataLocation + "GenotypeConcordanceComp.vcf -noEV -EV GenotypeConcordance -o %s", 1, - Arrays.asList("96f27163f16bb945f19c6623cd6db34e")); + Arrays.asList("9a56c20a7b9a554a7b530f2cb1dd776d")); executeTestParallel("testVEGenotypeConcordance" + vcfFile, spec); } @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("955c33365e017679047fabec0f14d5e0")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("aeff16bb43be03a2a7e5b9d0108b4999")); 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("fb7d989e44bd74c5376cb5732f9f3f64")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("9002023b8aa8fc2c9aac58b8a79bca1e")); 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("da5bcb305c5ef207ce175821efdbdefd")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("38ed9d216bd43f1cceceea24146fae38")); 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("fde839ece1442388f21a2f0b936756a8")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("453c6b1f7165913e8b1787e22bac1281")); 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("1efae6b3b88c752b771e0c8fae24464e")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("61052c19211e7eb61fbbb62db5e40b56")); executeTestParallel("testMultipleCompTracks",spec); } @Test public void testPerSampleAndSubsettedSampleHaveSameResults1() { - String md5 = "bc9bcabc3105e2515d9a2d41506d2de1"; + String md5 = "0edded1cd578db62fa296c99c34a909d"; WalkerTestSpec spec = new WalkerTestSpec( buildCommandLine( @@ -414,7 +414,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("e53546243250634fc03e83b4e61ec55f") + Arrays.asList("ee22604616b3e9fc48a6dcbbf73a056d") ); executeTest("testAlleleCountStrat", spec); } @@ -435,7 +435,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-o %s" ), 1, - Arrays.asList("c8086f0525bc13e666afeb670c2e13ae") + Arrays.asList("240369cd651c77e05e8a6659f4a6237e") ); executeTest("testIntervalStrat", spec); } 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 3a25bc5c1..16b6c97d0 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("1f7ed8c0f671dd227ab764624ef0d64c") + Arrays.asList("addf5f4596ddacef40808f6d3d281111") ); executeTest("testVCFStreamingChain", selectTestSpec); diff --git a/public/java/test/org/broadinstitute/sting/pipeline/PipelineUnitTest.java b/public/java/test/org/broadinstitute/sting/pipeline/PipelineUnitTest.java deleted file mode 100644 index 891356670..000000000 --- a/public/java/test/org/broadinstitute/sting/pipeline/PipelineUnitTest.java +++ /dev/null @@ -1,88 +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.pipeline; - -import org.broadinstitute.sting.pipeline.Pipeline; -import org.broadinstitute.sting.pipeline.PipelineSample; -import org.testng.Assert; -import org.broadinstitute.sting.utils.yaml.YamlUtils; - -import org.testng.annotations.Test; - -import java.io.File; -import java.util.Map; - -public class PipelineUnitTest { - @Test - public void testDumpAndLoad() throws Exception { - Pipeline pipeline = new Pipeline(); - - pipeline.getProject().setName("PRJ_NAME"); - pipeline.getProject().setReferenceFile(new File("my.fasta")); - pipeline.getProject().setGenotypeDbsnp(new File("my.vcf")); - pipeline.getProject().setEvalDbsnp(new File("my.dbsnp")); - pipeline.getProject().getTags().put("testProjectTag", "project value here"); - - PipelineSample sample = new PipelineSample(); - sample.setId("SMP_ID"); - sample.getBamFiles().put("recalibrated", new File("recalibrated.bam")); - sample.getBamFiles().put("cleaned", new File("/absolute/path/to/cleaned.bam")); - sample.getTags().put("testSampleTag", "sample value here"); - - pipeline.getSamples().add(sample); - - File file = File.createTempFile("testDumpAndLoad", ".yaml"); - YamlUtils.dump(pipeline, file); - Pipeline pipelineLoad = YamlUtils.load(Pipeline.class, file); - - Assert.assertEquals(pipelineLoad.getProject().getName(), pipeline.getProject().getName()); - Assert.assertEquals(pipeline.getProject().getReferenceFile(), pipelineLoad.getProject().getReferenceFile()); - Assert.assertEquals(pipeline.getProject().getIntervalList(), pipelineLoad.getProject().getIntervalList()); - Assert.assertEquals(pipeline.getProject().getGenotypeDbsnp(), pipelineLoad.getProject().getGenotypeDbsnp()); - Assert.assertEquals(pipeline.getProject().getGenotypeDbsnpType(), pipelineLoad.getProject().getGenotypeDbsnpType()); - Assert.assertEquals(pipeline.getProject().getEvalDbsnp(), pipelineLoad.getProject().getEvalDbsnp()); - Assert.assertEquals(pipeline.getProject().getEvalDbsnpType(), pipelineLoad.getProject().getEvalDbsnpType()); - - Assert.assertEquals(pipelineLoad.getProject().getTags().size(), pipeline.getProject().getTags().size()); - for (Map.Entry entry : pipeline.getProject().getTags().entrySet()) - Assert.assertEquals(pipeline.getProject().getTags().get(entry.getKey()), entry.getValue()); - - Assert.assertEquals(pipelineLoad.getSamples().size(), pipeline.getSamples().size()); - for (int i = 0; i < pipeline.getSamples().size(); i++) { - PipelineSample pipelineSample = pipeline.getSamples().get(i); - PipelineSample pipelineLoadSample = pipelineLoad.getSamples().get(i); - - Assert.assertEquals(pipelineLoadSample.getId(), pipelineSample.getId()); - - Assert.assertEquals(pipelineLoadSample.getBamFiles().size(), pipelineSample.getBamFiles().size()); - for (Map.Entry entry : pipelineSample.getBamFiles().entrySet()) - Assert.assertEquals(entry.getValue(), pipelineSample.getBamFiles().get(entry.getKey())); - - Assert.assertEquals(pipelineLoadSample.getTags().size(), pipelineSample.getTags().size()); - for (Map.Entry entry : pipelineSample.getTags().entrySet()) - Assert.assertEquals(pipelineSample.getTags().get(entry.getKey()), entry.getValue()); - } - } -} From 4001c22a11ae994008c135313e50c73787352bed Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Tue, 6 Dec 2011 10:10:38 -0500 Subject: [PATCH 219/380] Better file count / buffering variation in test suite. Parameterized read shard buffering. Misc cleanup. --- .../sting/gatk/ReadProperties.java | 12 --- .../gatk/datasources/reads/ReadShard.java | 11 ++- .../gatk/datasources/reads/SAMDataSource.java | 6 +- .../gatk/datasources/reads/SAMReaderID.java | 11 +++ .../gatk/iterators/BufferingReadIterator.java | 80 ------------------- .../reads/DownsamplerBenchmark.java | 1 - .../LocusIteratorByStateUnitTest.java | 1 - 7 files changed, 24 insertions(+), 98 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/iterators/BufferingReadIterator.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/ReadProperties.java b/public/java/src/org/broadinstitute/sting/gatk/ReadProperties.java index 93fa2d146..daa8ff60d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/ReadProperties.java +++ b/public/java/src/org/broadinstitute/sting/gatk/ReadProperties.java @@ -30,7 +30,6 @@ public class ReadProperties { private Collection readers = null; private SAMFileHeader header = null; private SAMFileReader.ValidationStringency validationStringency = SAMFileReader.ValidationStringency.STRICT; - private Integer readBufferSize = null; private DownsamplingMethod downsamplingMethod = null; private ValidationExclusion exclusionList = null; private Collection supplementalFilters = null; @@ -91,14 +90,6 @@ public class ReadProperties { return validationStringency; } - /** - * Gets a list of the total number of reads that the sharding system should buffer per BAM file. - * @return - */ - public Integer getReadBufferSize() { - return readBufferSize; - } - /** * Gets the method and parameters used when downsampling reads. * @return Downsample fraction. @@ -150,7 +141,6 @@ public class ReadProperties { * @param header sam file header. * @param useOriginalBaseQualities True if original base qualities should be used. * @param strictness Stringency of reads file parsing. - * @param readBufferSize Number of reads to hold in memory per BAM. * @param downsamplingMethod Method for downsampling reads at a given locus. * @param exclusionList what safety checks we're willing to let slide * @param supplementalFilters additional filters to dynamically apply. @@ -169,7 +159,6 @@ public class ReadProperties { SAMFileHeader header, boolean useOriginalBaseQualities, SAMFileReader.ValidationStringency strictness, - Integer readBufferSize, DownsamplingMethod downsamplingMethod, ValidationExclusion exclusionList, Collection supplementalFilters, @@ -181,7 +170,6 @@ public class ReadProperties { byte defaultBaseQualities) { this.readers = samFiles; this.header = header; - this.readBufferSize = readBufferSize; this.validationStringency = strictness; this.downsamplingMethod = downsamplingMethod == null ? DownsamplingMethod.NONE : downsamplingMethod; this.exclusionList = exclusionList == null ? new ValidationExclusion() : exclusionList; 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 5f40c0ea5..8d73b1b15 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 @@ -38,7 +38,7 @@ 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; + public static int MAX_READS = 10000; /** * The reads making up this shard. @@ -49,6 +49,15 @@ public class ReadShard extends Shard { super(parser, ShardType.READ, loci, readsDataSource, fileSpans, isUnmapped); } + /** + * Sets the maximum number of reads buffered in a read shard. Implemented as a weirdly static interface + * until we know what effect tuning this parameter has. + * @param bufferSize New maximum number + */ + static void setReadBufferSize(final int bufferSize) { + MAX_READS = bufferSize; + } + /** * Returns true if this shard is meant to buffer reads, rather * than just holding pointers to their locations. 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 0ace6fde2..2b163ecbd 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 @@ -250,6 +250,9 @@ public class SAMDataSource { dispatcher = null; validationStringency = strictness; + if(readBufferSize != null) + ReadShard.setReadBufferSize(readBufferSize); + for (SAMReaderID readerID : samFiles) { if (!readerID.samFile.canRead()) throw new UserException.CouldNotReadInputFile(readerID.samFile,"file is not present or user does not have appropriate permissions. " + @@ -293,7 +296,6 @@ public class SAMDataSource { mergedHeader, useOriginalBaseQualities, strictness, - readBufferSize, downsamplingMethod, exclusionList, supplementalFilters, @@ -551,8 +553,6 @@ public class SAMDataSource { 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().size() > 0) iterator = new IntervalOverlapFilteringIterator(iterator,shard.getGenomeLocs()); mergingIterator.addIterator(readers.getReader(id),iterator); diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderID.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderID.java index c84db7770..5eba5d84f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderID.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderID.java @@ -67,6 +67,7 @@ public class SAMReaderID implements Comparable { * @param other The other identifier. * @return True iff the two readers point to the same file. */ + @Override public boolean equals(Object other) { if(other == null) return false; if(!(other instanceof SAMReaderID)) return false; @@ -79,10 +80,20 @@ public class SAMReaderID implements Comparable { * Generate a hash code for this object. * @return A hash code, based solely on the file name at this point. */ + @Override public int hashCode() { return samFile.hashCode(); } + /** + * Best string representation for a SAM file reader is the path of the source file. + */ + @Override + public String toString() { + return getSamFilePath(); + } + + @Override public int compareTo(Object other) { return this.samFile.getAbsolutePath().compareTo(((SAMReaderID)other).samFile.getAbsolutePath()); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/iterators/BufferingReadIterator.java b/public/java/src/org/broadinstitute/sting/gatk/iterators/BufferingReadIterator.java deleted file mode 100644 index 7eaf4be41..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/BufferingReadIterator.java +++ /dev/null @@ -1,80 +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.iterators; - -import net.sf.samtools.SAMRecord; -import net.sf.samtools.util.CloseableIterator; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -import java.util.LinkedList; -import java.util.NoSuchElementException; -import java.util.Queue; - -/** - * Buffers access to a large stream of reads, replenishing the buffer only when the reads - * - * @author mhanna - * @version 0.1 - */ -public class BufferingReadIterator implements CloseableIterator { - private final CloseableIterator wrappedIterator; - private final Queue buffer; - private final int bufferSize; - - public BufferingReadIterator(final CloseableIterator readIterator, final int bufferSize) { - this.wrappedIterator = readIterator; - this.buffer = new LinkedList(); - this.bufferSize = bufferSize; - } - - public boolean hasNext() { - assureBufferFull(); - return !buffer.isEmpty(); - } - - public SAMRecord next() { - assureBufferFull(); - if(!hasNext()) throw new NoSuchElementException("No next element available"); - return buffer.remove(); - } - - public void close() { - wrappedIterator.close(); - } - - public void remove() { - throw new ReviewedStingException("Unable to remove from a BufferingReadIterator"); - } - - /** - * If the buffer is empty but there are more elements in the iterator, - */ - private void assureBufferFull() { - if(!buffer.isEmpty()) - return; - while(buffer.size() < bufferSize && wrappedIterator.hasNext()) - buffer.add(wrappedIterator.next()); - } -} diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/DownsamplerBenchmark.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/DownsamplerBenchmark.java index 5ee373e4f..5da8cebf4 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/DownsamplerBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/DownsamplerBenchmark.java @@ -72,7 +72,6 @@ public class DownsamplerBenchmark extends ReadProcessingBenchmark { reader.getFileHeader(), false, SAMFileReader.ValidationStringency.SILENT, - 0, downsampling.create(), new ValidationExclusion(Collections.singletonList(ValidationExclusion.TYPE.ALL)), Collections.emptyList(), diff --git a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java index c9727d904..4011594f3 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java @@ -301,7 +301,6 @@ public class LocusIteratorByStateUnitTest extends BaseTest { false, SAMFileReader.ValidationStringency.STRICT, null, - null, new ValidationExclusion(), Collections.emptyList(), false, From b4b7ae1bd94aa55759c3e7768351abb1252422eb Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Tue, 6 Dec 2011 10:32:19 -0500 Subject: [PATCH 220/380] Revved Picard to incorporate tfennell's AsyncSAMFileWriter. Removed DbSnpFileGenerator and related files as they were removed from PPP r2063 by ktibbett. --- public/packages/PicardPrivate.xml | 3 --- .../picard-private-parts-2068.jar | Bin 40956 -> 0 bytes .../picard-private-parts-2125.jar | Bin 0 -> 327508 bytes ...2068.xml => picard-private-parts-2125.xml} | 2 +- .../repository/net.sf/picard-1.55.985.xml | 3 --- ...card-1.55.985.jar => picard-1.57.1030.jar} | Bin 1151957 -> 1194798 bytes .../repository/net.sf/picard-1.57.1030.xml | 3 +++ settings/repository/net.sf/sam-1.55.985.xml | 3 --- .../{sam-1.55.985.jar => sam-1.57.1030.jar} | Bin 555881 -> 566967 bytes settings/repository/net.sf/sam-1.57.1030.xml | 3 +++ 10 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 settings/repository/edu.mit.broad/picard-private-parts-2068.jar create mode 100644 settings/repository/edu.mit.broad/picard-private-parts-2125.jar rename settings/repository/edu.mit.broad/{picard-private-parts-2068.xml => picard-private-parts-2125.xml} (64%) delete mode 100644 settings/repository/net.sf/picard-1.55.985.xml rename settings/repository/net.sf/{picard-1.55.985.jar => picard-1.57.1030.jar} (76%) create mode 100644 settings/repository/net.sf/picard-1.57.1030.xml delete mode 100644 settings/repository/net.sf/sam-1.55.985.xml rename settings/repository/net.sf/{sam-1.55.985.jar => sam-1.57.1030.jar} (84%) create mode 100644 settings/repository/net.sf/sam-1.57.1030.xml diff --git a/public/packages/PicardPrivate.xml b/public/packages/PicardPrivate.xml index 581c47979..a800294d6 100644 --- a/public/packages/PicardPrivate.xml +++ b/public/packages/PicardPrivate.xml @@ -12,15 +12,12 @@ - - - diff --git a/settings/repository/edu.mit.broad/picard-private-parts-2068.jar b/settings/repository/edu.mit.broad/picard-private-parts-2068.jar deleted file mode 100644 index bb6805d8de75968ab03002e950d4a428d3ce1f48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40956 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 BZGQj& diff --git a/settings/repository/edu.mit.broad/picard-private-parts-2125.jar b/settings/repository/edu.mit.broad/picard-private-parts-2125.jar new file mode 100644 index 0000000000000000000000000000000000000000..70c58957224875dd6ab582017fe4310d55c8388c GIT binary patch literal 327508 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

SrMbUL3o;F z7a>HYTYqq0IiId-%)0UrXyLmWDEJFvtG4uKs=iMnhnHW@t)i0%bVbchPA4FG%Kjl& zyGt$Zf;amAgMShEZ%D)TPUim|90vmW*B0N!u7Up1!qCCc*y8_>=!^8f8~$s-?SEwx zWBj}3MqZW#tHif_TJAcMHf)0lUqlL!`dfKL`e*NKWr?I~fUQ>3@=(U$~W&K4wiFp%X z>j>PchVB!*3A3nKSh_P!2FrCN@R5qA&-c~ z$_`9 z4#U9nya0o7=}p3(rV_w|5sYI+teB*5hXVgL*d7DI-s?NGIZ^AsoUOtDN9vnxZg6M& zW%pdu{{p^Pyd2g0@_SzNPiy8*^jNW?{Ee+yKTsB=3to^s!LhYt^>`tz+z}}KP;N(w zTldH6g0*7Xl>z4IE!(HRHR01&ue>X}@Qt+Sf%kJ&UO96shN;YNKaW!+&@&>P>)qNv z7uR(2_QB?VDsWo=Y#U#5p2mY|)x0`$;FQP(Ra^`{Sm=6AW36Uo#xTUTd=b*nRzMqg zm*29GRj*t5Hy4ab$De%Hq~<(2@{4tprq>to+K)%==bS$mG*Yd?68j7HhDj_wq_NBT z6hO~#u4F?5j`22R+&w&bG_y1?2=7D3LD1aAE>7!--Wy#29{w78^$7ZAIYDz

J5 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 z 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ˉM`ko~LH9NQ831eJnuqC#pk-w{h^58A5N6inZ% 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�gm&?u{)U5UL{J&Mr63V0j|eyaM-M7 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≈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
vwPB5_whIVfPK$+e#A#x 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
ZzYP-ByQ*PZoIe6u7_>P?1xmRWQ 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 literal 0 HcmV?d00001 diff --git a/settings/repository/edu.mit.broad/picard-private-parts-2068.xml b/settings/repository/edu.mit.broad/picard-private-parts-2125.xml similarity index 64% rename from settings/repository/edu.mit.broad/picard-private-parts-2068.xml rename to settings/repository/edu.mit.broad/picard-private-parts-2125.xml index 1ce10c193..0758c94cd 100644 --- a/settings/repository/edu.mit.broad/picard-private-parts-2068.xml +++ b/settings/repository/edu.mit.broad/picard-private-parts-2125.xml @@ -1,3 +1,3 @@ - + diff --git a/settings/repository/net.sf/picard-1.55.985.xml b/settings/repository/net.sf/picard-1.55.985.xml deleted file mode 100644 index e74b12604..000000000 --- a/settings/repository/net.sf/picard-1.55.985.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/settings/repository/net.sf/picard-1.55.985.jar b/settings/repository/net.sf/picard-1.57.1030.jar similarity index 76% rename from settings/repository/net.sf/picard-1.55.985.jar rename to settings/repository/net.sf/picard-1.57.1030.jar index a8ceaa878215be362162bcd8886f682535d11158..0ce2cb0dc7d1bba0989393673d3b17f5d0d24269 100644 GIT binary patch delta 165740 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 z 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 d$L(JmCu_e^$C>m@oUf#g$1gGavOT-K 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 + + diff --git a/settings/repository/net.sf/sam-1.55.985.xml b/settings/repository/net.sf/sam-1.55.985.xml deleted file mode 100644 index 415063a62..000000000 --- a/settings/repository/net.sf/sam-1.55.985.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/settings/repository/net.sf/sam-1.55.985.jar b/settings/repository/net.sf/sam-1.57.1030.jar similarity index 84% rename from settings/repository/net.sf/sam-1.55.985.jar rename to settings/repository/net.sf/sam-1.57.1030.jar index ff3d9a9451329d6bc8029b7a990fa79388f72ebe..2404c9986f1b5e4f858617ad8d8968b76fec0dc0 100644 GIT binary patch delta 48054 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 cmDtP37BGyfMu-7>HENmq%#uD&KgC6=w- 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 XKI;)#uWxc`a=C`@=G*3C_+0)A6pbxA diff --git a/settings/repository/net.sf/sam-1.57.1030.xml b/settings/repository/net.sf/sam-1.57.1030.xml new file mode 100644 index 000000000..a5dfe0718 --- /dev/null +++ b/settings/repository/net.sf/sam-1.57.1030.xml @@ -0,0 +1,3 @@ + + + From 79d18dc0784bc570b197b31ae5c7b31f86e40985 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 6 Dec 2011 16:17:18 -0500 Subject: [PATCH 221/380] Fixing indexing bug on the ACsets. Added unit tests for the Exact model code. --- .../genotyper/ExactAFCalculationModel.java | 12 ++- .../ExactAFCalculationModelUnitTest.java | 102 ++++++++++++++++++ .../UnifiedGenotyperIntegrationTest.java | 4 +- 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java 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 d801885c6..91299c902 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 @@ -263,7 +263,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // to minimize memory consumption, we know we can delete any sets in this list because no further sets will depend on them final ArrayList dependentACsetsToDelete = new ArrayList(); - // index used to represent this set in the global hashmap: (numSamples^0 * allele_1) + (numSamples^1 * allele_2) + (numSamples^2 * allele_3) + ... + // index used to represent this set in the global hashmap: (numChr^0 * allele_1) + (numChr^1 * allele_2) + (numChr^2 * allele_3) + ... private int index = -1; public ExactACset(final int size, final int[] ACcounts) { @@ -273,7 +273,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public int getIndex() { if ( index == -1 ) - index = generateIndex(ACcounts, log10Likelihoods.length); + index = generateIndex(ACcounts, 2 * log10Likelihoods.length - 1); return index; } @@ -350,7 +350,13 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // can we abort early because the log10Likelihoods are so small? if ( log10LofK < maxLog10L - MAX_LOG10_ERROR_TO_STOP_EARLY ) { - if ( DEBUG ) System.out.printf(" *** breaking early ks=%d log10L=%.2f maxLog10L=%.2f%n", set.index, log10LofK, maxLog10L); + if ( DEBUG ) + System.out.printf(" *** breaking early ks=%d log10L=%.2f maxLog10L=%.2f%n", set.index, log10LofK, maxLog10L); + + // no reason to keep this data around because nothing depends on it + if ( !preserveData ) + indexesToACset.put(set.getIndex(), null); + return log10LofK; } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java new file mode 100644 index 000000000..00cfff4b3 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java @@ -0,0 +1,102 @@ +package org.broadinstitute.sting.gatk.walkers.genotyper; + +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.variantcontext.Allele; +import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.GenotypesContext; +import org.testng.Assert; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Arrays; + + +public class ExactAFCalculationModelUnitTest extends BaseTest { + + static double[] AA1, AB1, BB1; + static double[] AA2, AB2, AC2, BB2, BC2, CC2; + static final int numSamples = 3; + static double[][] priors = new double[2][2*numSamples+1]; // flat priors + + @BeforeSuite + public void before() { + AA1 = new double[]{0.0, -20.0, -20.0}; + AB1 = new double[]{-20.0, 0.0, -20.0}; + BB1 = new double[]{-20.0, -20.0, 0.0}; + AA2 = new double[]{0.0, -20.0, -20.0, -20.0, -20.0, -20.0}; + AB2 = new double[]{-20.0, 0.0, -20.0, -20.0, -20.0, -20.0}; + AC2 = new double[]{-20.0, -20.0, 0.0, -20.0, -20.0, -20.0}; + BB2 = new double[]{-20.0, -20.0, -20.0, 0.0, -20.0, -20.0}; + BC2 = new double[]{-20.0, -20.0, -20.0, -20.0, 0.0, -20.0}; + CC2 = new double[]{-20.0, -20.0, -20.0, -20.0, -20.0, 0.0}; + } + + private class GetGLsTest extends TestDataProvider { + GenotypesContext GLs; + int numAltAlleles; + String name; + + private GetGLsTest(String name, int numAltAlleles, Genotype... arg) { + super(GetGLsTest.class, name); + GLs = GenotypesContext.create(arg); + this.name = name; + this.numAltAlleles = numAltAlleles; + } + + public String toString() { + return String.format("%s input=%s", super.toString(), GLs); + } + } + + private static Genotype createGenotype(String name, double[] gls) { + return new Genotype(name, Arrays.asList(Allele.NO_CALL, Allele.NO_CALL), Genotype.NO_LOG10_PERROR, gls); + } + + @DataProvider(name = "getGLs") + public Object[][] createGLsData() { + + // bi-allelic case + new GetGLsTest("B0", 1, createGenotype("AA1", AA1), createGenotype("AA2", AA1), createGenotype("AA3", AA1)); + new GetGLsTest("B1", 1, createGenotype("AA1", AA1), createGenotype("AA2", AA1), createGenotype("AB", AB1)); + new GetGLsTest("B2", 1, createGenotype("AA1", AA1), createGenotype("BB", BB1), createGenotype("AA2", AA1)); + new GetGLsTest("B3a", 1, createGenotype("AB", AB1), createGenotype("AA", AA1), createGenotype("BB", BB1)); + new GetGLsTest("B3b", 1, createGenotype("AB1", AB1), createGenotype("AB2", AB1), createGenotype("AB3", AB1)); + new GetGLsTest("B4", 1, createGenotype("BB1", BB1), createGenotype("BB2", BB1), createGenotype("AA", AA1)); + new GetGLsTest("B5", 1, createGenotype("BB1", BB1), createGenotype("AB", AB1), createGenotype("BB2", BB1)); + new GetGLsTest("B6", 1, createGenotype("BB1", BB1), createGenotype("BB2", BB1), createGenotype("BB3", BB1)); + + // tri-allelic case + new GetGLsTest("B1C0", 2, createGenotype("AA1", AA2), createGenotype("AA2", AA2), createGenotype("AB", AB2)); + new GetGLsTest("B0C1", 2, createGenotype("AA1", AA2), createGenotype("AA2", AA2), createGenotype("AC", AC2)); + new GetGLsTest("B1C1a", 2, createGenotype("AA", AA2), createGenotype("AB", AB2), createGenotype("AC", AC2)); + new GetGLsTest("B1C1b", 2, createGenotype("AA1", AA2), createGenotype("AA2", AA2), createGenotype("BC", BC2)); + new GetGLsTest("B2C1", 2, createGenotype("AB1", AB2), createGenotype("AB2", AB2), createGenotype("AC", AC2)); + new GetGLsTest("B3C2a", 2, createGenotype("AB", AB2), createGenotype("BC1", BC2), createGenotype("BC2", BC2)); + new GetGLsTest("B3C2b", 2, createGenotype("AB", AB2), createGenotype("BB", BB2), createGenotype("CC", CC2)); + + return GetGLsTest.getTests(GetGLsTest.class); + } + + + @Test(dataProvider = "getGLs") + public void testGLs(GetGLsTest cfg) { + + final double[][] log10AlleleFrequencyPosteriors = new double[2][2*numSamples+1]; + for ( int i = 0; i < 2; i++ ) { + for ( int j = 0; j < 2*numSamples+1; j++ ) { + log10AlleleFrequencyPosteriors[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; + } + } + + ExactAFCalculationModel.linearExactMultiAllelic(cfg.GLs, cfg.numAltAlleles, priors, log10AlleleFrequencyPosteriors, false); + + int nameIndex = 1; + for ( int allele = 0; allele < cfg.numAltAlleles; allele++, nameIndex+=2 ) { + int expectedAlleleCount = Integer.valueOf(cfg.name.substring(nameIndex, nameIndex+1)); + int calculatedAlleleCount = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors[allele]); + Assert.assertEquals(calculatedAlleleCount, expectedAlleleCount); + } + } +} 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 11e086db8..3275fc797 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 @@ -284,7 +284,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithIndelAllelesPassedIn3() { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( - baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2.20101123.indels.sites.vcf -I " + validationDataLocation + + baseCommandIndels + " --multiallelic --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("f93f8a35b47bcf96594ada55e2312c73")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); @@ -293,7 +293,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testWithIndelAllelesPassedIn4() { 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 + + baseCommandIndelsb37 + " --multiallelic --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("9be28cb208d8b0314d2bc2696e2fd8d4")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); From c9b2cd8ba52a25b2d8e12cdfe5f8871f8ff47e11 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Tue, 6 Dec 2011 18:05:17 -0500 Subject: [PATCH 222/380] Fix for chartl's stale null representation issue. --- .../datasources/reads/ReadShardBalancer.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) 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 index fa8a7d454..311c7874f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java @@ -89,11 +89,10 @@ public class ReadShardBalancer extends ShardBalancer { for(SAMReaderID id: shardPosition.keySet()) { SAMFileSpan fileSpan = new GATKBAMFileSpan(shardPosition.get(id).removeContentsBefore(position.get(id))); - if(!fileSpan.isEmpty()) - selectedReaders.put(id,fileSpan); + selectedReaders.put(id,fileSpan); } - if(selectedReaders.size() > 0) { + if(!isEmpty(selectedReaders)) { Shard shard = new ReadShard(parser,readsDataSource,selectedReaders,currentFilePointer.locations,currentFilePointer.isRegionUnmapped); readsDataSource.fillShard(shard); @@ -109,6 +108,19 @@ public class ReadShardBalancer extends ShardBalancer { position = readsDataSource.getCurrentPosition(); } + + /** + * Detects whether the list of file spans contain any read data. + * @param selectedSpans Mapping of readers to file spans. + * @return True if file spans are completely empty; false otherwise. + */ + private boolean isEmpty(Map selectedSpans) { + for(SAMFileSpan fileSpan: selectedSpans.values()) { + if(!fileSpan.isEmpty()) + return false; + } + return true; + } }; } From 6bf18899dffa4a601d5a5810a783b390fbab0733 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Dec 2011 09:02:49 -0500 Subject: [PATCH 223/380] Fix for variant summary -- now treats all 50 bp deletions or insertions as CNVs --- .../gatk/walkers/varianteval/evaluators/VariantSummary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index b74af9f91..1e33ccc21 100644 --- 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 @@ -184,7 +184,7 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { return Type.SNP; case INDEL: for ( int l : vc.getIndelLengths() ) - if ( l > MAX_INDEL_LENGTH ) + if ( Math.abs(l) > MAX_INDEL_LENGTH ) return Type.CNV; return Type.INDEL; case SYMBOLIC: From e17a1923fb0dfeffe73e78a660de4811652e33b3 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Dec 2011 09:24:47 -0500 Subject: [PATCH 224/380] Plots runtimes by analysis name and exechosts Useful to understand the performance of analysis jobs by hosts, and to debug problematic nodes --- .../sting/queue/util/queueJobReport.R | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R index 866766c2c..7bfdd4d84 100644 --- a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R +++ b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R @@ -12,7 +12,7 @@ if ( onCMDLine ) { inputFileName = args[1] outputPDF = args[2] } else { - inputFileName = "~/Desktop/broadLocal/GATK/unstable/wgs.jobreport.txt" + inputFileName = "Q-8271@gsa2.jobreport.txt" #inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/Q-25718@node1149.jobreport.txt" #inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/rodPerformanceGoals/history/report.082711.txt" outputPDF = NA @@ -149,6 +149,35 @@ convertUnits <- function(gatkReportData) { lapply(gatkReportData, convertGroup) } +# +# Plots runtimes by analysis name and exechosts +# +# Useful to understand the performance of analysis jobs by hosts, +# and to debug problematic nodes +# +plotTimeByHost <- function(gatkReportData) { + fields = c("analysisName", "exechosts", "runtime") + + runtimes = data.frame() + for ( report in gatkReportData ) { + runtimes = rbind(runtimes, report[,fields]) + } + + plotMe <- function(name, vis) { + p = ggplot(data=runtimes, aes(x=exechosts, y=runtime, group=exechosts, color=exechosts)) + p = p + facet_grid(analysisName ~ .) + p = p + vis() + p = p + xlab("Job execution host") + p = p + opts(title = paste(name, "of job runtimes by analysis name and execution host")) + p = p + ylab(paste("Distribution of runtimes", RUNTIME_UNITS)) + p = p + opts(axis.text.x=theme_text(angle=45, hjust=1, vjust=1)) + print(p) + } + + plotMe("Boxplot", geom_boxplot) + plotMe("Jittered points", geom_jitter) +} + # read the table gatkReportData <- gsa.read.gatkreport(inputFileName) @@ -162,6 +191,7 @@ if ( ! is.na(outputPDF) ) { plotJobsGantt(gatkReportData, T, F) plotJobsGantt(gatkReportData, F, F) plotProgressByTime(gatkReportData) +plotTimeByHost(gatkReportData) for ( group in gatkReportData ) { plotGroup(group) } From 15533e08dfdb28002c5fe9cb0925cd3d4242268c Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Wed, 7 Dec 2011 11:55:42 -0500 Subject: [PATCH 226/380] Fixed issue with RODWalker parallelization. Turns out that someone previously upped the declared size of a ROD shard to 100M bases, making each ROD shard larger than the size of chr20. Why didn't we see this in Stable? Because the ShardStrategy/ShardStrategyFactory mechanism was dutifully ignoring the shard size specification. When I rolled the ShardStrategy/ShardStrategyFactory mechanics back into the DataSources as part of the async I/O project, I inadvertently reenabled this specifier. --- .../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 7cc8e9e29..d37116215 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -469,7 +469,7 @@ public class GenomeAnalysisEngine { throw new ReviewedStingException("Unable to determine walker type for walker " + walker.getClass().getName()); } else { - final int SHARD_SIZE = walker instanceof RodWalker ? 100000000 : 100000; + final int SHARD_SIZE = walker instanceof RodWalker ? 1000000 : 100000; if(intervals == null) return referenceDataSource.createShardsOverEntireReference(readsDataSource,genomeLocParser,SHARD_SIZE); else From 4055877708a4152127418cd73c43aed9cb3c782c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Dec 2011 12:07:54 -0500 Subject: [PATCH 227/380] Prints 0.0 TiTv not NaN when there are no variants -- Updated md5 --- .../walkers/varianteval/evaluators/VariantSummary.java | 3 ++- .../walkers/varianteval/VariantEvalIntegrationTest.java | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) 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 index 1e33ccc21..a271d3c35 100644 --- 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 @@ -144,7 +144,8 @@ public class VariantSummary extends VariantEvaluator implements StandardEval { n++; } } - return sum / (1.0 * n); + + return n > 0 ? sum / (1.0 * n) : 0.0; } } 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 1555b56d5..17a813a70 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 @@ -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("6fa6e77f149de3d13c31d410a98043a0")); + 1, Arrays.asList("4f60acc8a4b21c4b4ccb51ad9071449c")); 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("aeff16bb43be03a2a7e5b9d0108b4999")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("fa13eb59892892c07711c6ffe31bf870")); executeTestParallel("testCompVsEvalAC",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("38ed9d216bd43f1cceceea24146fae38")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("190e1a171132832bf92fbca56a9c40bb")); 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("453c6b1f7165913e8b1787e22bac1281")); + WalkerTestSpec spec = new WalkerTestSpec(extraArgs,1,Arrays.asList("08586d443fdcf3b7f63b8f9e3a943c62")); executeTestParallel("testMultipleEvalTracksWithoutGenotypes",spec); } From 50c4436f90d4df204b1111940a9b8964ec03d291 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Dec 2011 14:09:32 -0500 Subject: [PATCH 229/380] scales=free shows variance within analysis better --- .../org/broadinstitute/sting/queue/util/queueJobReport.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R index 7bfdd4d84..aff783b8e 100644 --- a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R +++ b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R @@ -165,7 +165,7 @@ plotTimeByHost <- function(gatkReportData) { plotMe <- function(name, vis) { p = ggplot(data=runtimes, aes(x=exechosts, y=runtime, group=exechosts, color=exechosts)) - p = p + facet_grid(analysisName ~ .) + p = p + facet_grid(analysisName ~ ., scale="free") p = p + vis() p = p + xlab("Job execution host") p = p + opts(title = paste(name, "of job runtimes by analysis name and execution host")) From 7750bafb12ada511e9c764668d013f09d2b92893 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 8 Dec 2011 13:50:50 -0500 Subject: [PATCH 230/380] Fixed bug where last dependent set index wasn't properly being transferred for sites with many alleles. Adding debugging output. --- .../genotyper/ExactAFCalculationModel.java | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 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 91299c902..9c3b499b4 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 @@ -36,10 +36,10 @@ import java.io.PrintStream; import java.util.*; public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { - // - // code for testing purposes - // + private final static boolean DEBUG = false; + //private final static boolean DEBUG = true; + private final static double MAX_LOG10_ERROR_TO_STOP_EARLY = 6; // we want the calculation to be accurate to 1 / 10^6 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. @@ -291,6 +291,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { sum += count; return sum; } + + public boolean equals(Object obj) { + return (obj instanceof ExactACset) ? getIndex() == ((ExactACset)obj).getIndex() : false; + } } public static void linearExactMultiAllelic(final GenotypesContext GLs, @@ -337,13 +341,19 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final double[][] log10AlleleFrequencyPriors, final double[][] log10AlleleFrequencyPosteriors) { + if ( DEBUG ) + System.out.printf(" *** computing LofK for set=%d%n", set.getIndex()); + // compute the log10Likelihoods computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); // clean up memory if ( !preserveData ) { - for ( int index : set.dependentACsetsToDelete ) + for ( int index : set.dependentACsetsToDelete ) { indexesToACset.put(index, null); + if ( DEBUG ) + System.out.printf(" *** removing used set=%d after seeing final dependent set=%d%n", index, set.getIndex()); + } } final double log10LofK = set.log10Likelihoods[set.log10Likelihoods.length-1]; @@ -351,7 +361,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // can we abort early because the log10Likelihoods are so small? if ( log10LofK < maxLog10L - MAX_LOG10_ERROR_TO_STOP_EARLY ) { if ( DEBUG ) - System.out.printf(" *** breaking early ks=%d log10L=%.2f maxLog10L=%.2f%n", set.index, log10LofK, maxLog10L); + System.out.printf(" *** breaking early set=%d log10L=%.2f maxLog10L=%.2f%n", set.getIndex(), log10LofK, maxLog10L); // no reason to keep this data around because nothing depends on it if ( !preserveData ) @@ -386,20 +396,27 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final int[] ACcountsClone = set.ACcounts.clone(); ACcountsClone[allele_i]++; ACcountsClone[allele_j]++; - lastSet = updateACset(ACcountsClone, numChr,set.getIndex(), ++PLindex , ACqueue, indexesToACset); + lastSet = updateACset(ACcountsClone, numChr, set.getIndex(), ++PLindex , ACqueue, indexesToACset); } } } - if ( lastSet == null ) - throw new ReviewedStingException("No new AC sets were added or updated but the AC still hasn't reached 2N"); - lastSet.dependentACsetsToDelete.add(set.index); + // if the last dependent set was not at the back of the queue (i.e. not just added), then we need to iterate + // over all the dependent sets to find the last one in the queue (otherwise it will be cleaned up too early) + if ( !preserveData && lastSet == null ) { + if ( DEBUG ) + System.out.printf(" *** iterating over dependent sets for set=%d%n", set.getIndex()); + lastSet = determineLastDependentSetInQueue(set.getIndex(), ACqueue); + } + if ( lastSet != null ) + lastSet.dependentACsetsToDelete.add(set.index); return log10LofK; } // adds the ExactACset represented by the ACcounts to the ACqueue if not already there (creating it if needed) and // also adds it as a dependency to the given callingSetIndex. + // returns the ExactACset if that set was not already in the queue and null otherwise. private static ExactACset updateACset(final int[] ACcounts, final int numChr, final int callingSetIndex, @@ -407,15 +424,26 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final Queue ACqueue, final HashMap indexesToACset) { final int index = ExactACset.generateIndex(ACcounts, numChr+1); + boolean wasInQueue = true; if ( !indexesToACset.containsKey(index) ) { ExactACset set = new ExactACset(numChr/2 +1, ACcounts); indexesToACset.put(index, set); ACqueue.add(set); + wasInQueue = false; } // add the given dependency to the set final ExactACset set = indexesToACset.get(index); set.ACsetIndexToPLIndex.put(callingSetIndex, PLsetIndex); + return wasInQueue ? null : set; + } + + private static ExactACset determineLastDependentSetInQueue(final int callingSetIndex, final Queue ACqueue) { + ExactACset set = null; + for ( ExactACset queued : ACqueue ) { + if ( queued.dependentACsetsToDelete.contains(callingSetIndex) ) + set = queued; + } return set; } @@ -454,9 +482,12 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // deal with the other possible conformations now if ( totalK <= 2*j ) { // skip impossible conformations int conformationIndex = 1; - for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) + for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) { + if ( DEBUG ) + System.out.printf(" *** evaluating set=%d which depends on set=%d%n", set.getIndex(), mapping.getKey()); log10ConformationLikelihoods[conformationIndex++] = determineCoefficient(mapping.getValue(), j, set.ACcounts, totalK) + indexesToACset.get(mapping.getKey()).log10Likelihoods[j-1] + gl[mapping.getValue()]; + } } final double log10Max = approximateLog10SumLog10(log10ConformationLikelihoods); From 4aebe99445c00c617be2f1f6a6551769a398fdf7 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 8 Dec 2011 15:31:02 -0500 Subject: [PATCH 231/380] Need to use longs for the set index (because we can run out of ints when there are too many alternate alleles). Integration tests now use the multiallelic implementation. --- .../genotyper/ExactAFCalculationModel.java | 33 +++++++++---------- .../UnifiedGenotyperIntegrationTest.java | 12 +++---- 2 files changed, 22 insertions(+), 23 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 9c3b499b4..1381f48ec 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 @@ -38,7 +38,6 @@ import java.util.*; public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { private final static boolean DEBUG = false; - //private final static boolean DEBUG = true; private final static double MAX_LOG10_ERROR_TO_STOP_EARLY = 6; // we want the calculation to be accurate to 1 / 10^6 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. @@ -258,27 +257,27 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // mapping of column index for those columns upon which this one depends to the index into the PLs which is used as the transition to this column; // for example, in the biallelic case, the transition from k=0 to k=1 would be AB while the transition to k=2 would be BB. - final HashMap ACsetIndexToPLIndex = new HashMap(); + final HashMap ACsetIndexToPLIndex = new HashMap(); // to minimize memory consumption, we know we can delete any sets in this list because no further sets will depend on them - final ArrayList dependentACsetsToDelete = new ArrayList(); + final ArrayList dependentACsetsToDelete = new ArrayList(); // index used to represent this set in the global hashmap: (numChr^0 * allele_1) + (numChr^1 * allele_2) + (numChr^2 * allele_3) + ... - private int index = -1; + private long index = -1; public ExactACset(final int size, final int[] ACcounts) { this.ACcounts = ACcounts; log10Likelihoods = new double[size]; } - public int getIndex() { + public long getIndex() { if ( index == -1 ) index = generateIndex(ACcounts, 2 * log10Likelihoods.length - 1); return index; } - public static int generateIndex(final int[] ACcounts, final int multiplier) { - int index = 0; + public static long generateIndex(final int[] ACcounts, final int multiplier) { + long index = 0L; for ( int i = 0; i < ACcounts.length; i++ ) index += Math.pow(multiplier, i) * ACcounts[i]; return index; @@ -311,13 +310,13 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final Queue ACqueue = new LinkedList(); // mapping of ExactACset indexes to the objects - final HashMap indexesToACset = new HashMap(numChr+1); + final HashMap indexesToACset = new HashMap(numChr+1); // add AC=0 to the queue int[] zeroCounts = new int[numAlternateAlleles]; ExactACset zeroSet = new ExactACset(numSamples+1, zeroCounts); ACqueue.add(zeroSet); - indexesToACset.put(0, zeroSet); + indexesToACset.put(0L, zeroSet); // keep processing while we have AC conformations that need to be calculated double maxLog10L = Double.NEGATIVE_INFINITY; @@ -337,7 +336,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final int numChr, final boolean preserveData, final Queue ACqueue, - final HashMap indexesToACset, + final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, final double[][] log10AlleleFrequencyPosteriors) { @@ -349,7 +348,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // clean up memory if ( !preserveData ) { - for ( int index : set.dependentACsetsToDelete ) { + for ( long index : set.dependentACsetsToDelete ) { indexesToACset.put(index, null); if ( DEBUG ) System.out.printf(" *** removing used set=%d after seeing final dependent set=%d%n", index, set.getIndex()); @@ -419,11 +418,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // returns the ExactACset if that set was not already in the queue and null otherwise. private static ExactACset updateACset(final int[] ACcounts, final int numChr, - final int callingSetIndex, + final long callingSetIndex, final int PLsetIndex, final Queue ACqueue, - final HashMap indexesToACset) { - final int index = ExactACset.generateIndex(ACcounts, numChr+1); + final HashMap indexesToACset) { + final long index = ExactACset.generateIndex(ACcounts, numChr+1); boolean wasInQueue = true; if ( !indexesToACset.containsKey(index) ) { ExactACset set = new ExactACset(numChr/2 +1, ACcounts); @@ -438,7 +437,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return wasInQueue ? null : set; } - private static ExactACset determineLastDependentSetInQueue(final int callingSetIndex, final Queue ACqueue) { + private static ExactACset determineLastDependentSetInQueue(final long callingSetIndex, final Queue ACqueue) { ExactACset set = null; for ( ExactACset queued : ACqueue ) { if ( queued.dependentACsetsToDelete.contains(callingSetIndex) ) @@ -449,7 +448,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { private static void computeLofK(final ExactACset set, final ArrayList genotypeLikelihoods, - final HashMap indexesToACset, + final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, final double[][] log10AlleleFrequencyPosteriors) { @@ -482,7 +481,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // deal with the other possible conformations now if ( totalK <= 2*j ) { // skip impossible conformations int conformationIndex = 1; - for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) { + for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) { if ( DEBUG ) System.out.printf(" *** evaluating set=%d which depends on set=%d%n", set.getIndex(), mapping.getKey()); log10ConformationLikelihoods[conformationIndex++] = 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 3275fc797..ccc585bc7 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 @@ -16,9 +16,9 @@ import java.util.Map; public class UnifiedGenotyperIntegrationTest extends WalkerTest { - private final static String baseCommand = "-T UnifiedGenotyper -R " + b36KGReference + " -NO_HEADER -glm BOTH --dbsnp " + b36dbSNP129; - private final static String baseCommandIndels = "-T UnifiedGenotyper -R " + b36KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b36dbSNP129; - private final static String baseCommandIndelsb37 = "-T UnifiedGenotyper -R " + b37KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b37dbSNP132; + private final static String baseCommand = "-T UnifiedGenotyper --multiallelic -R " + b36KGReference + " -NO_HEADER -glm BOTH --dbsnp " + b36dbSNP129; + private final static String baseCommandIndels = "-T UnifiedGenotyper --multiallelic -R " + b36KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b36dbSNP129; + private final static String baseCommandIndelsb37 = "-T UnifiedGenotyper --multiallelic -R " + b37KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b37dbSNP132; // -------------------------------------------------------------------------------------------------------------- // @@ -284,16 +284,16 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithIndelAllelesPassedIn3() { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( - baseCommandIndels + " --multiallelic --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2.20101123.indels.sites.vcf -I " + validationDataLocation + + 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("f93f8a35b47bcf96594ada55e2312c73")); + Arrays.asList("1d4a6a1b840ca6a130516ab9f2d99869")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @Test public void testWithIndelAllelesPassedIn4() { WalkerTest.WalkerTestSpec spec4 = new WalkerTest.WalkerTestSpec( - baseCommandIndelsb37 + " --multiallelic --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2_chr20_100_110K.20101123.indels.sites.vcf -I " + validationDataLocation + + 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("9be28cb208d8b0314d2bc2696e2fd8d4")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); From 3e7714629f00955cce5a4f3acfe7f24d25d6b1ae Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 8 Dec 2011 23:50:54 -0500 Subject: [PATCH 232/380] Scrapped the whole idea of an int/long as an index into the ACset: with lots of alternate alleles we run into overflow issues. Instead, simply use the ACcounts array as the hash key since it is unique for each AC conformation. To do this, it needed to be wrapped inside an object so hashcode() would work. --- .../genotyper/ExactAFCalculationModel.java | 132 ++++++++++-------- .../genotyper/UnifiedArgumentCollection.java | 9 ++ .../genotyper/UnifiedGenotyperEngine.java | 8 +- 3 files changed, 89 insertions(+), 60 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 1381f48ec..f9d30e0d1 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 @@ -244,55 +244,77 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // ------------------------------------------------------------------------------------- private static final int HOM_REF_INDEX = 0; // AA likelihoods are always first - private static final int AC_ZERO_INDEX = 0; // ExactACset index for k=0 over all k + + // a wrapper around the int array so that we can make it hashable + private static final class ExactACcounts { + + private final int[] counts; + private int hashcode = -1; + + public ExactACcounts(final int[] counts) { + this.counts = counts; + } + + public int[] getCounts() { + return counts; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof ExactACcounts) ? Arrays.equals(counts, ((ExactACcounts)obj).counts) : false; + } + + @Override + public int hashCode() { + if ( hashcode == -1 ) + hashcode = Arrays.hashCode(counts); + return hashcode; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(counts[0]); + for ( int i = 1; i < counts.length; i++ ) { + sb.append("/"); + sb.append(counts[i]); + } + return sb.toString(); + } + } // This class represents a column in the Exact AC calculation matrix private static final class ExactACset { // the counts of the various alternate alleles which this column represents - final int[] ACcounts; + final ExactACcounts ACcounts; // the column of the matrix final double[] log10Likelihoods; // mapping of column index for those columns upon which this one depends to the index into the PLs which is used as the transition to this column; // for example, in the biallelic case, the transition from k=0 to k=1 would be AB while the transition to k=2 would be BB. - final HashMap ACsetIndexToPLIndex = new HashMap(); + final HashMap ACsetIndexToPLIndex = new HashMap(); // to minimize memory consumption, we know we can delete any sets in this list because no further sets will depend on them - final ArrayList dependentACsetsToDelete = new ArrayList(); + final ArrayList dependentACsetsToDelete = new ArrayList(); - // index used to represent this set in the global hashmap: (numChr^0 * allele_1) + (numChr^1 * allele_2) + (numChr^2 * allele_3) + ... - private long index = -1; - public ExactACset(final int size, final int[] ACcounts) { + public ExactACset(final int size, final ExactACcounts ACcounts) { this.ACcounts = ACcounts; log10Likelihoods = new double[size]; } - public long getIndex() { - if ( index == -1 ) - index = generateIndex(ACcounts, 2 * log10Likelihoods.length - 1); - return index; - } - - public static long generateIndex(final int[] ACcounts, final int multiplier) { - long index = 0L; - for ( int i = 0; i < ACcounts.length; i++ ) - index += Math.pow(multiplier, i) * ACcounts[i]; - return index; - } - // sum of all the non-reference alleles public int getACsum() { int sum = 0; - for ( int count : ACcounts ) + for ( int count : ACcounts.getCounts() ) sum += count; return sum; } public boolean equals(Object obj) { - return (obj instanceof ExactACset) ? getIndex() == ((ExactACset)obj).getIndex() : false; + return (obj instanceof ExactACset) ? ACcounts.equals(((ExactACset)obj).ACcounts) : false; } } @@ -310,13 +332,13 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final Queue ACqueue = new LinkedList(); // mapping of ExactACset indexes to the objects - final HashMap indexesToACset = new HashMap(numChr+1); + final HashMap indexesToACset = new HashMap(numChr+1); // add AC=0 to the queue int[] zeroCounts = new int[numAlternateAlleles]; - ExactACset zeroSet = new ExactACset(numSamples+1, zeroCounts); + ExactACset zeroSet = new ExactACset(numSamples+1, new ExactACcounts(zeroCounts)); ACqueue.add(zeroSet); - indexesToACset.put(0L, zeroSet); + indexesToACset.put(zeroSet.ACcounts, zeroSet); // keep processing while we have AC conformations that need to be calculated double maxLog10L = Double.NEGATIVE_INFINITY; @@ -336,22 +358,22 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final int numChr, final boolean preserveData, final Queue ACqueue, - final HashMap indexesToACset, + final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, final double[][] log10AlleleFrequencyPosteriors) { if ( DEBUG ) - System.out.printf(" *** computing LofK for set=%d%n", set.getIndex()); + System.out.printf(" *** computing LofK for set=%s%n", set.ACcounts); // compute the log10Likelihoods computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); // clean up memory if ( !preserveData ) { - for ( long index : set.dependentACsetsToDelete ) { + for ( ExactACcounts index : set.dependentACsetsToDelete ) { indexesToACset.put(index, null); if ( DEBUG ) - System.out.printf(" *** removing used set=%d after seeing final dependent set=%d%n", index, set.getIndex()); + System.out.printf(" *** removing used set=%s after seeing final dependent set=%s%n", index, set.ACcounts); } } @@ -360,11 +382,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // can we abort early because the log10Likelihoods are so small? if ( log10LofK < maxLog10L - MAX_LOG10_ERROR_TO_STOP_EARLY ) { if ( DEBUG ) - System.out.printf(" *** breaking early set=%d log10L=%.2f maxLog10L=%.2f%n", set.getIndex(), log10LofK, maxLog10L); + System.out.printf(" *** breaking early set=%s log10L=%.2f maxLog10L=%.2f%n", set.ACcounts, log10LofK, maxLog10L); // no reason to keep this data around because nothing depends on it if ( !preserveData ) - indexesToACset.put(set.getIndex(), null); + indexesToACset.put(set.ACcounts, null); return log10LofK; } @@ -375,7 +397,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return log10LofK; ExactACset lastSet = null; // keep track of the last set placed in the queue so that we can tell it to clean us up when done processing - final int numAltAlleles = set.ACcounts.length; + final int numAltAlleles = set.ACcounts.getCounts().length; // genotype likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. // so e.g. with 2 alt alleles the likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. @@ -383,19 +405,19 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // add conformations for the k+1 case int PLindex = 0; for ( int allele = 0; allele < numAltAlleles; allele++ ) { - final int[] ACcountsClone = set.ACcounts.clone(); + final int[] ACcountsClone = set.ACcounts.getCounts().clone(); ACcountsClone[allele]++; - lastSet = updateACset(ACcountsClone, numChr, set.getIndex(), ++PLindex, ACqueue, indexesToACset); + lastSet = updateACset(ACcountsClone, numChr, set, ++PLindex, ACqueue, indexesToACset); } // add conformations for the k+2 case if it makes sense; note that the 2 new alleles may be the same or different if ( ACwiggle > 1 ) { for ( int allele_i = 0; allele_i < numAltAlleles; allele_i++ ) { for ( int allele_j = allele_i; allele_j < numAltAlleles; allele_j++ ) { - final int[] ACcountsClone = set.ACcounts.clone(); + final int[] ACcountsClone = set.ACcounts.getCounts().clone(); ACcountsClone[allele_i]++; ACcountsClone[allele_j]++; - lastSet = updateACset(ACcountsClone, numChr, set.getIndex(), ++PLindex , ACqueue, indexesToACset); + lastSet = updateACset(ACcountsClone, numChr, set, ++PLindex , ACqueue, indexesToACset); } } } @@ -404,11 +426,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // over all the dependent sets to find the last one in the queue (otherwise it will be cleaned up too early) if ( !preserveData && lastSet == null ) { if ( DEBUG ) - System.out.printf(" *** iterating over dependent sets for set=%d%n", set.getIndex()); - lastSet = determineLastDependentSetInQueue(set.getIndex(), ACqueue); + System.out.printf(" *** iterating over dependent sets for set=%s%n", set.ACcounts); + lastSet = determineLastDependentSetInQueue(set.ACcounts, ACqueue); } if ( lastSet != null ) - lastSet.dependentACsetsToDelete.add(set.index); + lastSet.dependentACsetsToDelete.add(set.ACcounts); return log10LofK; } @@ -418,14 +440,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // returns the ExactACset if that set was not already in the queue and null otherwise. private static ExactACset updateACset(final int[] ACcounts, final int numChr, - final long callingSetIndex, + final ExactACset callingSet, final int PLsetIndex, final Queue ACqueue, - final HashMap indexesToACset) { - final long index = ExactACset.generateIndex(ACcounts, numChr+1); + final HashMap indexesToACset) { + final ExactACcounts index = new ExactACcounts(ACcounts); boolean wasInQueue = true; if ( !indexesToACset.containsKey(index) ) { - ExactACset set = new ExactACset(numChr/2 +1, ACcounts); + ExactACset set = new ExactACset(numChr/2 +1, index); indexesToACset.put(index, set); ACqueue.add(set); wasInQueue = false; @@ -433,11 +455,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // add the given dependency to the set final ExactACset set = indexesToACset.get(index); - set.ACsetIndexToPLIndex.put(callingSetIndex, PLsetIndex); + set.ACsetIndexToPLIndex.put(callingSet.ACcounts, PLsetIndex); return wasInQueue ? null : set; } - private static ExactACset determineLastDependentSetInQueue(final long callingSetIndex, final Queue ACqueue) { + private static ExactACset determineLastDependentSetInQueue(final ExactACcounts callingSetIndex, final Queue ACqueue) { ExactACset set = null; for ( ExactACset queued : ACqueue ) { if ( queued.dependentACsetsToDelete.contains(callingSetIndex) ) @@ -448,7 +470,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { private static void computeLofK(final ExactACset set, final ArrayList genotypeLikelihoods, - final HashMap indexesToACset, + final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, final double[][] log10AlleleFrequencyPosteriors) { @@ -456,7 +478,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final int totalK = set.getACsum(); // special case for k = 0 over all k - if ( set.getIndex() == AC_ZERO_INDEX ) { + if ( totalK == 0 ) { for ( int j = 1; j < set.log10Likelihoods.length; j++ ) set.log10Likelihoods[j] = set.log10Likelihoods[j-1] + genotypeLikelihoods.get(j)[HOM_REF_INDEX]; } @@ -481,11 +503,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // deal with the other possible conformations now if ( totalK <= 2*j ) { // skip impossible conformations int conformationIndex = 1; - for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) { + for ( Map.Entry mapping : set.ACsetIndexToPLIndex.entrySet() ) { if ( DEBUG ) - System.out.printf(" *** evaluating set=%d which depends on set=%d%n", set.getIndex(), mapping.getKey()); + System.out.printf(" *** evaluating set=%s which depends on set=%s%n", set.ACcounts, mapping.getKey()); log10ConformationLikelihoods[conformationIndex++] = - determineCoefficient(mapping.getValue(), j, set.ACcounts, totalK) + indexesToACset.get(mapping.getKey()).log10Likelihoods[j-1] + gl[mapping.getValue()]; + determineCoefficient(mapping.getValue(), j, set.ACcounts.getCounts(), totalK) + indexesToACset.get(mapping.getKey()).log10Likelihoods[j-1] + gl[mapping.getValue()]; } } @@ -501,16 +523,16 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // determine the power of theta to use int nonRefAlleles = 0; - for ( int i = 0; i < set.ACcounts.length; i++ ) { - if ( set.ACcounts[i] > 0 ) + for ( int i = 0; i < set.ACcounts.getCounts().length; i++ ) { + if ( set.ACcounts.getCounts()[i] > 0 ) nonRefAlleles++; } // update the posteriors vector which is a collapsed view of each of the various ACs - for ( int i = 0; i < set.ACcounts.length; i++ ) { + for ( int i = 0; i < set.ACcounts.getCounts().length; i++ ) { // for k=0 we still want to use theta - final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][set.ACcounts[i]]; - log10AlleleFrequencyPosteriors[i][set.ACcounts[i]] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][set.ACcounts[i]], log10LofK + prior); + final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][set.ACcounts.getCounts()[i]]; + log10AlleleFrequencyPosteriors[i][set.ACcounts.getCounts()[i]] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][set.ACcounts.getCounts()[i]], log10LofK + prior); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index d7101da6b..bfa87122c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java @@ -157,6 +157,14 @@ public class UnifiedArgumentCollection { @Argument(fullName = "multiallelic", shortName = "multiallelic", doc = "Allow multiple alleles in discovery", required = false) public boolean MULTI_ALLELIC = false; + /** + * If there are more than this number of alternate alleles presented to the genotyper (either through discovery or GENOTYPE_GIVEN ALLELES), + * then this site will be skipped and a warning printed. + */ + @Hidden + @Argument(fullName = "max_alternate_alleles", shortName = "maxAlleles", doc = "Maximum number of alternate alleles to genotype", required = false) + public int MAX_ALTERNATE_ALLELES = 5; + // Developers must remember to add any newly added arguments to the list here as well otherwise they won't get changed from their default value! public UnifiedArgumentCollection clone() { @@ -180,6 +188,7 @@ public class UnifiedArgumentCollection { uac.OUTPUT_DEBUG_INDEL_INFO = OUTPUT_DEBUG_INDEL_INFO; uac.INDEL_HAPLOTYPE_SIZE = INDEL_HAPLOTYPE_SIZE; uac.alleles = alleles; + uac.MAX_ALTERNATE_ALLELES = MAX_ALTERNATE_ALLELES; // todo- arguments to remove uac.IGNORE_SNP_ALLELES = IGNORE_SNP_ALLELES; 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 beba865dd..6a79061fc 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 @@ -80,9 +80,6 @@ public class UnifiedGenotyperEngine { // the allele frequency likelihoods (allocated once as an optimization) private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); - // the maximum number of alternate alleles for genotyping supported by the genotyper; we fix this here so that the AF priors and posteriors can be initialized at startup - private static final int MAX_NUMBER_OF_ALTERNATE_ALLELES = 10; - // the priors object private final GenotypePriors genotypePriorsSNPs; private final GenotypePriors genotypePriorsIndels; @@ -126,8 +123,8 @@ public class UnifiedGenotyperEngine { this.annotationEngine = engine; N = 2 * this.samples.size(); - log10AlleleFrequencyPriorsSNPs = new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]; - log10AlleleFrequencyPriorsIndels = new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]; + log10AlleleFrequencyPriorsSNPs = new double[UAC.MAX_ALTERNATE_ALLELES][N+1]; + log10AlleleFrequencyPriorsIndels = new double[UAC.MAX_ALTERNATE_ALLELES][N+1]; computeAlleleFrequencyPriors(N, log10AlleleFrequencyPriorsSNPs, UAC.heterozygosity); computeAlleleFrequencyPriors(N, log10AlleleFrequencyPriorsIndels, UAC.INDEL_HETEROZYGOSITY); genotypePriorsSNPs = createGenotypePriors(GenotypeLikelihoodsCalculationModel.Model.SNP); @@ -155,6 +152,7 @@ public class UnifiedGenotyperEngine { return (UAC.OutputMode == OUTPUT_MODE.EMIT_ALL_SITES && UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ? generateEmptyContext(tracker, refContext, stratifiedContexts, rawContext) : null); } + VariantContext vc = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, null, true, model); VariantContext vc = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, null, true, model); if ( vc == null ) From 8777288a9f62a610e3efaf1ebfdd434b0bf79520 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 9 Dec 2011 00:00:20 -0500 Subject: [PATCH 233/380] Don't throw a UserException if too many alt alleles are trying to be genotyped. Instead, I've added an argument that allows the user to set the max number of alt alleles to genotype and the UG warns and skips any sites with more than that number. --- .../genotyper/UnifiedArgumentCollection.java | 2 +- .../genotyper/UnifiedGenotyperEngine.java | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index bfa87122c..53600b145 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java @@ -159,7 +159,7 @@ public class UnifiedArgumentCollection { /** * If there are more than this number of alternate alleles presented to the genotyper (either through discovery or GENOTYPE_GIVEN ALLELES), - * then this site will be skipped and a warning printed. + * then this site will be skipped and a warning printed. Note that genotyping sites with many alternate alleles is both CPU and memory intensive. */ @Hidden @Argument(fullName = "max_alternate_alleles", shortName = "maxAlleles", doc = "Maximum number of alternate alleles to genotype", required = false) 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 6a79061fc..6e61790ed 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 @@ -38,7 +38,6 @@ import org.broadinstitute.sting.utils.*; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; @@ -153,8 +152,6 @@ public class UnifiedGenotyperEngine { } VariantContext vc = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, null, true, model); - VariantContext vc = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, null, true, model); - if ( vc == null ) return null; @@ -297,13 +294,15 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyPosteriors.set(new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]); + log10AlleleFrequencyPosteriors.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } // don't try to genotype too many alternate alleles - if ( vc.getAlternateAlleles().size() > MAX_NUMBER_OF_ALTERNATE_ALLELES ) - throw new UserException("the Unified Genotyper is currently not equipped to genotype more than " + MAX_NUMBER_OF_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles"); + if ( vc.getAlternateAlleles().size() > UAC.MAX_ALTERNATE_ALLELES ) { + logger.warn("the Unified Genotyper is currently set to genotype at most " + UAC.MAX_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles; see the --max_alternate_alleles argument"); + return null; + } // estimate our confidence in a reference call and return if ( vc.getNSamples() == 0 ) @@ -446,13 +445,15 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyPosteriors.set(new double[MAX_NUMBER_OF_ALTERNATE_ALLELES][N+1]); + log10AlleleFrequencyPosteriors.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } // don't try to genotype too many alternate alleles - if ( vc.getAlternateAlleles().size() > MAX_NUMBER_OF_ALTERNATE_ALLELES ) - throw new UserException("the Unified Genotyper is currently not equipped to genotype more than " + MAX_NUMBER_OF_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles"); + if ( vc.getAlternateAlleles().size() > UAC.MAX_ALTERNATE_ALLELES ) { + logger.warn("the Unified Genotyper is currently set to genotype at most " + UAC.MAX_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles; see the --max_alternate_alleles argument"); + return null; + } // estimate our confidence in a reference call and return if ( vc.getNSamples() == 0 ) From 2fe50c64da60ab27bf119f2975b364e3e629d18d Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 9 Dec 2011 00:47:01 -0500 Subject: [PATCH 234/380] Updating md5s --- .../walkers/genotyper/UnifiedGenotyperIntegrationTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 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 ccc585bc7..a91b6c15d 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 @@ -7,7 +7,6 @@ import org.testng.annotations.Test; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; // ********************************************************************************** // @@ -29,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("b70732a2f63f8409b61e41fa53eaae3e")); + Arrays.asList("f6ef10dee80f9ccd7d245a28787ca887")); executeTest("test MultiSample Pilot1", spec); } @@ -295,8 +294,8 @@ 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("9be28cb208d8b0314d2bc2696e2fd8d4")); - executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); + Arrays.asList("6ee2f3c6b5422f0a2ad0669639e293cb")); + executeTest("test MultiSample Phase1 indels with complicated records", spec4); } @Test From aa4a8c5303e41d6a9b9cfe28c3a8321dabb86ff3 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 9 Dec 2011 02:25:06 -0500 Subject: [PATCH 235/380] No dynamic programming solution for assignning genotypes; just done greedily now. Fixed QualByDepth to skip no-call genotypes. No-calls are no longer given annotations (attributes). --- .../gatk/walkers/annotator/QualByDepth.java | 2 +- .../genotyper/ExactAFCalculationModel.java | 99 ++----------------- .../UnifiedGenotyperIntegrationTest.java | 20 ++-- 3 files changed, 20 insertions(+), 101 deletions(-) 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 d555463bc..6638fc7a8 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 @@ -38,7 +38,7 @@ public class QualByDepth extends InfoFieldAnnotation implements StandardAnnotati for ( final Genotype genotype : genotypes ) { // we care only about variant calls with likelihoods - if ( genotype.isHomRef() ) + if ( !genotype.isHet() && !genotype.isHomVar() ) continue; AlignmentContext context = stratifiedContexts.get(genotype.getSampleName()); 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 f9d30e0d1..22017a1ee 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,7 +42,6 @@ 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 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 static final boolean SIMPLE_GREEDY_GENOTYPER = false; private static final List NO_CALL_ALLELES = Arrays.asList(Allele.NO_CALL, Allele.NO_CALL); private final boolean USE_MULTI_ALLELIC_CALCULATION; @@ -592,10 +591,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { GenotypesContext GLs = vc.getGenotypes(); double[][] pathMetricArray = new double[GLs.size()+1][AFofMaxLikelihood+1]; - int[][] tracebackArray = new int[GLs.size()+1][AFofMaxLikelihood+1]; ArrayList sampleIndices = new ArrayList(); - int sampleIdx = 0; // todo - optimize initialization for (int k=0; k <= AFofMaxLikelihood; k++) @@ -604,83 +601,29 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { pathMetricArray[0][0] = 0.0; - // todo = can't deal with optimal dynamic programming solution with multiallelic records - if (SIMPLE_GREEDY_GENOTYPER || !vc.isBiallelic()) { - sampleIndices.addAll(GLs.getSampleNamesOrderedByName()); - sampleIdx = GLs.size(); - } - else { - - for ( final Genotype genotype : GLs.iterateInSampleNameOrder() ) { - if ( !genotype.hasLikelihoods() ) - continue; - - double[] likelihoods = genotype.getLikelihoods().getAsVector(); - - if (MathUtils.sum(likelihoods) > SUM_GL_THRESH_NOCALL) { - //System.out.print(sample.getKey()+":"); - //for (int k=0; k < likelihoods.length; k++) - // System.out.format("%4.2f ",likelihoods[k]); - //System.out.println(); - // all likelihoods are essentially the same: skip this sample and will later on force no call. - //sampleIdx++; - continue; - } - - sampleIndices.add(genotype.getSampleName()); - - for (int k=0; k <= AFofMaxLikelihood; k++) { - - double bestMetric = pathMetricArray[sampleIdx][k] + likelihoods[0]; - int bestIndex = k; - - if (k>0) { - double m2 = pathMetricArray[sampleIdx][k-1] + likelihoods[1]; - if (m2 > bestMetric) { - bestMetric = m2; - bestIndex = k-1; - } - } - - if (k>1) { - double m2 = pathMetricArray[sampleIdx][k-2] + likelihoods[2]; - if (m2 > bestMetric) { - bestMetric = m2; - bestIndex = k-2; - } - } - - pathMetricArray[sampleIdx+1][k] = bestMetric; - tracebackArray[sampleIdx+1][k] = bestIndex; - } - sampleIdx++; - } - } + sampleIndices.addAll(GLs.getSampleNamesOrderedByName()); GenotypesContext calls = GenotypesContext.create(); - int startIdx = AFofMaxLikelihood; - for (int k = sampleIdx; k > 0; k--) { + for (int k = GLs.size(); k > 0; k--) { int bestGTguess; String sample = sampleIndices.get(k-1); Genotype g = GLs.get(sample); if ( !g.hasLikelihoods() ) continue; - // if all likelihoods are essentially the same: we want to force no-call. In this case, we skip this sample for now, - // and will add no-call genotype to GL's in a second pass + ArrayList myAlleles = new ArrayList(); 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; + // if there is no mass on the likelihoods, then just no-call the sample + if ( MathUtils.sum(likelihoods) > SUM_GL_THRESH_NOCALL ) { + calls.add(new Genotype(g.getSampleName(), NO_CALL_ALLELES, Genotype.NO_LOG10_PERROR, null, null, false)); + continue; } + bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); + // 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 @@ -709,33 +652,9 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } 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; - - 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.replace(new Genotype(g.getSampleName(), NO_CALL_ALLELES, qual, null, g.getAttributes(), false)); - } - return calls; } - - private final static void printLikelihoods(int numChr, double[][] logYMatrix, double[] log10AlleleFrequencyPriors) { - int j = logYMatrix.length - 1; - System.out.printf("-----------------------------------%n"); - for (int k=0; k <= numChr; k++) { - double posterior = logYMatrix[j][k] + log10AlleleFrequencyPriors[k]; - System.out.printf(" %4d\t%8.2f\t%8.2f\t%8.2f%n", k, logYMatrix[j][k], log10AlleleFrequencyPriors[k], posterior); - } - } } 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 a91b6c15d..c04b0085c 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 @@ -28,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("f6ef10dee80f9ccd7d245a28787ca887")); + Arrays.asList("a2d3839c4ebb390b0012d495e4e53b3a")); executeTest("test MultiSample Pilot1", spec); } @@ -44,7 +44,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("d0593483e85a7d815f4c5ee6db284d2a")); + Arrays.asList("43e7a17d95b1a0cf72e669657794d802")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } @@ -52,7 +52,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("3ccce5d909f8f128e496f6841836e5f7")); + Arrays.asList("ae29b9c9aacce8046dc780430540cd62")); executeTest("test SingleSample Pilot2", spec); } @@ -62,7 +62,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // // -------------------------------------------------------------------------------------------------------------- - private final static String COMPRESSED_OUTPUT_MD5 = "890143b366050e78d6c6ba6b2c6b6864"; + private final static String COMPRESSED_OUTPUT_MD5 = "fda341de80b3f6fd42a83352b18b1d65"; @Test public void testCompressedOutput() { @@ -83,7 +83,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 = "95614280c565ad90f8c000376fef822c"; + String md5 = "32a34362dff51d8b73a3335048516d82"; 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, @@ -164,8 +164,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testHeterozyosity() { HashMap e = new HashMap(); - e.put( 0.01, "46243ecc2b9dc716f48ea280c9bb7e72" ); - e.put( 1.0 / 1850, "6b2a59dbc76984db6d4d6d6b5ee5d62c" ); + e.put( 0.01, "2cb2544739e01f6c08fd820112914317" ); + e.put( 1.0 / 1850, "730b2b83a4b1f6d46fc3b5cd7d90756c" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -275,7 +275,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("6e182a58472ea17c8b0eb01f80562fbd")); + Arrays.asList("45633d905136c86e9d3f90ce613255e5")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -285,7 +285,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("1d4a6a1b840ca6a130516ab9f2d99869")); + Arrays.asList("75e49dff01763aff2984dc86a72eb229")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -294,7 +294,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("6ee2f3c6b5422f0a2ad0669639e293cb")); + Arrays.asList("8209a308d95659c6da7dab8733c736f9")); executeTest("test MultiSample Phase1 indels with complicated records", spec4); } From 5a0617080483b4fb4076bdc915962a06ace255ea Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Fri, 9 Dec 2011 14:51:34 +0100 Subject: [PATCH 236/380] Corrected bug causing getChildrenWithParents() to not take the last family member into consideration. --- .../src/org/broadinstitute/sting/gatk/samples/SampleDB.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java index 1ed8dd7a3..a6f6b3481 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -200,7 +200,9 @@ public class SampleDB { continue; sampleIterator = familyMembers.iterator(); - for(Sample sample = sampleIterator.next(); sampleIterator.hasNext(); sample = sampleIterator.next()){ + Sample sample; + while(sampleIterator.hasNext()){ + sample = sampleIterator.next(); if(sample.getParents().size() == 2 && familyMembers.containsAll(sample.getParents())) childrenWithParents.add(sample); } From 72fbfba97d3875253f3dfbd8221c7f2e442fc35b Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Fri, 9 Dec 2011 15:57:07 +0100 Subject: [PATCH 237/380] Added UnitTests for getFamilies() and getChildrenWithParents() --- .../sting/gatk/samples/SampleDBUnitTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java index d498ee61a..7f21da4f4 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java @@ -27,11 +27,42 @@ public class SampleDBUnitTest extends BaseTest { new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.AFFECTED))); + private static final Set testPEDFamilyF2 = new HashSet(Arrays.asList( + new Sample("s2", "fam2", "d2", "m2", Gender.FEMALE, Affection.AFFECTED), + new Sample("d2", "fam2", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("m2", "fam2", null, null, Gender.FEMALE, Affection.UNKNOWN) + )); + + private static final Set testPEDFamilyF3 = new HashSet(Arrays.asList( + new Sample("s1", "fam3", "d1", "m1", Gender.FEMALE, Affection.AFFECTED), + new Sample("d1", "fam3", null, null, Gender.FEMALE, Affection.UNKNOWN), + new Sample("m1", "fam3", null, null, Gender.FEMALE, Affection.UNKNOWN) + )); + private static final Set testSAMSamples = new HashSet(Arrays.asList( new Sample("kid", null, null, null, Gender.UNKNOWN, Affection.UNKNOWN), new Sample("mom", null, null, null, Gender.UNKNOWN, Affection.UNKNOWN), new Sample("dad", null, null, null, Gender.UNKNOWN, Affection.UNKNOWN))); + private static final HashMap> testGetFamilies = new HashMap>(); + static { + testGetFamilies.put("fam1", testPEDSamples); + testGetFamilies.put("fam2", testPEDFamilyF2); + testGetFamilies.put("fam3", testPEDFamilyF3); + } + + private static final Set testKidsWithParentsFamilies2 = new HashSet(Arrays.asList( + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("kid3", "fam5", "dad2", "mom2", Gender.MALE, Affection.AFFECTED), + new Sample("kid2", "fam5", "dad2", "mom2", Gender.MALE, Affection.AFFECTED))); + + private static final HashSet testGetPartialFamiliesIds = new HashSet(Arrays.asList("kid","s1")); + private static final HashMap> testGetPartialFamilies = new HashMap>(); + static { + testGetPartialFamilies.put("fam1", new HashSet(Arrays.asList(new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED)))); + testGetPartialFamilies.put("fam3", new HashSet(Arrays.asList(new Sample("s1", "fam3", "d1", "m1", Gender.FEMALE, Affection.AFFECTED)))); + } + private static final String testPEDString = String.format("%s%n%s%n%s", "fam1 kid dad mom 1 2", @@ -46,6 +77,18 @@ public class SampleDBUnitTest extends BaseTest { "fam3 s1 d1 m1 2 2", "fam2 s2 d2 m2 2 2"); + private static final String testPEDMultipleFamilies2 = + String.format("%s%n%s%n%s%n%s%n%s%n%s%n%s%n%s%n%s", + "fam1 kid dad mom 1 2", + "fam1 dad 0 0 1 1", + "fam1 mom 0 0 2 2", + "fam4 kid4 dad4 0 1 2", + "fam4 dad4 0 0 1 1", + "fam5 kid2 dad2 mom2 1 2", + "fam5 kid3 dad2 mom2 1 2", + "fam5 dad2 0 0 1 1", + "fam5 mom2 0 0 2 2"); + private static final String testPEDStringInconsistentGender = "fam1 kid 0 0 2 2"; @@ -138,6 +181,25 @@ public class SampleDBUnitTest extends BaseTest { Assert.assertEquals(db.getFamily("fam1"), testPEDSamplesAsSet); } + @Test() + public void getFamilies(){ + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDMultipleFamilies)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(db.getFamilies(),testGetFamilies); + Assert.assertEquals(db.getFamilies(null),testGetFamilies); + Assert.assertEquals(db.getFamilies(testGetPartialFamiliesIds),testGetPartialFamilies); + } + + @Test() + public void testGetChildrenWithParents() + { + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDMultipleFamilies2)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(db.getChildrenWithParents(), testKidsWithParentsFamilies2); + Assert.assertEquals(db.getChildrenWithParents(false), testKidsWithParentsFamilies2); + Assert.assertEquals(db.getChildrenWithParents(true), new HashSet(Arrays.asList(new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED)))); + } + @Test() public void loadFamilyIDs() { builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDMultipleFamilies)); From 442ceb6ad9b381108a3420a4158118e38339b474 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 9 Dec 2011 10:16:44 -0500 Subject: [PATCH 238/380] The Exact model now computes both the likelihoods and posteriors (in separate arrays); likelihoods are used for assigning genotypes, not the posteriors. --- .../AlleleFrequencyCalculationModel.java | 16 +++++----- .../genotyper/ExactAFCalculationModel.java | 31 ++++++++++++------- .../genotyper/UnifiedGenotyperEngine.java | 22 ++++++++----- .../ExactAFCalculationModelUnitTest.java | 4 ++- 4 files changed, 47 insertions(+), 26 deletions(-) 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 2808e6968..01e696237 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 @@ -62,24 +62,26 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { /** * Must be overridden by concrete subclasses - * @param GLs genotype likelihoods - * @param Alleles Alleles corresponding to GLs - * @param log10AlleleFrequencyPriors priors - * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store results + * @param GLs genotype likelihoods + * @param Alleles Alleles corresponding to GLs + * @param log10AlleleFrequencyPriors priors + * @param log10AlleleFrequencyLikelihoods array (pre-allocated) to store likelihoods results + * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store posteriors results */ protected abstract void getLog10PNonRef(GenotypesContext GLs, List Alleles, double[][] log10AlleleFrequencyPriors, + double[][] log10AlleleFrequencyLikelihoods, double[][] log10AlleleFrequencyPosteriors); /** * Can be overridden by concrete subclasses * @param vc variant context with genotype likelihoods - * @param log10AlleleFrequencyPosteriors allele frequency results + * @param log10AlleleFrequencyLikelihoods allele frequency results * @param AFofMaxLikelihood allele frequency of max likelihood * * @return calls */ protected abstract GenotypesContext assignGenotypes(VariantContext vc, - double[][] log10AlleleFrequencyPosteriors, - int AFofMaxLikelihood); + double[][] log10AlleleFrequencyLikelihoods, + 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 22017a1ee..f4af579e3 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 @@ -52,15 +52,17 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { USE_MULTI_ALLELIC_CALCULATION = UAC.MULTI_ALLELIC; } - public void getLog10PNonRef(GenotypesContext GLs, List alleles, - double[][] log10AlleleFrequencyPriors, - double[][] log10AlleleFrequencyPosteriors) { + public void getLog10PNonRef(final GenotypesContext GLs, + final List alleles, + final double[][] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyLikelihoods, + final double[][] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); if ( USE_MULTI_ALLELIC_CALCULATION ) - linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, false); + linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors, false); else - linearExact(GLs, log10AlleleFrequencyPriors[0], log10AlleleFrequencyPosteriors); + linearExact(GLs, log10AlleleFrequencyPriors[0], log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); } private static final ArrayList getGLs(GenotypesContext GLs) { @@ -125,6 +127,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public int linearExact(GenotypesContext GLs, double[] log10AlleleFrequencyPriors, + double[][] log10AlleleFrequencyLikelihoods, double[][] log10AlleleFrequencyPosteriors) { final ArrayList genotypeLikelihoods = getGLs(GLs); final int numSamples = genotypeLikelihoods.size()-1; @@ -176,6 +179,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // update the posteriors vector final double log10LofK = kMinus0[numSamples]; + log10AlleleFrequencyLikelihoods[0][k] = log10LofK; log10AlleleFrequencyPosteriors[0][k] = log10LofK + log10AlleleFrequencyPriors[k]; // can we abort early? @@ -320,6 +324,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public static void linearExactMultiAllelic(final GenotypesContext GLs, final int numAlternateAlleles, final double[][] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyLikelihoods, final double[][] log10AlleleFrequencyPosteriors, final boolean preserveData) { @@ -344,7 +349,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { while ( !ACqueue.isEmpty() ) { // compute log10Likelihoods final ExactACset set = ACqueue.remove(); - final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); + final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); // adjust max likelihood seen if needed maxLog10L = Math.max(maxLog10L, log10LofKs); @@ -359,13 +364,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final Queue ACqueue, final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyLikelihoods, final double[][] log10AlleleFrequencyPosteriors) { if ( DEBUG ) System.out.printf(" *** computing LofK for set=%s%n", set.ACcounts); // compute the log10Likelihoods - computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors); + computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); // clean up memory if ( !preserveData ) { @@ -471,6 +477,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final ArrayList genotypeLikelihoods, final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, + final double[][] log10AlleleFrequencyLikelihoods, final double[][] log10AlleleFrequencyPosteriors) { set.log10Likelihoods[0] = 0.0; // the zero case @@ -517,7 +524,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } } - // update the posteriors vector final double log10LofK = set.log10Likelihoods[set.log10Likelihoods.length-1]; // determine the power of theta to use @@ -527,11 +533,14 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { nonRefAlleles++; } - // update the posteriors vector which is a collapsed view of each of the various ACs + // update the likelihoods/posteriors vectors which are collapsed views of each of the various ACs for ( int i = 0; i < set.ACcounts.getCounts().length; i++ ) { + int AC = set.ACcounts.getCounts()[i]; + log10AlleleFrequencyLikelihoods[i][AC] = approximateLog10SumLog10(log10AlleleFrequencyLikelihoods[i][AC], log10LofK); + // for k=0 we still want to use theta - final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][set.ACcounts.getCounts()[i]]; - log10AlleleFrequencyPosteriors[i][set.ACcounts.getCounts()[i]] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][set.ACcounts.getCounts()[i]], log10LofK + prior); + final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][AC]; + log10AlleleFrequencyPosteriors[i][AC] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][AC], log10LofK + prior); } } 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 6e61790ed..606a0544c 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 @@ -77,6 +77,7 @@ public class UnifiedGenotyperEngine { private final double[][] log10AlleleFrequencyPriorsIndels; // the allele frequency likelihoods (allocated once as an optimization) + private ThreadLocal log10AlleleFrequencyLikelihoods = new ThreadLocal(); private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); // the priors object @@ -294,6 +295,7 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { + log10AlleleFrequencyLikelihoods.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); log10AlleleFrequencyPosteriors.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } @@ -311,8 +313,9 @@ public class UnifiedGenotyperEngine { generateEmptyContext(tracker, refContext, stratifiedContexts, rawContext)); // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) + clearAFarray(log10AlleleFrequencyLikelihoods.get()); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); // find the most likely frequency int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[0]); @@ -350,7 +353,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyLikelihoods.get(), bestAFguess); // print out stats if we have a writer if ( verboseWriter != null ) @@ -369,16 +372,18 @@ public class UnifiedGenotyperEngine { // the overall lod VariantContext vcOverall = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, vc.getAlternateAllele(0), false, model); + clearAFarray(log10AlleleFrequencyLikelihoods.get()); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); //double overallLog10PofNull = log10AlleleFrequencyPosteriors.get()[0]; double overallLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); //if ( DEBUG_SLOD ) System.out.println("overallLog10PofF=" + overallLog10PofF); // the forward lod VariantContext vcForward = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.FORWARD, vc.getAlternateAllele(0), false, model); + clearAFarray(log10AlleleFrequencyLikelihoods.get()); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); //double[] normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); double forwardLog10PofNull = log10AlleleFrequencyPosteriors.get()[0][0]; double forwardLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); @@ -386,8 +391,9 @@ public class UnifiedGenotyperEngine { // the reverse lod VariantContext vcReverse = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.REVERSE, vc.getAlternateAllele(0), false, model); + clearAFarray(log10AlleleFrequencyLikelihoods.get()); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); //normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); double reverseLog10PofNull = log10AlleleFrequencyPosteriors.get()[0][0]; double reverseLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); @@ -445,6 +451,7 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { + log10AlleleFrequencyLikelihoods.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); log10AlleleFrequencyPosteriors.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); } @@ -460,8 +467,9 @@ public class UnifiedGenotyperEngine { return null; // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) + clearAFarray(log10AlleleFrequencyLikelihoods.get()); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); // find the most likely frequency int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[0]); @@ -499,7 +507,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyPosteriors.get(), bestAFguess); + GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyLikelihoods.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/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java index 00cfff4b3..9640a8963 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java @@ -83,14 +83,16 @@ public class ExactAFCalculationModelUnitTest extends BaseTest { @Test(dataProvider = "getGLs") public void testGLs(GetGLsTest cfg) { + final double[][] log10AlleleFrequencyLikelihoods = new double[2][2*numSamples+1]; final double[][] log10AlleleFrequencyPosteriors = new double[2][2*numSamples+1]; for ( int i = 0; i < 2; i++ ) { for ( int j = 0; j < 2*numSamples+1; j++ ) { + log10AlleleFrequencyLikelihoods[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; log10AlleleFrequencyPosteriors[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; } } - ExactAFCalculationModel.linearExactMultiAllelic(cfg.GLs, cfg.numAltAlleles, priors, log10AlleleFrequencyPosteriors, false); + ExactAFCalculationModel.linearExactMultiAllelic(cfg.GLs, cfg.numAltAlleles, priors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors, false); int nameIndex = 1; for ( int allele = 0; allele < cfg.numAltAlleles; allele++, nameIndex+=2 ) { From 64dad13e2da3294d7f8132fce40bd9da24529375 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 9 Dec 2011 11:09:40 -0500 Subject: [PATCH 239/380] Don't carry around an extra copy of the code for the Haplotype Caller --- .../genotyper/ExactAFCalculationModel.java | 3 +- .../genotyper/UnifiedGenotyperEngine.java | 135 ++++-------------- 2 files changed, 31 insertions(+), 107 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 f4af579e3..dccc2c02c 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 @@ -588,12 +588,13 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { /** * Can be overridden by concrete subclasses * @param vc variant context with genotype likelihoods + * @param log10AlleleFrequencyLikelihoods likelihoods * @param AFofMaxLikelihood allele frequency of max likelihood * * @return calls */ public GenotypesContext assignGenotypes(VariantContext vc, - double[][] log10AlleleFrequencyPosteriors, + double[][] log10AlleleFrequencyLikelihoods, int AFofMaxLikelihood) { if ( !vc.isVariant() ) throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); 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 606a0544c..4821b3eb8 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 @@ -97,6 +97,7 @@ public class UnifiedGenotyperEngine { // the standard filter to use for calls below the confidence threshold but above the emit threshold private static final Set filter = new HashSet(1); + private final GenomeLocParser genomeLocParser; private final boolean BAQEnabledOnCMDLine; @@ -114,6 +115,7 @@ public class UnifiedGenotyperEngine { @Requires({"toolkit != null", "UAC != null", "logger != null", "samples != null && samples.size() > 0"}) public UnifiedGenotyperEngine(GenomeAnalysisEngine toolkit, UnifiedArgumentCollection UAC, Logger logger, PrintStream verboseWriter, VariantAnnotatorEngine engine, Set samples) { this.BAQEnabledOnCMDLine = toolkit.getArguments().BAQMode != BAQ.CalculationMode.OFF; + genomeLocParser = toolkit.getGenomeLocParser(); this.samples = new TreeSet(samples); // note that, because we cap the base quality by the mapping quality, minMQ cannot be less than minBQ this.UAC = UAC.clone(); @@ -290,8 +292,13 @@ public class UnifiedGenotyperEngine { 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 - private VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, ReferenceContext refContext, AlignmentContext rawContext, Map stratifiedContexts, VariantContext vc, final GenotypeLikelihoodsCalculationModel.Model model) { + public VariantCallContext calculateGenotypes(VariantContext vc, final GenotypeLikelihoodsCalculationModel.Model model) { + return calculateGenotypes(null, null, null, null, vc, model); + } + + public VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, ReferenceContext refContext, AlignmentContext rawContext, Map stratifiedContexts, VariantContext vc, final GenotypeLikelihoodsCalculationModel.Model model) { + + boolean limitedContext = tracker == null || refContext == null || rawContext == null || stratifiedContexts == null; // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { @@ -307,10 +314,13 @@ public class UnifiedGenotyperEngine { } // estimate our confidence in a reference call and return - if ( vc.getNSamples() == 0 ) + if ( vc.getNSamples() == 0 ) { + if ( limitedContext ) + return null; return (UAC.OutputMode != OUTPUT_MODE.EMIT_ALL_SITES ? estimateReferenceConfidence(vc, stratifiedContexts, getGenotypePriors(model).getHeterozygosity(), false, 1.0) : generateEmptyContext(tracker, refContext, stratifiedContexts, rawContext)); + } // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) clearAFarray(log10AlleleFrequencyLikelihoods.get()); @@ -349,25 +359,31 @@ public class UnifiedGenotyperEngine { if ( UAC.OutputMode != OUTPUT_MODE.EMIT_ALL_SITES && !passesEmitThreshold(phredScaledConfidence, bestAFguess) ) { // technically, at this point our confidence in a reference call isn't accurately estimated // because it didn't take into account samples with no data, so let's get a better estimate - return estimateReferenceConfidence(vc, stratifiedContexts, getGenotypePriors(model).getHeterozygosity(), true, 1.0 - PofF); + return limitedContext ? null : estimateReferenceConfidence(vc, stratifiedContexts, getGenotypePriors(model).getHeterozygosity(), true, 1.0 - PofF); + } + + // strip out the alternate allele(s) if we're making a ref call + Set myAlleles = new HashSet(vc.getAlleles()); + if ( bestAFguess == 0 && UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { + myAlleles = new HashSet(1); + myAlleles.add(vc.getReference()); } // create the genotypes GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyLikelihoods.get(), bestAFguess); // print out stats if we have a writer - if ( verboseWriter != null ) + if ( verboseWriter != null && !limitedContext ) printVerboseData(refContext.getLocus().toString(), vc, PofF, phredScaledConfidence, normalizedPosteriors, model); // *** note that calculating strand bias involves overwriting data structures, so we do that last HashMap attributes = new HashMap(); // if the site was downsampled, record that fact - if ( rawContext.hasPileupBeenDownsampled() ) + if ( !limitedContext && rawContext.hasPileupBeenDownsampled() ) attributes.put(VCFConstants.DOWNSAMPLED_KEY, true); - - if ( UAC.COMPUTE_SLOD && bestAFguess != 0 ) { + if ( UAC.COMPUTE_SLOD && !limitedContext && bestAFguess != 0 ) { //final boolean DEBUG_SLOD = false; // the overall lod @@ -412,26 +428,18 @@ public class UnifiedGenotyperEngine { attributes.put("SB", strandScore); } - GenomeLoc loc = refContext.getLocus(); + GenomeLoc loc = genomeLocParser.createGenomeLoc(vc); - int endLoc = calculateEndPos(vc.getAlleles(), vc.getReference(), loc); - - Set myAlleles = new HashSet(vc.getAlleles()); - // strip out the alternate allele if it's a ref call - if ( bestAFguess == 0 && UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { - myAlleles = new HashSet(1); - myAlleles.add(vc.getReference()); - } - - VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), endLoc, myAlleles); + VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), loc.getStop(), myAlleles); builder.genotypes(genotypes); builder.log10PError(phredScaledConfidence/-10.0); if ( ! passesCallThreshold(phredScaledConfidence) ) builder.filters(filter); builder.attributes(attributes); - builder.referenceBaseForIndel(refContext.getBase()); + if ( !limitedContext ) + builder.referenceBaseForIndel(refContext.getBase()); VariantContext vcCall = builder.make(); - if ( annotationEngine != null ) { + if ( annotationEngine != null && !limitedContext ) { // Note: we want to use the *unfiltered* and *unBAQed* context for the annotations ReadBackedPileup pileup = null; if (rawContext.hasExtendedEventPileup()) @@ -446,91 +454,6 @@ public class UnifiedGenotyperEngine { return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } - // A barebones entry point to the exact model when there is no tracker or stratified contexts available -- only GLs - public VariantCallContext calculateGenotypes(final VariantContext vc, final GenomeLoc loc, final GenotypeLikelihoodsCalculationModel.Model model) { - - // initialize the data for this thread if that hasn't been done yet - if ( afcm.get() == null ) { - log10AlleleFrequencyLikelihoods.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); - log10AlleleFrequencyPosteriors.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); - afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); - } - - // don't try to genotype too many alternate alleles - if ( vc.getAlternateAlleles().size() > UAC.MAX_ALTERNATE_ALLELES ) { - logger.warn("the Unified Genotyper is currently set to genotype at most " + UAC.MAX_ALTERNATE_ALLELES + " alternate alleles in a given context, but the context at " + vc.getChr() + ":" + vc.getStart() + " has " + vc.getAlternateAlleles().size() + " alternate alleles; see the --max_alternate_alleles argument"); - return null; - } - - // estimate our confidence in a reference call and return - if ( vc.getNSamples() == 0 ) - return null; - - // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) - clearAFarray(log10AlleleFrequencyLikelihoods.get()); - clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); - - // find the most likely frequency - int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[0]); - - // calculate p(f>0) - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()[0]); - double sum = 0.0; - for (int i = 1; i <= N; i++) - sum += normalizedPosteriors[i]; - double PofF = Math.min(sum, 1.0); // deal with precision errors - - double phredScaledConfidence; - if ( bestAFguess != 0 || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { - phredScaledConfidence = QualityUtils.phredScaleErrorRate(normalizedPosteriors[0]); - if ( Double.isInfinite(phredScaledConfidence) ) - phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0][0]; - } else { - phredScaledConfidence = QualityUtils.phredScaleErrorRate(PofF); - if ( Double.isInfinite(phredScaledConfidence) ) { - sum = 0.0; - for (int i = 1; i <= N; i++) { - if ( log10AlleleFrequencyPosteriors.get()[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) - break; - sum += log10AlleleFrequencyPosteriors.get()[0][i]; - } - phredScaledConfidence = (MathUtils.compareDoubles(sum, 0.0) == 0 ? 0 : -10.0 * sum); - } - } - - // return a null call if we don't pass the confidence cutoff or the most likely allele frequency is zero - if ( UAC.OutputMode != OUTPUT_MODE.EMIT_ALL_SITES && !passesEmitThreshold(phredScaledConfidence, bestAFguess) ) { - // technically, at this point our confidence in a reference call isn't accurately estimated - // because it didn't take into account samples with no data, so let's get a better estimate - return null; - } - - // create the genotypes - GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyLikelihoods.get(), bestAFguess); - - // *** note that calculating strand bias involves overwriting data structures, so we do that last - HashMap attributes = new HashMap(); - - int endLoc = calculateEndPos(vc.getAlleles(), vc.getReference(), loc); - - Set myAlleles = new HashSet(vc.getAlleles()); - // strip out the alternate allele if it's a ref call - if ( bestAFguess == 0 && UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { - myAlleles = new HashSet(1); - myAlleles.add(vc.getReference()); - } - - 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) { // TODO - temp fix until we can deal with extended events properly // for indels, stop location is one more than ref allele length From 0e9c2cefa2ce88f578bd7108b5586bcfe977b0ab Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Wed, 23 Nov 2011 06:42:03 -0500 Subject: [PATCH 240/380] testHardClipSoftClippedBases works with Matches and Deletions Insertions are a problem so cigar cases with "I" are commented out. The test works with multiple deletions and matches. This is still not a complete test. A lot of cigar test cases are commented out. Added insertions to ReadClipperUnitTest ReadClipper now tests for all indels. Signed-off-by: Mauricio Carneiro --- .../utils/clipreads/ReadClipperUnitTest.java | 270 +++++++++++++++--- 1 file changed, 238 insertions(+), 32 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 ecb5a6d33..a7944c9ad 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -25,15 +25,16 @@ package org.broadinstitute.sting.utils.clipreads; -import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.*; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; -import org.testng.annotations.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * Created by IntelliJ IDEA. @@ -44,7 +45,7 @@ import java.util.List; */ public class ReadClipperUnitTest extends BaseTest { - // TODO: Add error messages on failed tests + // TODO: exception testing, make cases that should fail will fail //int debug = 0; @@ -53,13 +54,27 @@ public class ReadClipperUnitTest extends BaseTest { final static String BASES = "ACTG"; final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 + private void testBaseQualCigar(GATKSAMRecord read, byte[] readBases, byte[] baseQuals, String cigar) { + // Because quals to char start at 33 for visibility + baseQuals = subtractToArray(baseQuals, 33); - public void testIfEqual( GATKSAMRecord read, byte[] readBases, String baseQuals, String cigar) { Assert.assertEquals(read.getReadBases(), readBases); - Assert.assertEquals(read.getBaseQualityString(), baseQuals); + Assert.assertEquals(read.getBaseQualities(), baseQuals); Assert.assertEquals(read.getCigarString(), cigar); } + private void testBaseQual(GATKSAMRecord read, byte[] readBases, byte[] baseQuals) { + // Because quals to chars start at 33 for visibility + baseQuals = subtractToArray(baseQuals, 33); + + if ( readBases.length > 0 && baseQuals.length > 0 ) { + Assert.assertEquals(read.getReadBases(), readBases); + Assert.assertEquals(read.getBaseQualities(), baseQuals); + } + else + Assert.assertTrue(read.isEmpty()); + } + public class testParameter { int inputStart; int inputStop; @@ -76,6 +91,18 @@ public class ReadClipperUnitTest extends BaseTest { } } + private byte[] subtractToArray (byte[] array, int n ) { + if ( array == null) + return null; + + byte[] output = new byte[array.length]; + + for ( int i = 0; i < array.length; i++) + output[i] = (byte) ( array[i] - n ); + + return output; + } + // What the test read looks like // Ref: 1 2 3 4 5 6 7 8 // Read: 0 1 2 3 - - - - @@ -127,10 +154,10 @@ public class ReadClipperUnitTest extends BaseTest { 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 ); + testBaseQualCigar(readClipper.hardClipByReadCoordinates(p.inputStart, p.inputStop), + BASES.substring(p.substringStart, p.substringStop).getBytes(), + QUALS.substring(p.substringStart, p.substringStop).getBytes(), + p.cigar); } } @@ -151,10 +178,10 @@ public class ReadClipperUnitTest extends BaseTest { 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 ); + testBaseQualCigar(readClipper.hardClipByReferenceCoordinates(p.inputStart, p.inputStop), + BASES.substring(p.substringStart, p.substringStop).getBytes(), + QUALS.substring(p.substringStart, p.substringStop).getBytes(), + p.cigar); } } @@ -174,10 +201,10 @@ public class ReadClipperUnitTest extends BaseTest { 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 ); + testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesLeftTail(p.inputStart), + BASES.substring(p.substringStart, p.substringStop).getBytes(), + QUALS.substring(p.substringStart, p.substringStop).getBytes(), + p.cigar); } } @@ -197,17 +224,17 @@ public class ReadClipperUnitTest extends BaseTest { 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 ); + testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesRightTail(p.inputStop), + BASES.substring(p.substringStart, p.substringStop).getBytes(), + QUALS.substring(p.substringStart, p.substringStop).getBytes(), + p.cigar); } } - @Test ( enabled = true ) // TODO This function is returning null reads + @Test ( enabled = true ) public void testHardClipLowQualEnds() { - + // Needs a thorough redesign logger.warn("Executing testHardClipByReferenceCoordinates"); //Clip whole read @@ -220,10 +247,10 @@ public class ReadClipperUnitTest extends BaseTest { 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 ); + testBaseQualCigar(readClipper.hardClipLowQualEnds((byte) p.inputStart), + BASES.substring(p.substringStart, p.substringStop).getBytes(), + QUALS.substring(p.substringStart, p.substringStop).getBytes(), + p.cigar); } /* todo find a better way to test lowqual tail clipping on both sides // Reverse Quals sequence @@ -237,7 +264,7 @@ public class ReadClipperUnitTest extends BaseTest { 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 ), + testBaseQualCigar( readClipper.hardClipLowQualEnds( (byte)p.inputStart ), BASES.substring(p.substringStart,p.substringStop).getBytes(), QUALS.substring(p.substringStart,p.substringStop), p.cigar ); @@ -245,15 +272,194 @@ public class ReadClipperUnitTest extends BaseTest { */ } - public class CigarReadMaker { + // public class ReadMaker { + //Should have functions that can + // make basic read + // make reads by cigar, + // and by quals, + // package in a readclipper + // This has been already done in the current class + // GATKSAMRecord read; + // ReadClipper readClipper; + + public String cycleString ( String string, int length ) { + String output = ""; + int cycles = ( length / string.length() ) + 1; + + for ( int i = 1; i < cycles; i++ ) + output += string; + + for ( int j = 0; output.length() < length; j++ ) + output += string.charAt( j % string.length() ); + + return output; } - @Test ( enabled = false ) + private void makeFromCigar( Cigar cigar ) { + readClipper = null; + read = null; + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); + read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, cigar.getReadLength()); + read.setReadBases(cycleString(BASES, cigar.getReadLength()).getBytes()); + read.setBaseQualityString( cycleString(QUALS, cigar.getReadLength() )); + read.setCigar(cigar); + readClipper = new ReadClipper(read); + } + + // } + + private Set generateCigars() { + + // This function generates every permutation of cigar strings we need. + + LinkedHashSet output = new LinkedHashSet(); + + List clippingOptionsStart = new LinkedList(); + clippingOptionsStart.add( new Cigar() ); + clippingOptionsStart.add( TextCigarCodec.getSingleton().decode("1H1S") ); + clippingOptionsStart.add( TextCigarCodec.getSingleton().decode("1S") ); + clippingOptionsStart.add( TextCigarCodec.getSingleton().decode("1H") ); + + LinkedList clippingOptionsEnd = new LinkedList(); + clippingOptionsEnd.add( new Cigar() ); + clippingOptionsEnd.add( TextCigarCodec.getSingleton().decode("1S1H") ); + clippingOptionsEnd.add( TextCigarCodec.getSingleton().decode("1S")); + clippingOptionsEnd.add( TextCigarCodec.getSingleton().decode("1H")); + + + LinkedList indelOptions = new LinkedList(); + indelOptions.add( new Cigar() ); + //indelOptions.add( TextCigarCodec.getSingleton().decode("1I1D")); + //indelOptions.add( TextCigarCodec.getSingleton().decode("1D1I") ); + indelOptions.add( TextCigarCodec.getSingleton().decode("1I") ); + indelOptions.add( TextCigarCodec.getSingleton().decode("1D") ); + + // Start With M as base CigarElements, M, + + LinkedList base = new LinkedList(); + base.add( TextCigarCodec.getSingleton().decode("1M")); + base.add( TextCigarCodec.getSingleton().decode("5M")); + base.add( TextCigarCodec.getSingleton().decode("25M")); + // Should indel be added as a base? + + // Nested loops W00t! + for ( Cigar Base : base) { + for ( Cigar indelStart: indelOptions) { + for ( Cigar indelEnd: indelOptions) { + for ( Cigar clipStart: clippingOptionsStart) { + for ( Cigar clipEnd: clippingOptionsEnd) { + // Create a list of Cigar Elements and construct Cigar + List CigarBuilder = new ArrayList(); + CigarBuilder.addAll(Base.getCigarElements()); + CigarBuilder.addAll(indelStart.getCigarElements()); + CigarBuilder.addAll(Base.getCigarElements()); + CigarBuilder.addAll(indelEnd.getCigarElements()); + CigarBuilder.addAll(clipEnd.getCigarElements()); + //CigarBuilder.addAll(0, indelStart.getCigarElements()); + CigarBuilder.addAll(0, clipStart.getCigarElements()); + //System.out.println( new Cigar( removeConsecutiveElements(CigarBuilder) ).toString() ); + output.add( new Cigar( removeConsecutiveElements(CigarBuilder) ) ); + + } + } + } + } + } + + return output; + } + + private List removeConsecutiveElements(List cigarBuilder) { + LinkedList output = new LinkedList(); + for ( CigarElement E: cigarBuilder ) { + if ( output.isEmpty() || output.getLast().getOperator() != E.getOperator()) + output.add(E); + } + return output; + } + + @Test ( enabled = true ) public void testHardClipSoftClippedBases() { // Generate a list of cigars to test + for ( Cigar cigar: generateCigars() ) { + //logger.warn("Testing Cigar: "+cigar.toString()); + makeFromCigar( cigar ); + + + try { + readClipper.hardClipLeadingInsertions(); + } + catch ( ReviewedStingException e ) {} + + + int clipStart = 0; + int clipEnd = 0; + boolean expectEmptyRead = false; + + List cigarElements = cigar.getCigarElements(); + int CigarListLength = cigarElements.size(); + + + // It will know what needs to be clipped based on the start and end of the string, hardclips and softclips + // are added to the amount to clip + if ( cigarElements.get(0).getOperator() == CigarOperator.HARD_CLIP ) { + //clipStart += cigarElements.get(0).getLength(); + if ( cigarElements.get(1).getOperator() == CigarOperator.SOFT_CLIP ) { + clipStart += cigarElements.get(1).getLength(); + // Check for leading indel + if ( cigarElements.get(2).getOperator() == CigarOperator.INSERTION ) { + expectEmptyRead = true; + } + } + // Check for leading indel + else if ( cigarElements.get(1).getOperator() == CigarOperator.INSERTION ) { + expectEmptyRead = true; + } + } + else if ( cigarElements.get(0).getOperator() == CigarOperator.SOFT_CLIP ) { + clipStart += cigarElements.get(0).getLength(); + // Check for leading indel + if ( cigarElements.get(1).getOperator() == CigarOperator.INSERTION ) { + expectEmptyRead = true; + } + } + //Check for leading indel + else if ( cigarElements.get(0).getOperator() == CigarOperator.INSERTION ) { + expectEmptyRead = true; + } + + if ( cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.HARD_CLIP ) { + //clipEnd += cigarElements.get(CigarListLength - 1).getLength(); + if ( cigarElements.get(CigarListLength - 2).getOperator() == CigarOperator.SOFT_CLIP ) + clipEnd += cigarElements.get(CigarListLength - 2).getLength(); + } + else if ( cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.SOFT_CLIP ) + clipEnd += cigarElements.get(CigarListLength - 1).getLength(); + + String readBases = readClipper.read.getReadString(); + String baseQuals = readClipper.read.getBaseQualityString(); + + // "*" is the default empty-sequence-string and for our test it needs to be changed to "" + if (readBases.equals("*")) + readBases = ""; + if (baseQuals.equals("*")) + baseQuals = ""; + + logger.warn(String.format("Testing cigar %s, expecting Base: %s and Qual: %s", + cigar.toString(), readBases.substring( clipStart, readBases.length() - clipEnd ), + baseQuals.substring( clipStart, baseQuals.length() - clipEnd ) ) ); + //if (expectEmptyRead) + // testBaseQual( readClipper.hardClipSoftClippedBases(), new byte[0], new byte[0] ); + //else + testBaseQual(readClipper.hardClipSoftClippedBases(), + readBases.substring( clipStart, readBases.length() - clipEnd ).getBytes(), + baseQuals.substring( clipStart, baseQuals.length() - clipEnd ).getBytes()); + logger.warn("Cigar: "+cigar.toString()+" PASSED!"); + } // We will use testParameter in the following way // Right tail, left tail, + } } \ No newline at end of file From 4cbd1f0dec66ec26204dcc4956a830d23527ce47 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Tue, 29 Nov 2011 18:30:23 -0500 Subject: [PATCH 241/380] Reorganized the testing code and created ClipReadsTestUtils Tests are more rigorous and includes many more test cases. We can tests custom cigars and the generated cigars. *Still needs debugging because code is not working. Created test classes to be used across several tests. Some cases are still commented out. Signed-off-by: Mauricio Carneiro --- .../utils/clipreads/CigarStringTestPair.java | 18 + .../utils/clipreads/ClipReadsTestUtils.java | 185 ++++++++ .../utils/clipreads/ClippingOpUnitTest.java | 69 +++ .../utils/clipreads/ReadClipperUnitTest.java | 413 +++++++----------- .../sting/utils/clipreads/TestParameter.java | 24 + 5 files changed, 442 insertions(+), 267 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java new file mode 100644 index 000000000..cc9021fae --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java @@ -0,0 +1,18 @@ +package org.broadinstitute.sting.utils.clipreads; + +/** + * Created by IntelliJ IDEA. + * User: roger + * Date: 11/29/11 + * Time: 4:53 PM + * To change this template use File | Settings | File Templates. + */ +public class CigarStringTestPair { + public String toTest; + public String expected; + + public CigarStringTestPair(String ToTest, String Expected) { + this.toTest = ToTest; + this.expected = Expected; + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java new file mode 100644 index 000000000..a5524e6f1 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java @@ -0,0 +1,185 @@ +package org.broadinstitute.sting.utils.clipreads; + +import net.sf.samtools.Cigar; +import net.sf.samtools.CigarElement; +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.TextCigarCodec; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.testng.Assert; + +import java.util.*; + +/** + * Created by IntelliJ IDEA. + * User: roger + * Date: 11/27/11 + * Time: 6:45 AM + * To change this template use File | Settings | File Templates. + */ +public class ClipReadsTestUtils { + //Should contain all the utils needed for tests to mass produce + //reads, cigars, and other needed classes + + final static String BASES = "ACTG"; + final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 + + public static void testBaseQualCigar(GATKSAMRecord read, byte[] readBases, byte[] baseQuals, String cigar) { + // Because quals to char start at 33 for visibility + baseQuals = subtractToArray(baseQuals, 33); + + Assert.assertEquals(read.getReadBases(), readBases); + Assert.assertEquals(read.getBaseQualities(), baseQuals); + Assert.assertEquals(read.getCigarString(), cigar); + } + + public static void testCigar(GATKSAMRecord read, String cigar) { + Assert.assertEquals(read.getCigarString(), cigar); + } + + public static void testBaseQual(GATKSAMRecord read, byte[] readBases, byte[] baseQuals) { + // Because quals to chars start at 33 for visibility + baseQuals = subtractToArray(baseQuals, 33); + + if (readBases.length > 0 && baseQuals.length > 0) { + Assert.assertEquals(read.getReadBases(), readBases); + Assert.assertEquals(read.getBaseQualities(), baseQuals); + } else + Assert.assertTrue(read.isEmpty()); + } + + private static byte[] subtractToArray(byte[] array, int n) { + if (array == null) + return null; + + byte[] output = new byte[array.length]; + + for (int i = 0; i < array.length; i++) + output[i] = (byte) (array[i] - n); + + return output; + } + + // What the test read looks like + // Ref: 10 11 12 13 14 15 16 17 + // Read: 0 1 2 3 - - - - + // -------------------------------- + // Bases: A C T G - - - - + // Quals: ! + 5 ? - - - - + + public static GATKSAMRecord makeRead() { + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); + GATKSAMRecord output = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 10, BASES.length()); + output.setReadBases(new String(BASES).getBytes()); + output.setBaseQualityString(new String(QUALS)); + + return output; + } + + public static GATKSAMRecord makeReadFromCigar(Cigar cigar) { + + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); + GATKSAMRecord output = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 10, cigar.getReadLength()); + output.setReadBases(cycleString(BASES, cigar.getReadLength()).getBytes()); + output.setBaseQualityString(cycleString(QUALS, cigar.getReadLength())); + output.setCigar(cigar); + + return output; + } + + private static String cycleString(String string, int length) { + String output = ""; + int cycles = (length / string.length()) + 1; + + for (int i = 1; i < cycles; i++) + output += string; + + for (int j = 0; output.length() < length; j++) + output += string.charAt(j % string.length()); + + return output; + } + + public static Set generateCigars() { + + // This function generates every permutation of cigar strings we need. + + LinkedHashSet output = new LinkedHashSet(); + + List clippingOptionsStart = new LinkedList(); + clippingOptionsStart.add(new Cigar()); + clippingOptionsStart.add(TextCigarCodec.getSingleton().decode("1H1S")); + clippingOptionsStart.add(TextCigarCodec.getSingleton().decode("1S")); + clippingOptionsStart.add(TextCigarCodec.getSingleton().decode("1H")); + + LinkedList clippingOptionsEnd = new LinkedList(); + clippingOptionsEnd.add(new Cigar()); + clippingOptionsEnd.add(TextCigarCodec.getSingleton().decode("1S1H")); + clippingOptionsEnd.add(TextCigarCodec.getSingleton().decode("1S")); + clippingOptionsEnd.add(TextCigarCodec.getSingleton().decode("1H")); + + + LinkedList indelOptions1 = new LinkedList(); + indelOptions1.add(new Cigar()); + //indelOptions1.add( TextCigarCodec.getSingleton().decode("1I1D")); + //indelOptions1.add( TextCigarCodec.getSingleton().decode("1D1I") ); + indelOptions1.add(TextCigarCodec.getSingleton().decode("1I")); + indelOptions1.add(TextCigarCodec.getSingleton().decode("1D")); + + LinkedList indelOptions2 = new LinkedList(); + indelOptions2.add(new Cigar()); + indelOptions2.add(TextCigarCodec.getSingleton().decode("1I")); + indelOptions2.add(null); + + + // Start With M as base CigarElements, M, + + LinkedList base = new LinkedList(); + base.add(TextCigarCodec.getSingleton().decode("1M")); + base.add(TextCigarCodec.getSingleton().decode("5M")); + base.add(TextCigarCodec.getSingleton().decode("25M")); + // Should indel be added as a base? + + // Nested loops W00t! + for (Cigar Base : base) { + for (Cigar indelStart : indelOptions1) { + for (Cigar indelEnd : indelOptions2) { + for (Cigar clipStart : clippingOptionsStart) { + for (Cigar clipEnd : clippingOptionsEnd) { + // Create a list of Cigar Elements and construct Cigar + List CigarBuilder = new ArrayList(); + // add starting clipping (H/S) + CigarBuilder.addAll(clipStart.getCigarElements()); + // add first base (M) + CigarBuilder.addAll(Base.getCigarElements()); + // add first indel + CigarBuilder.addAll(indelStart.getCigarElements()); + // add second base (M) + CigarBuilder.addAll(Base.getCigarElements()); + // add another indel or nothing (M) + if (indelEnd != null) + CigarBuilder.addAll(indelEnd.getCigarElements()); + // add final clipping (S/H) + CigarBuilder.addAll(clipEnd.getCigarElements()); + + + output.add(new Cigar(removeConsecutiveElements(CigarBuilder))); + + } + } + } + } + } + + return output; + } + + private static List removeConsecutiveElements(List cigarBuilder) { + LinkedList output = new LinkedList(); + for (CigarElement E : cigarBuilder) { + if (output.isEmpty() || output.getLast().getOperator() != E.getOperator()) + output.add(E); + } + return output; + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java new file mode 100644 index 000000000..719d04287 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java @@ -0,0 +1,69 @@ +package org.broadinstitute.sting.utils.clipreads; + +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.util.LinkedList; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: roger + * Date: 11/27/11 + * Time: 5:17 AM + * To change this template use File | Settings | File Templates. + */ +public class ClippingOpUnitTest extends BaseTest { + + ClippingOp clippingOp; + GATKSAMRecord read; + + @BeforeTest + public void init() { + read = ClipReadsTestUtils.makeRead(); + } + + @Test + private void testHardClip() { + 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 + testList.add(new TestParameter(0, 2, 3, 4, "3H1M"));//clip 3 bases at start + testList.add(new TestParameter(1, 3, 0, 1, "1M3H"));//clip 3 bases at end + + for (TestParameter p : testList) { + init(); + clippingOp = new ClippingOp(p.inputStart, p.inputStop); + logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.substringStart + "," + p.substringStop + "," + p.cigar); + ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.HARDCLIP_BASES, read), + ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), + p.cigar); + } + + } + + @Test + private void testSoftClip() { + List testList = new LinkedList(); + testList.add(new TestParameter(0, 0, -1, -1, "1S3M"));//clip 1 base at start + testList.add(new TestParameter(3, 3, -1, -1, "3M1S"));//clip 1 base at end + testList.add(new TestParameter(0, 1, -1, -1, "2S2M"));//clip 2 bases at start + testList.add(new TestParameter(2, 3, -1, -1, "2M2S"));//clip 2 bases at end + testList.add(new TestParameter(0, 2, -1, -1, "3S1M"));//clip 3 bases at start + testList.add(new TestParameter(1, 3, -1, -1, "1M3S"));//clip 3 bases at end + + for (TestParameter p : testList) { + init(); + clippingOp = new ClippingOp(p.inputStart, p.inputStop); + logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.cigar); + ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.SOFTCLIP_BASES, read), + ClipReadsTestUtils.BASES.getBytes(), ClipReadsTestUtils.QUALS.getBytes(), p.cigar); + } + + } +} 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 a7944c9ad..73ba5c12d 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -25,16 +25,19 @@ package org.broadinstitute.sting.utils.clipreads; -import net.sf.samtools.*; +import net.sf.samtools.Cigar; +import net.sf.samtools.CigarElement; +import net.sf.samtools.CigarOperator; +import net.sf.samtools.TextCigarCodec; import org.broadinstitute.sting.BaseTest; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.ReadUtils; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.*; +import java.util.LinkedList; +import java.util.List; /** * Created by IntelliJ IDEA. @@ -47,212 +50,205 @@ public class ReadClipperUnitTest extends BaseTest { // TODO: exception testing, make cases that should fail will fail - //int debug = 0; + // TODO: add indels to all test cases - GATKSAMRecord read, expected; ReadClipper readClipper; - final static String BASES = "ACTG"; - final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 - - private void testBaseQualCigar(GATKSAMRecord read, byte[] readBases, byte[] baseQuals, String cigar) { - // Because quals to char start at 33 for visibility - baseQuals = subtractToArray(baseQuals, 33); - - Assert.assertEquals(read.getReadBases(), readBases); - Assert.assertEquals(read.getBaseQualities(), baseQuals); - Assert.assertEquals(read.getCigarString(), cigar); - } - - private void testBaseQual(GATKSAMRecord read, byte[] readBases, byte[] baseQuals) { - // Because quals to chars start at 33 for visibility - baseQuals = subtractToArray(baseQuals, 33); - - if ( readBases.length > 0 && baseQuals.length > 0 ) { - Assert.assertEquals(read.getReadBases(), readBases); - Assert.assertEquals(read.getBaseQualities(), baseQuals); - } - else - Assert.assertTrue(read.isEmpty()); - } - - 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; - } - } - - private byte[] subtractToArray (byte[] array, int n ) { - if ( array == null) - return null; - - byte[] output = new byte[array.length]; - - for ( int i = 0; i < array.length; i++) - output[i] = (byte) ( array[i] - n ); - - return output; - } - - // 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()); + readClipper = new ReadClipper(ClipReadsTestUtils.makeRead()); } - @Test ( enabled = true ) + @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())); + Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(10, 10), new GATKSAMRecord(readClipper.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"); + ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipBothEndsByReferenceCoordinates(10, 13), + ClipReadsTestUtils.BASES.substring(1, 3).getBytes(), ClipReadsTestUtils.QUALS.substring(1, 3).getBytes(), + "1H2M1H"); + List cigarStringTestPairs = new LinkedList(); + cigarStringTestPairs.add(new CigarStringTestPair("5M1D1M2I4M5I6M1D3M2I100M", "1H4M1D1M2I4M5I6M1D3M2I99M1H")); + //cigarStringTestPairs.add( new CigarStringTestPair("5M1I1M2I1M","1H4M1I1M2I1H")); + cigarStringTestPairs.add(new CigarStringTestPair("1S1M1I1M1I1M1I1M1I1M1I1M1S", "1H1M1I1M1I1M1I1M1I1M1I1M1H")); + cigarStringTestPairs.add(new CigarStringTestPair("1S1M1D1M1D1M1D1M1D1M1D1M1S", "1H1M1D1M1D1M1D1M1D1M1D1M1H")); + + for (CigarStringTestPair pair : cigarStringTestPairs) { + readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar(TextCigarCodec.getSingleton().decode(pair.toTest))); + ClipReadsTestUtils.testCigar(readClipper.hardClipBothEndsByReferenceCoordinates( + ReadUtils.getRefCoordSoftUnclippedStart(readClipper.read), + ReadUtils.getRefCoordSoftUnclippedEnd(readClipper.read)), + pair.expected); + } + /* + for ( Cigar cigar: ClipReadsTestUtils.generateCigars() ) { + // The read has to be long enough to clip one base from each side + // This also filters a lot of cigars + if ( cigar.getReadLength() > 26 ) { + readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar( cigar )); + System.out.println( "Testing Cigar: "+cigar.toString() ) ; + //cigar length reference plus soft clip + + ClipReadsTestUtils.testBaseQual( + readClipper.hardClipBothEndsByReferenceCoordinates( + ReadUtils.getRefCoordSoftUnclippedStart(readClipper.read), + ReadUtils.getRefCoordSoftUnclippedEnd(readClipper.read) ), + readClipper.read.getReadString().substring(1, (cigar.getReadLength() - 1)).getBytes(), + readClipper.read.getBaseQualityString().substring(1, (cigar.getReadLength() - 1)).getBytes()); + } + } + */ } - @Test ( enabled = true ) + @Test(enabled = true) public void testHardClipByReadCoordinates() { logger.warn("Executing testHardClipByReadCoordinates"); //Clip whole read - Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); + Assert.assertEquals(readClipper.hardClipByReadCoordinates(0, 3), new GATKSAMRecord(readClipper.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 + 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 ) { + for (TestParameter p : testList) { init(); //logger.warn("Testing Parameters: " + p.inputStart+","+p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); - testBaseQualCigar(readClipper.hardClipByReadCoordinates(p.inputStart, p.inputStop), - BASES.substring(p.substringStart, p.substringStop).getBytes(), - QUALS.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReadCoordinates(p.inputStart, p.inputStop), + ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), p.cigar); } } - @Test ( enabled = true ) + @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())); + Assert.assertEquals(readClipper.hardClipByReferenceCoordinates(10, 13), new GATKSAMRecord(readClipper.read.getHeader())); - 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 + List testList = new LinkedList(); + testList.add(new TestParameter(-1, 10, 1, 4, "1H3M"));//clip 1 base at start + testList.add(new TestParameter(13, -1, 0, 3, "3M1H"));//clip 1 base at end + testList.add(new TestParameter(-1, 11, 2, 4, "2H2M"));//clip 2 bases at start + testList.add(new TestParameter(12, -1, 0, 2, "2M2H"));//clip 2 bases at end - for ( testParameter p : testList ) { + for (TestParameter p : testList) { init(); //logger.warn("Testing Parameters: " + p.inputStart+","+p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); - testBaseQualCigar(readClipper.hardClipByReferenceCoordinates(p.inputStart, p.inputStop), - BASES.substring(p.substringStart, p.substringStop).getBytes(), - QUALS.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReferenceCoordinates(p.inputStart, p.inputStop), + ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), p.cigar); } + List cigarStringTestPairs = new LinkedList(); + cigarStringTestPairs.add(new CigarStringTestPair("5M1D1M2I4M5I6M1D3M2I100M", "1H4M1D1M2I4M5I6M1D3M2I100M")); + //cigarStringTestPairs.add( new CigarStringTestPair("5M1I1M2I1M","1H4M1I1M2I1M")); + cigarStringTestPairs.add(new CigarStringTestPair("1S1M1I1M1I1M1I1M1I1M1I1M1S", "1H1M1I1M1I1M1I1M1I1M1I1M1S")); + cigarStringTestPairs.add(new CigarStringTestPair("1S1M1D1M1D1M1D1M1D1M1D1M1S", "1H1M1D1M1D1M1D1M1D1M1D1M1S")); + + //Clips only first base + for (CigarStringTestPair pair : cigarStringTestPairs) { + readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar(TextCigarCodec.getSingleton().decode(pair.toTest))); + ClipReadsTestUtils.testCigar(readClipper.hardClipByReadCoordinates(0, 0), pair.expected); + } + /* + for ( Cigar cigar: ClipReadsTestUtils.generateCigars() ) { + // The read has to be long enough to clip one base + // This also filters a lot of cigars + if ( cigar.getReadLength() > 26 ) { + readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar( cigar )); + System.out.println( "Testing Cigar: "+cigar.toString() ) ; + //cigar length reference plus soft clip + + // Clip first read + ClipReadsTestUtils.testBaseQual( + readClipper.hardClipByReadCoordinates(0,0), + readClipper.read.getReadString().substring(1, cigar.getReadLength()).getBytes(), + readClipper.read.getBaseQualityString().substring(1, cigar.getReadLength()).getBytes()); + } + } + */ } - @Test ( enabled = true ) + @Test(enabled = true) public void testHardClipByReferenceCoordinatesLeftTail() { init(); logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); //Clip whole read - Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(4), new GATKSAMRecord(read.getHeader())); + Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(13), new GATKSAMRecord(readClipper.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 + List testList = new LinkedList(); + testList.add(new TestParameter(10, -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 ) { + for (TestParameter p : testList) { init(); //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); - testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesLeftTail(p.inputStart), - BASES.substring(p.substringStart, p.substringStop).getBytes(), - QUALS.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesLeftTail(p.inputStart), + ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), p.cigar); } } - @Test ( enabled = true ) + @Test(enabled = true) public void testHardClipByReferenceCoordinatesRightTail() { init(); logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); //Clip whole read - Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(1), new GATKSAMRecord(read.getHeader())); + Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(10), new GATKSAMRecord(readClipper.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 + List testList = new LinkedList(); + testList.add(new TestParameter(-1, 13, 0, 3, "3M1H"));//clip 1 base at end + testList.add(new TestParameter(-1, 12, 0, 2, "2M2H"));//clip 2 bases at end - for ( testParameter p : testList ) { + for (TestParameter p : testList) { init(); //logger.warn("Testing Parameters: " + p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); - testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesRightTail(p.inputStop), - BASES.substring(p.substringStart, p.substringStop).getBytes(), - QUALS.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesRightTail(p.inputStop), + ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), p.cigar); } } - @Test ( enabled = true ) + @Test(enabled = true) public void testHardClipLowQualEnds() { // Needs a thorough redesign logger.warn("Executing testHardClipByReferenceCoordinates"); //Clip whole read - Assert.assertEquals(readClipper.hardClipLowQualEnds((byte)64), new GATKSAMRecord(read.getHeader())); + Assert.assertEquals(readClipper.hardClipLowQualEnds((byte) 64), new GATKSAMRecord(readClipper.read.getHeader())); - 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 + 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 ) { + for (TestParameter p : testList) { init(); //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); - testBaseQualCigar(readClipper.hardClipLowQualEnds((byte) p.inputStart), - BASES.substring(p.substringStart, p.substringStop).getBytes(), - QUALS.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipLowQualEnds((byte) p.inputStart), + ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), + ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), p.cigar); } - /* todo find a better way to test lowqual tail clipping on both sides + /* todo find a better way to test lowqual tail clipping on both sides // Reverse Quals sequence readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 @@ -272,127 +268,13 @@ public class ReadClipperUnitTest extends BaseTest { */ } - // public class ReadMaker { - //Should have functions that can - // make basic read - // make reads by cigar, - // and by quals, - // package in a readclipper - // This has been already done in the current class - // GATKSAMRecord read; - // ReadClipper readClipper; - - public String cycleString ( String string, int length ) { - String output = ""; - int cycles = ( length / string.length() ) + 1; - - for ( int i = 1; i < cycles; i++ ) - output += string; - - for ( int j = 0; output.length() < length; j++ ) - output += string.charAt( j % string.length() ); - - return output; - - } - - private void makeFromCigar( Cigar cigar ) { - readClipper = null; - read = null; - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); - read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, cigar.getReadLength()); - read.setReadBases(cycleString(BASES, cigar.getReadLength()).getBytes()); - read.setBaseQualityString( cycleString(QUALS, cigar.getReadLength() )); - read.setCigar(cigar); - readClipper = new ReadClipper(read); - } - - // } - - private Set generateCigars() { - - // This function generates every permutation of cigar strings we need. - - LinkedHashSet output = new LinkedHashSet(); - - List clippingOptionsStart = new LinkedList(); - clippingOptionsStart.add( new Cigar() ); - clippingOptionsStart.add( TextCigarCodec.getSingleton().decode("1H1S") ); - clippingOptionsStart.add( TextCigarCodec.getSingleton().decode("1S") ); - clippingOptionsStart.add( TextCigarCodec.getSingleton().decode("1H") ); - - LinkedList clippingOptionsEnd = new LinkedList(); - clippingOptionsEnd.add( new Cigar() ); - clippingOptionsEnd.add( TextCigarCodec.getSingleton().decode("1S1H") ); - clippingOptionsEnd.add( TextCigarCodec.getSingleton().decode("1S")); - clippingOptionsEnd.add( TextCigarCodec.getSingleton().decode("1H")); - - - LinkedList indelOptions = new LinkedList(); - indelOptions.add( new Cigar() ); - //indelOptions.add( TextCigarCodec.getSingleton().decode("1I1D")); - //indelOptions.add( TextCigarCodec.getSingleton().decode("1D1I") ); - indelOptions.add( TextCigarCodec.getSingleton().decode("1I") ); - indelOptions.add( TextCigarCodec.getSingleton().decode("1D") ); - - // Start With M as base CigarElements, M, - - LinkedList base = new LinkedList(); - base.add( TextCigarCodec.getSingleton().decode("1M")); - base.add( TextCigarCodec.getSingleton().decode("5M")); - base.add( TextCigarCodec.getSingleton().decode("25M")); - // Should indel be added as a base? - - // Nested loops W00t! - for ( Cigar Base : base) { - for ( Cigar indelStart: indelOptions) { - for ( Cigar indelEnd: indelOptions) { - for ( Cigar clipStart: clippingOptionsStart) { - for ( Cigar clipEnd: clippingOptionsEnd) { - // Create a list of Cigar Elements and construct Cigar - List CigarBuilder = new ArrayList(); - CigarBuilder.addAll(Base.getCigarElements()); - CigarBuilder.addAll(indelStart.getCigarElements()); - CigarBuilder.addAll(Base.getCigarElements()); - CigarBuilder.addAll(indelEnd.getCigarElements()); - CigarBuilder.addAll(clipEnd.getCigarElements()); - //CigarBuilder.addAll(0, indelStart.getCigarElements()); - CigarBuilder.addAll(0, clipStart.getCigarElements()); - //System.out.println( new Cigar( removeConsecutiveElements(CigarBuilder) ).toString() ); - output.add( new Cigar( removeConsecutiveElements(CigarBuilder) ) ); - - } - } - } - } - } - - return output; - } - - private List removeConsecutiveElements(List cigarBuilder) { - LinkedList output = new LinkedList(); - for ( CigarElement E: cigarBuilder ) { - if ( output.isEmpty() || output.getLast().getOperator() != E.getOperator()) - output.add(E); - } - return output; - } - - @Test ( enabled = true ) + @Test(enabled = true) public void testHardClipSoftClippedBases() { // Generate a list of cigars to test - for ( Cigar cigar: generateCigars() ) { + for (Cigar cigar : ClipReadsTestUtils.generateCigars()) { //logger.warn("Testing Cigar: "+cigar.toString()); - makeFromCigar( cigar ); - - - try { - readClipper.hardClipLeadingInsertions(); - } - catch ( ReviewedStingException e ) {} - + readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar(cigar)); int clipStart = 0; int clipEnd = 0; @@ -401,41 +283,38 @@ public class ReadClipperUnitTest extends BaseTest { List cigarElements = cigar.getCigarElements(); int CigarListLength = cigarElements.size(); - // It will know what needs to be clipped based on the start and end of the string, hardclips and softclips // are added to the amount to clip - if ( cigarElements.get(0).getOperator() == CigarOperator.HARD_CLIP ) { + if (cigarElements.get(0).getOperator() == CigarOperator.HARD_CLIP) { //clipStart += cigarElements.get(0).getLength(); - if ( cigarElements.get(1).getOperator() == CigarOperator.SOFT_CLIP ) { + if (cigarElements.get(1).getOperator() == CigarOperator.SOFT_CLIP) { clipStart += cigarElements.get(1).getLength(); // Check for leading indel - if ( cigarElements.get(2).getOperator() == CigarOperator.INSERTION ) { + if (cigarElements.get(2).getOperator() == CigarOperator.INSERTION) { expectEmptyRead = true; } } // Check for leading indel - else if ( cigarElements.get(1).getOperator() == CigarOperator.INSERTION ) { + else if (cigarElements.get(1).getOperator() == CigarOperator.INSERTION) { expectEmptyRead = true; } - } - else if ( cigarElements.get(0).getOperator() == CigarOperator.SOFT_CLIP ) { + } else if (cigarElements.get(0).getOperator() == CigarOperator.SOFT_CLIP) { clipStart += cigarElements.get(0).getLength(); // Check for leading indel - if ( cigarElements.get(1).getOperator() == CigarOperator.INSERTION ) { + if (cigarElements.get(1).getOperator() == CigarOperator.INSERTION) { expectEmptyRead = true; } } //Check for leading indel - else if ( cigarElements.get(0).getOperator() == CigarOperator.INSERTION ) { - expectEmptyRead = true; - } - - if ( cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.HARD_CLIP ) { - //clipEnd += cigarElements.get(CigarListLength - 1).getLength(); - if ( cigarElements.get(CigarListLength - 2).getOperator() == CigarOperator.SOFT_CLIP ) - clipEnd += cigarElements.get(CigarListLength - 2).getLength(); + else if (cigarElements.get(0).getOperator() == CigarOperator.INSERTION) { + expectEmptyRead = true; } - else if ( cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.SOFT_CLIP ) + + if (cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.HARD_CLIP) { + //clipEnd += cigarElements.get(CigarListLength - 1).getLength(); + if (cigarElements.get(CigarListLength - 2).getOperator() == CigarOperator.SOFT_CLIP) + clipEnd += cigarElements.get(CigarListLength - 2).getLength(); + } else if (cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.SOFT_CLIP) clipEnd += cigarElements.get(CigarListLength - 1).getLength(); String readBases = readClipper.read.getReadString(); @@ -448,15 +327,15 @@ public class ReadClipperUnitTest extends BaseTest { baseQuals = ""; logger.warn(String.format("Testing cigar %s, expecting Base: %s and Qual: %s", - cigar.toString(), readBases.substring( clipStart, readBases.length() - clipEnd ), - baseQuals.substring( clipStart, baseQuals.length() - clipEnd ) ) ); + cigar.toString(), readBases.substring(clipStart, readBases.length() - clipEnd), + baseQuals.substring(clipStart, baseQuals.length() - clipEnd))); //if (expectEmptyRead) // testBaseQual( readClipper.hardClipSoftClippedBases(), new byte[0], new byte[0] ); //else - testBaseQual(readClipper.hardClipSoftClippedBases(), - readBases.substring( clipStart, readBases.length() - clipEnd ).getBytes(), - baseQuals.substring( clipStart, baseQuals.length() - clipEnd ).getBytes()); - logger.warn("Cigar: "+cigar.toString()+" PASSED!"); + ClipReadsTestUtils.testBaseQual(readClipper.hardClipSoftClippedBases(), + readBases.substring(clipStart, readBases.length() - clipEnd).getBytes(), + baseQuals.substring(clipStart, baseQuals.length() - clipEnd).getBytes()); + logger.warn("Cigar: " + cigar.toString() + " PASSED!"); } // We will use testParameter in the following way // Right tail, left tail, diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java new file mode 100644 index 000000000..155fe094e --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java @@ -0,0 +1,24 @@ +package org.broadinstitute.sting.utils.clipreads; + +/** + * Created by IntelliJ IDEA. + * User: roger + * Date: 11/28/11 + * Time: 4:07 PM + * To change this template use File | Settings | File Templates. + */ +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; + } +} From 8475328b2c825c412b1c23a2ab31c6c1f2b5784c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 9 Dec 2011 11:53:12 -0500 Subject: [PATCH 242/380] Turning off test that breaks read clipper until we define what is the desired behavior for clipping this particular case. --- .../sting/utils/clipreads/ReadClipperUnitTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 73ba5c12d..ff33e3184 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -268,7 +268,7 @@ public class ReadClipperUnitTest extends BaseTest { */ } - @Test(enabled = true) + @Test(enabled = false) public void testHardClipSoftClippedBases() { // Generate a list of cigars to test From 364f1a030b5e55538e53c293e3015dbbb4d8b081 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 9 Dec 2011 14:25:28 -0500 Subject: [PATCH 243/380] Plumbing added so that the UG engine can handle multiple alleles and they can successfully be genotyped. Alleles that aren't likely are not allowed to be used when assigning genotypes, but otherwise the greedy PL-based approach is what is used. Moved assign genotypes code to UG engine since it has nothing to do with the Exact model. Still have some TODOs in here before I can push this out to everyone. --- .../AlleleFrequencyCalculationModel.java | 12 -- .../genotyper/ExactAFCalculationModel.java | 90 +--------- .../genotyper/UnifiedGenotyperEngine.java | 164 +++++++++++++++--- 3 files changed, 145 insertions(+), 121 deletions(-) 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 01e696237..7d3e7047d 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 @@ -72,16 +72,4 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { double[][] log10AlleleFrequencyPriors, double[][] log10AlleleFrequencyLikelihoods, double[][] log10AlleleFrequencyPosteriors); - - /** - * Can be overridden by concrete subclasses - * @param vc variant context with genotype likelihoods - * @param log10AlleleFrequencyLikelihoods allele frequency results - * @param AFofMaxLikelihood allele frequency of max likelihood - * - * @return calls - */ - protected abstract GenotypesContext assignGenotypes(VariantContext vc, - double[][] log10AlleleFrequencyLikelihoods, - 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 dccc2c02c..8fbc9f178 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 @@ -27,9 +27,7 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; import org.broadinstitute.sting.utils.MathUtils; -import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.*; import java.io.PrintStream; @@ -40,9 +38,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { private final static boolean DEBUG = false; private final static double MAX_LOG10_ERROR_TO_STOP_EARLY = 6; // we want the calculation to be accurate to 1 / 10^6 - 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 static final List NO_CALL_ALLELES = Arrays.asList(Allele.NO_CALL, Allele.NO_CALL); private final boolean USE_MULTI_ALLELIC_CALCULATION; @@ -73,7 +68,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { if ( sample.hasLikelihoods() ) { double[] gls = sample.getLikelihoods().getAsVector(); - if (MathUtils.sum(gls) < SUM_GL_THRESH_NOCALL) + if ( MathUtils.sum(gls) < UnifiedGenotyperEngine.SUM_GL_THRESH_NOCALL ) genotypeLikelihoods.add(gls); } } @@ -584,87 +579,4 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return coeff; } - - /** - * Can be overridden by concrete subclasses - * @param vc variant context with genotype likelihoods - * @param log10AlleleFrequencyLikelihoods likelihoods - * @param AFofMaxLikelihood allele frequency of max likelihood - * - * @return calls - */ - public GenotypesContext assignGenotypes(VariantContext vc, - double[][] log10AlleleFrequencyLikelihoods, - int AFofMaxLikelihood) { - if ( !vc.isVariant() ) - throw new UserException("The VCF record passed in does not contain an ALT allele at " + vc.getChr() + ":" + vc.getStart()); - - GenotypesContext GLs = vc.getGenotypes(); - double[][] pathMetricArray = new double[GLs.size()+1][AFofMaxLikelihood+1]; - - ArrayList sampleIndices = new ArrayList(); - - // todo - optimize initialization - for (int k=0; k <= AFofMaxLikelihood; k++) - for (int j=0; j <= GLs.size(); j++) - pathMetricArray[j][k] = -1e30; - - pathMetricArray[0][0] = 0.0; - - sampleIndices.addAll(GLs.getSampleNamesOrderedByName()); - - GenotypesContext calls = GenotypesContext.create(); - - for (int k = GLs.size(); k > 0; k--) { - int bestGTguess; - String sample = sampleIndices.get(k-1); - Genotype g = GLs.get(sample); - if ( !g.hasLikelihoods() ) - continue; - - ArrayList myAlleles = new ArrayList(); - - double[] likelihoods = g.getLikelihoods().getAsVector(); - - // if there is no mass on the likelihoods, then just no-call the sample - if ( MathUtils.sum(likelihoods) > SUM_GL_THRESH_NOCALL ) { - calls.add(new Genotype(g.getSampleName(), NO_CALL_ALLELES, Genotype.NO_LOG10_PERROR, null, null, false)); - continue; - } - - bestGTguess = Utils.findIndexOfMaxEntry(likelihoods); - - // 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)); - } - - return calls; - } } 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 4821b3eb8..918f83514 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 @@ -59,6 +59,9 @@ public class UnifiedGenotyperEngine { EMIT_ALL_SITES } + protected static final List NO_CALL_ALLELES = Arrays.asList(Allele.NO_CALL, Allele.NO_CALL); + protected static final 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. + // the unified argument collection private final UnifiedArgumentCollection UAC; public UnifiedArgumentCollection getUAC() { return UAC; } @@ -327,18 +330,21 @@ public class UnifiedGenotyperEngine { clearAFarray(log10AlleleFrequencyPosteriors.get()); afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); - // find the most likely frequency - int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[0]); + // TODO -- this is not the right thing mathematically to do! In a case of B=1,C=0 the likelihoods would get added to both AC=0 and AC=1 + double[] collapsedPosteriors = collapseAFarrays(log10AlleleFrequencyPosteriors.get(), vc.getAlternateAlleles().size()); + + // is the most likely frequency conformation AC=0 for all alternate alleles? + boolean bestGuessIsRef = MathUtils.maxElementIndex(collapsedPosteriors) == 0; // calculate p(f>0) - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()[0]); + double[] normalizedPosteriors = MathUtils.normalizeFromLog10(collapsedPosteriors); double sum = 0.0; for (int i = 1; i <= N; i++) sum += normalizedPosteriors[i]; double PofF = Math.min(sum, 1.0); // deal with precision errors double phredScaledConfidence; - if ( bestAFguess != 0 || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { + if ( !bestGuessIsRef || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { phredScaledConfidence = QualityUtils.phredScaleErrorRate(normalizedPosteriors[0]); if ( Double.isInfinite(phredScaledConfidence) ) phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0][0]; @@ -356,21 +362,46 @@ public class UnifiedGenotyperEngine { } // return a null call if we don't pass the confidence cutoff or the most likely allele frequency is zero - if ( UAC.OutputMode != OUTPUT_MODE.EMIT_ALL_SITES && !passesEmitThreshold(phredScaledConfidence, bestAFguess) ) { + if ( UAC.OutputMode != OUTPUT_MODE.EMIT_ALL_SITES && !passesEmitThreshold(phredScaledConfidence, bestGuessIsRef) ) { // technically, at this point our confidence in a reference call isn't accurately estimated // because it didn't take into account samples with no data, so let's get a better estimate return limitedContext ? null : estimateReferenceConfidence(vc, stratifiedContexts, getGenotypePriors(model).getHeterozygosity(), true, 1.0 - PofF); } - // strip out the alternate allele(s) if we're making a ref call - Set myAlleles = new HashSet(vc.getAlleles()); - if ( bestAFguess == 0 && UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { - myAlleles = new HashSet(1); + // strip out any alleles that aren't going to be used + Set myAlleles; + boolean[] altAllelesToUse = new boolean[vc.getAlternateAlleles().size()]; + if ( UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { + myAlleles = new HashSet(vc.getAlleles().size()); myAlleles.add(vc.getReference()); + + // if we're making a reference call then we keep just the ref allele, otherwise we need to determine which ones are okay + if ( !bestGuessIsRef ) { + for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { + if ( MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[i]) != 0 ) { + myAlleles.add(vc.getAlternateAllele(i)); + altAllelesToUse[i] = true; + } + } + } + } else { + // use all of the alleles if we are given them by the user + myAlleles = new HashSet(vc.getAlleles()); + for ( int i = 0; i < altAllelesToUse.length; i++ ) + altAllelesToUse[i] = true; } + // start constructing the resulting VC + GenomeLoc loc = genomeLocParser.createGenomeLoc(vc); + VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), loc.getStop(), myAlleles); + builder.log10PError(phredScaledConfidence/-10.0); + if ( ! passesCallThreshold(phredScaledConfidence) ) + builder.filters(filter); + if ( !limitedContext ) + builder.referenceBaseForIndel(refContext.getBase()); + // create the genotypes - GenotypesContext genotypes = afcm.get().assignGenotypes(vc, log10AlleleFrequencyLikelihoods.get(), bestAFguess); + GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse); // print out stats if we have a writer if ( verboseWriter != null && !limitedContext ) @@ -383,7 +414,7 @@ public class UnifiedGenotyperEngine { if ( !limitedContext && rawContext.hasPileupBeenDownsampled() ) attributes.put(VCFConstants.DOWNSAMPLED_KEY, true); - if ( UAC.COMPUTE_SLOD && !limitedContext && bestAFguess != 0 ) { + if ( UAC.COMPUTE_SLOD && !limitedContext && !bestGuessIsRef ) { //final boolean DEBUG_SLOD = false; // the overall lod @@ -428,15 +459,9 @@ public class UnifiedGenotyperEngine { attributes.put("SB", strandScore); } - GenomeLoc loc = genomeLocParser.createGenomeLoc(vc); - - VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), loc.getStop(), myAlleles); + // finish constructing the resulting VC builder.genotypes(genotypes); - builder.log10PError(phredScaledConfidence/-10.0); - if ( ! passesCallThreshold(phredScaledConfidence) ) builder.filters(filter); builder.attributes(attributes); - if ( !limitedContext ) - builder.referenceBaseForIndel(refContext.getBase()); VariantContext vcCall = builder.make(); if ( annotationEngine != null && !limitedContext ) { @@ -454,6 +479,21 @@ public class UnifiedGenotyperEngine { return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } + private static double[] collapseAFarrays(double[][] original, int numDimensions) { + int size = original[0].length; + double[] newArray = new double[size]; + for ( int i = 0; i < size; i++) + newArray[i] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; + + for ( int i = 0; i < numDimensions; i++ ) { + for ( int j = 0; j < size; j++ ) { + newArray[j] = ExactAFCalculationModel.approximateLog10SumLog10(newArray[j], original[i][j]); + } + } + + return newArray; + } + private int calculateEndPos(Collection alleles, Allele refAllele, GenomeLoc loc) { // TODO - temp fix until we can deal with extended events properly // for indels, stop location is one more than ref allele length @@ -634,8 +674,8 @@ public class UnifiedGenotyperEngine { verboseWriter.println(); } - protected boolean passesEmitThreshold(double conf, int bestAFguess) { - return (UAC.OutputMode == OUTPUT_MODE.EMIT_ALL_CONFIDENT_SITES || bestAFguess != 0) && conf >= Math.min(UAC.STANDARD_CONFIDENCE_FOR_CALLING, UAC.STANDARD_CONFIDENCE_FOR_EMITTING); + protected boolean passesEmitThreshold(double conf, boolean bestGuessIsRef) { + return (UAC.OutputMode == OUTPUT_MODE.EMIT_ALL_CONFIDENT_SITES || !bestGuessIsRef) && conf >= Math.min(UAC.STANDARD_CONFIDENCE_FOR_CALLING, UAC.STANDARD_CONFIDENCE_FOR_EMITTING); } protected boolean passesCallThreshold(double conf) { @@ -780,4 +820,88 @@ public class UnifiedGenotyperEngine { return vc; } + + /** + * @param vc variant context with genotype likelihoods + * @param allelesToUse bit vector describing which alternate alleles from the vc are okay to use + * + * @return genotypes + */ + public GenotypesContext assignGenotypes(VariantContext vc, + boolean[] allelesToUse) { + + final GenotypesContext GLs = vc.getGenotypes(); + + final List sampleIndices = GLs.getSampleNamesOrderedByName(); + + final GenotypesContext calls = GenotypesContext.create(); + + for ( int k = GLs.size() - 1; k >= 0; k-- ) { + final String sample = sampleIndices.get(k); + final Genotype g = GLs.get(sample); + if ( !g.hasLikelihoods() ) + continue; + + final double[] likelihoods = g.getLikelihoods().getAsVector(); + + // if there is no mass on the likelihoods, then just no-call the sample + if ( MathUtils.sum(likelihoods) > SUM_GL_THRESH_NOCALL ) { + calls.add(new Genotype(g.getSampleName(), NO_CALL_ALLELES, Genotype.NO_LOG10_PERROR, null, null, false)); + continue; + } + + // genotype likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. + // so e.g. with 2 alt alleles the likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. + + final int numAltAlleles = allelesToUse.length; + + // start with the assumption that the ideal genotype is homozygous reference + Allele maxAllele1 = vc.getReference(), maxAllele2 = vc.getReference(); + double maxLikelihoodSeen = likelihoods[0]; + int indexOfMax = 0; + + // keep track of some state + Allele firstAllele = vc.getReference(); + int subtractor = numAltAlleles + 1; + int subtractionsMade = 0; + + for ( int i = 1, PLindex = 1; i < likelihoods.length; i++, PLindex++ ) { + if ( PLindex == subtractor ) { + firstAllele = vc.getAlternateAllele(subtractionsMade); + PLindex -= subtractor; + subtractor--; + subtractionsMade++; + + // we can skip this allele if it's not usable + if ( !allelesToUse[subtractionsMade-1] ) { + i += subtractor - 1; + PLindex += subtractor - 1; + continue; + } + } + + // we don't care about the entry if we've already seen better + if ( likelihoods[i] <= maxLikelihoodSeen ) + continue; + + // if it's usable then update the alleles + int alleleIndex = subtractionsMade + PLindex - 1; + if ( allelesToUse[alleleIndex] ) { + maxAllele1 = firstAllele; + maxAllele2 = vc.getAlternateAllele(alleleIndex); + maxLikelihoodSeen = likelihoods[i]; + indexOfMax = i; + } + } + + ArrayList myAlleles = new ArrayList(); + myAlleles.add(maxAllele1); + myAlleles.add(maxAllele2); + + final double qual = GenotypeLikelihoods.getQualFromLikelihoods(indexOfMax, likelihoods); + calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); + } + + return calls; + } } From 044f211a30b427ab62f175095d6e604321b94223 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sat, 10 Dec 2011 23:57:14 -0500 Subject: [PATCH 244/380] Don't collapse likelihoods over all alt alleles - that's just not right. For now, the QUAL is calculated for just the most likely of the alt alleles; I need to think about the right way to handle this properly. --- .../genotyper/UnifiedGenotyperEngine.java | 72 +++++++++---------- .../UnifiedGenotyperIntegrationTest.java | 4 +- 2 files changed, 38 insertions(+), 38 deletions(-) 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 918f83514..3a86743de 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 @@ -330,14 +330,38 @@ public class UnifiedGenotyperEngine { clearAFarray(log10AlleleFrequencyPosteriors.get()); afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); - // TODO -- this is not the right thing mathematically to do! In a case of B=1,C=0 the likelihoods would get added to both AC=0 and AC=1 - double[] collapsedPosteriors = collapseAFarrays(log10AlleleFrequencyPosteriors.get(), vc.getAlternateAlleles().size()); - // is the most likely frequency conformation AC=0 for all alternate alleles? - boolean bestGuessIsRef = MathUtils.maxElementIndex(collapsedPosteriors) == 0; + boolean bestGuessIsRef = true; + + // which alternate allele has the highest MLE AC? + int indexOfHighestAlt = -1; + int alleleCountOfHighestAlt = -1; + + // determine which alternate alleles have AF>0 + boolean[] altAllelesToUse = new boolean[vc.getAlternateAlleles().size()]; + for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { + int indexOfBestAC = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[i]); + + // if the most likely AC is not 0, then this is a good alternate allele to use + if ( indexOfBestAC != 0 ) { + altAllelesToUse[i] = true; + bestGuessIsRef = false; + } + // if in GENOTYPE_GIVEN_ALLELES mode, we still want to allow the use of a poor allele + else if ( UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { + altAllelesToUse[i] = true; + } + + // keep track of the "best" alternate allele to use + if ( indexOfBestAC > alleleCountOfHighestAlt) { + alleleCountOfHighestAlt = indexOfBestAC; + indexOfHighestAlt = i; + } + } // calculate p(f>0) - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(collapsedPosteriors); + // TODO -- right now we just calculate it for the alt allele with highest AF, but the likelihoods need to be combined correctly over all AFs + double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()[indexOfHighestAlt]); double sum = 0.0; for (int i = 1; i <= N; i++) sum += normalizedPosteriors[i]; @@ -368,27 +392,18 @@ public class UnifiedGenotyperEngine { return limitedContext ? null : estimateReferenceConfidence(vc, stratifiedContexts, getGenotypePriors(model).getHeterozygosity(), true, 1.0 - PofF); } - // strip out any alleles that aren't going to be used - Set myAlleles; - boolean[] altAllelesToUse = new boolean[vc.getAlternateAlleles().size()]; + // strip out any alleles that aren't going to be used in the VariantContext + List myAlleles; if ( UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { - myAlleles = new HashSet(vc.getAlleles().size()); + myAlleles = new ArrayList(vc.getAlleles().size()); myAlleles.add(vc.getReference()); - - // if we're making a reference call then we keep just the ref allele, otherwise we need to determine which ones are okay - if ( !bestGuessIsRef ) { - for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { - if ( MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[i]) != 0 ) { - myAlleles.add(vc.getAlternateAllele(i)); - altAllelesToUse[i] = true; - } - } + for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { + if ( altAllelesToUse[i] ) + myAlleles.add(vc.getAlternateAllele(i)); } } else { // use all of the alleles if we are given them by the user - myAlleles = new HashSet(vc.getAlleles()); - for ( int i = 0; i < altAllelesToUse.length; i++ ) - altAllelesToUse[i] = true; + myAlleles = vc.getAlleles(); } // start constructing the resulting VC @@ -479,21 +494,6 @@ public class UnifiedGenotyperEngine { return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } - private static double[] collapseAFarrays(double[][] original, int numDimensions) { - int size = original[0].length; - double[] newArray = new double[size]; - for ( int i = 0; i < size; i++) - newArray[i] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; - - for ( int i = 0; i < numDimensions; i++ ) { - for ( int j = 0; j < size; j++ ) { - newArray[j] = ExactAFCalculationModel.approximateLog10SumLog10(newArray[j], original[i][j]); - } - } - - return newArray; - } - private int calculateEndPos(Collection alleles, Allele refAllele, GenomeLoc loc) { // TODO - temp fix until we can deal with extended events properly // for indels, stop location is one more than ref allele length 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 c04b0085c..605d6051c 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 @@ -285,7 +285,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("75e49dff01763aff2984dc86a72eb229")); + Arrays.asList("98a4d1e1e0a363ba37518563ac6cbead")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -294,7 +294,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("8209a308d95659c6da7dab8733c736f9")); + Arrays.asList("915e7a3e7cbfd995dbc41fdd382d0d51")); executeTest("test MultiSample Phase1 indels with complicated records", spec4); } From 7c4b9338ad9cdcab164081b497d8aa86e2a6242a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sun, 11 Dec 2011 00:23:33 -0500 Subject: [PATCH 245/380] The old bi-allelic implementation of the Exact model has been completely deprecated - you can only use the multi-allelic implementation now. --- .../genotyper/ExactAFCalculationModel.java | 249 +++++++++--------- .../UnifiedGenotyperIntegrationTest.java | 6 +- 2 files changed, 127 insertions(+), 128 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 8fbc9f178..77a940dcf 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 @@ -39,12 +39,9 @@ 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 USE_MULTI_ALLELIC_CALCULATION; - protected ExactAFCalculationModel(UnifiedArgumentCollection UAC, int N, Logger logger, PrintStream verboseWriter) { super(UAC, N, logger, verboseWriter); - USE_MULTI_ALLELIC_CALCULATION = UAC.MULTI_ALLELIC; } public void getLog10PNonRef(final GenotypesContext GLs, @@ -54,10 +51,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final double[][] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); - if ( USE_MULTI_ALLELIC_CALCULATION ) - linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors, false); - else - linearExact(GLs, log10AlleleFrequencyPriors[0], log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); + //linearExact(GLs, log10AlleleFrequencyPriors[0], log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); + linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors, false); } private static final ArrayList getGLs(GenotypesContext GLs) { @@ -77,120 +72,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } - // ------------------------------------------------------------------------------------- - // - // Linearized, ~O(N), implementation. - // - // ------------------------------------------------------------------------------------- - - /** - * A simple data structure that holds the current, prev, and prev->prev likelihoods vectors - * for the exact model calculation - */ - private final static class ExactACCache { - double[] kMinus2, kMinus1, kMinus0; - - private final static double[] create(int n) { - return new double[n]; - } - - public ExactACCache(int n) { - kMinus2 = create(n); - kMinus1 = create(n); - kMinus0 = create(n); - } - - final public void rotate() { - double[] tmp = kMinus2; - kMinus2 = kMinus1; - kMinus1 = kMinus0; - kMinus0 = tmp; - } - - final public double[] getkMinus2() { - return kMinus2; - } - - final public double[] getkMinus1() { - return kMinus1; - } - - final public double[] getkMinus0() { - return kMinus0; - } - } - - public int linearExact(GenotypesContext GLs, - double[] log10AlleleFrequencyPriors, - double[][] log10AlleleFrequencyLikelihoods, - double[][] log10AlleleFrequencyPosteriors) { - final ArrayList genotypeLikelihoods = getGLs(GLs); - final int numSamples = genotypeLikelihoods.size()-1; - final int numChr = 2*numSamples; - - final ExactACCache logY = new ExactACCache(numSamples+1); - logY.getkMinus0()[0] = 0.0; // the zero case - - double maxLog10L = Double.NEGATIVE_INFINITY; - boolean done = false; - int lastK = -1; - - for (int k=0; k <= numChr && ! done; k++ ) { - final double[] kMinus0 = logY.getkMinus0(); - - if ( k == 0 ) { // special case for k = 0 - for ( int j=1; j <= numSamples; j++ ) { - kMinus0[j] = kMinus0[j-1] + genotypeLikelihoods.get(j)[0]; - } - } else { // k > 0 - final double[] kMinus1 = logY.getkMinus1(); - final double[] kMinus2 = logY.getkMinus2(); - - for ( int j=1; j <= numSamples; j++ ) { - final double[] gl = genotypeLikelihoods.get(j); - final double logDenominator = MathUtils.log10Cache[2*j] + MathUtils.log10Cache[2*j-1]; - - double aa = Double.NEGATIVE_INFINITY; - double ab = Double.NEGATIVE_INFINITY; - if (k < 2*j-1) - aa = MathUtils.log10Cache[2*j-k] + MathUtils.log10Cache[2*j-k-1] + kMinus0[j-1] + gl[0]; - - if (k < 2*j) - ab = MathUtils.log10Cache[2*k] + MathUtils.log10Cache[2*j-k]+ kMinus1[j-1] + gl[1]; - - double log10Max; - if (k > 1) { - final double bb = MathUtils.log10Cache[k] + MathUtils.log10Cache[k-1] + kMinus2[j-1] + gl[2]; - log10Max = approximateLog10SumLog10(aa, ab, bb); - } else { - // we know we aren't considering the BB case, so we can use an optimized log10 function - log10Max = approximateLog10SumLog10(aa, ab); - } - - // finally, update the L(j,k) value - kMinus0[j] = log10Max - logDenominator; - } - } - - // update the posteriors vector - final double log10LofK = kMinus0[numSamples]; - log10AlleleFrequencyLikelihoods[0][k] = log10LofK; - log10AlleleFrequencyPosteriors[0][k] = log10LofK + log10AlleleFrequencyPriors[k]; - - // can we abort early? - lastK = k; - maxLog10L = Math.max(maxLog10L, log10LofK); - if ( log10LofK < maxLog10L - MAX_LOG10_ERROR_TO_STOP_EARLY ) { - if ( DEBUG ) System.out.printf(" *** breaking early k=%d log10L=%.2f maxLog10L=%.2f%n", k, log10LofK, maxLog10L); - done = true; - } - - logY.rotate(); - } - - return lastK; - } - final static double approximateLog10SumLog10(double[] vals) { if ( vals.length < 2 ) throw new ReviewedStingException("Passing array with fewer than 2 values when computing approximateLog10SumLog10"); @@ -201,10 +82,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return approx; } - final static double approximateLog10SumLog10(double a, double b, double c) { - return approximateLog10SumLog10(approximateLog10SumLog10(a, b), c); - } - final static double approximateLog10SumLog10(double small, double big) { // make sure small is really the smaller value if ( small > big ) { @@ -579,4 +456,126 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { return coeff; } + + + // ------------------------------------------------------------------------------------- + // + // Deprecated bi-allelic ~O(N) implementation. Kept here for posterity. + // + // ------------------------------------------------------------------------------------- + + /** + * A simple data structure that holds the current, prev, and prev->prev likelihoods vectors + * for the exact model calculation + */ +/* + private final static class ExactACCache { + double[] kMinus2, kMinus1, kMinus0; + + private final static double[] create(int n) { + return new double[n]; + } + + public ExactACCache(int n) { + kMinus2 = create(n); + kMinus1 = create(n); + kMinus0 = create(n); + } + + final public void rotate() { + double[] tmp = kMinus2; + kMinus2 = kMinus1; + kMinus1 = kMinus0; + kMinus0 = tmp; + } + + final public double[] getkMinus2() { + return kMinus2; + } + + final public double[] getkMinus1() { + return kMinus1; + } + + final public double[] getkMinus0() { + return kMinus0; + } + } + + public int linearExact(GenotypesContext GLs, + double[] log10AlleleFrequencyPriors, + double[][] log10AlleleFrequencyLikelihoods, + double[][] log10AlleleFrequencyPosteriors) { + final ArrayList genotypeLikelihoods = getGLs(GLs); + final int numSamples = genotypeLikelihoods.size()-1; + final int numChr = 2*numSamples; + + final ExactACCache logY = new ExactACCache(numSamples+1); + logY.getkMinus0()[0] = 0.0; // the zero case + + double maxLog10L = Double.NEGATIVE_INFINITY; + boolean done = false; + int lastK = -1; + + for (int k=0; k <= numChr && ! done; k++ ) { + final double[] kMinus0 = logY.getkMinus0(); + + if ( k == 0 ) { // special case for k = 0 + for ( int j=1; j <= numSamples; j++ ) { + kMinus0[j] = kMinus0[j-1] + genotypeLikelihoods.get(j)[0]; + } + } else { // k > 0 + final double[] kMinus1 = logY.getkMinus1(); + final double[] kMinus2 = logY.getkMinus2(); + + for ( int j=1; j <= numSamples; j++ ) { + final double[] gl = genotypeLikelihoods.get(j); + final double logDenominator = MathUtils.log10Cache[2*j] + MathUtils.log10Cache[2*j-1]; + + double aa = Double.NEGATIVE_INFINITY; + double ab = Double.NEGATIVE_INFINITY; + if (k < 2*j-1) + aa = MathUtils.log10Cache[2*j-k] + MathUtils.log10Cache[2*j-k-1] + kMinus0[j-1] + gl[0]; + + if (k < 2*j) + ab = MathUtils.log10Cache[2*k] + MathUtils.log10Cache[2*j-k]+ kMinus1[j-1] + gl[1]; + + double log10Max; + if (k > 1) { + final double bb = MathUtils.log10Cache[k] + MathUtils.log10Cache[k-1] + kMinus2[j-1] + gl[2]; + log10Max = approximateLog10SumLog10(aa, ab, bb); + } else { + // we know we aren't considering the BB case, so we can use an optimized log10 function + log10Max = approximateLog10SumLog10(aa, ab); + } + + // finally, update the L(j,k) value + kMinus0[j] = log10Max - logDenominator; + } + } + + // update the posteriors vector + final double log10LofK = kMinus0[numSamples]; + log10AlleleFrequencyLikelihoods[0][k] = log10LofK; + log10AlleleFrequencyPosteriors[0][k] = log10LofK + log10AlleleFrequencyPriors[k]; + + // can we abort early? + lastK = k; + maxLog10L = Math.max(maxLog10L, log10LofK); + if ( log10LofK < maxLog10L - MAX_LOG10_ERROR_TO_STOP_EARLY ) { + if ( DEBUG ) System.out.printf(" *** breaking early k=%d log10L=%.2f maxLog10L=%.2f%n", k, log10LofK, maxLog10L); + done = true; + } + + logY.rotate(); + } + + return lastK; + } + + final static double approximateLog10SumLog10(double a, double b, double c) { + return approximateLog10SumLog10(approximateLog10SumLog10(a, b), c); + } +*/ + } 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 605d6051c..4ae00431c 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 @@ -15,9 +15,9 @@ import java.util.Map; public class UnifiedGenotyperIntegrationTest extends WalkerTest { - private final static String baseCommand = "-T UnifiedGenotyper --multiallelic -R " + b36KGReference + " -NO_HEADER -glm BOTH --dbsnp " + b36dbSNP129; - private final static String baseCommandIndels = "-T UnifiedGenotyper --multiallelic -R " + b36KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b36dbSNP129; - private final static String baseCommandIndelsb37 = "-T UnifiedGenotyper --multiallelic -R " + b37KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b37dbSNP132; + private final static String baseCommand = "-T UnifiedGenotyper -R " + b36KGReference + " -NO_HEADER -glm BOTH --dbsnp " + b36dbSNP129; + private final static String baseCommandIndels = "-T UnifiedGenotyper -R " + b36KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b36dbSNP129; + private final static String baseCommandIndelsb37 = "-T UnifiedGenotyper -R " + b37KGReference + " -NO_HEADER -glm INDEL -mbq 20 --dbsnp " + b37dbSNP132; // -------------------------------------------------------------------------------------------------------------- // From cca8a18608806fb44b8373af551e9847260dedf0 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Sun, 11 Dec 2011 13:16:23 -0500 Subject: [PATCH 247/380] PPP pipeline test * added a pipeline test to the Pacbio Processing Pipeline. * updated exampleBAM with more complete RG information so we can use it in a wider variety of pipeline tests * added exampleDBSNP.vcf file with only chromosome 1 in the range of the exampleFASTA.fasta reference for pipeline tests --- .../qscripts/PacbioProcessingPipeline.scala | 1 - .../PacbioProcessingPipelineTest.scala | 45 +++ public/testdata/exampleBAM.bam | Bin 3601 -> 3635 bytes public/testdata/exampleBAM.bam.bai | Bin 136 -> 232 bytes public/testdata/exampleDBSNP.vcf | 282 ++++++++++++++++++ 5 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala create mode 100644 public/testdata/exampleDBSNP.vcf diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala index 6f5dae2f8..1d3fb2622 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala @@ -177,7 +177,6 @@ class PacbioProcessingPipeline extends QScript { } case class analyzeCovariates (inRecalFile: File, outPath: String) extends AnalyzeCovariates { - this.resources = R this.recal_file = inRecalFile this.output_dir = outPath this.analysisName = queueLogDir + inRecalFile + ".analyze_covariates" diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala new file mode 100644 index 000000000..355420a93 --- /dev/null +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala @@ -0,0 +1,45 @@ +package org.broadinstitute.sting.queue.pipeline + +/* + * 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. + */ + +import org.testng.annotations.Test +import org.broadinstitute.sting.BaseTest + +class PacbioProcessingPipelineTest { + @Test + def testBAM { + val testOut = "exampleBAM.recal.bam" + val spec = new PipelineTestSpec + spec.name = "pacbioProcessingPipeline" + spec.args = Array( + " -S public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala", + " -R " + BaseTest.testDir + "exampleFASTA.fasta", + " -i " + BaseTest.testDir + "exampleBAM.bam", + " -blasr ", + " -D " + BaseTest.testDir + "exampleDBSNP.vcf").mkString + spec.fileMD5s += testOut -> "91a88b51d00cec40596d6061aa0c9938" + PipelineTest.executeTest(spec) + } +} diff --git a/public/testdata/exampleBAM.bam b/public/testdata/exampleBAM.bam index a6ebb6fd1e485f0036c63fa78457a6f1cc4ffd41..319dd1a72de427c1d7a3b343368c124735b41542 100644 GIT binary patch delta 3627 zcmV+`4%G3H9J3sMABzYC000000RIL6LPG)o77neM+mGbfUB|1Ty6)B0=lIw@zMO0N z!7tNY)%PwGw0115H_U8?Z3Tr0wDipMz)Ex5?pYur0oho=3KB2yh!Uhkit>O^gv2vX z$rBHG$YYXGBorj1`~`^RcU)E7)3Y6@LhEVQR2{oL_3`(AclrK2NqleZGr!(wyp^Th z-+6aB*&cV_{=)Rx^JnMX(=SXPK7W4k=!0ixFVA~#o&I+B^xf&hKf0K7@4|^=vvuRs zY4-NyxckNTraPDCAMRY7KR&-WfA;YFawmKK^!fXjXD>cDe|Gu)+2!T=(+5w!GCAJ) zEECwq_gj`q%v_V!K=A00f{dvN&RuzPxc zYAzz@7wNtA-rL=G(&_o1o;`i>qeCNdn56>|H#1 z|HawG%dhM_J^Rq?^6A+Nc_)>3F6CI>Ig^uvG5mclci|?=`cm^N?2Yf=|9l*O=}SAG zo7|Zk?7qJ{9-rJ9Pwwm<%-`-{Y%>*-uCm4UT&W~dA4o- z$xlIlkq3{@KJ9Nffs20lX@4dP5v)6FsuNlO@y+Zuq3Wy48m1u-@jOVX$&5N`)hvw|4x9-W% z;w@LSny3{rg(VJT*pi{7Y@PX%%f47=l5a{6?gI(eDRb7nzK}uJ83`3qZ^VQ^eu!5_ zY^*IoRD90~lK0nVE$A~lmO$5D)hD`JdLwgrn}7zUPSJMEAO zrZ`h7^gYjYLQaL^VY}UBo0CBU6@MG8!Df5dZjI1@wpK>`X0$;3_7&nUVeR}@6%UW5 zNBjHJ$=+mlj+i1^VU!|;$`U0QsVYya3L&b<7Rn(=W0Iw$#numi>>rjAn z@G*q<+LLuGL-;BXwmwV4L319wgIyuU0)&)QDVWPJs=!6q|&nzh?H5TW3Zq#h0+W2MkxpDQ5% zmP?{MA0Tz;XypVB50*y!=^W>4zuRbt#^-O8o4EAK5@9Gtq{}SYS?{zR z86t@sd(;j`ZJ_DeHtHeYY4t~KiY=%qc!Q4m#Fm|oPaE_+zsOhI*wF6q^V}GDRTm6zLHFMoKZy?<`A7FrN)d8tQ)!vn!`s3V&>y9g zT)B?VAq9ch=6{SkhF1|E4yhpc|Fn!wWm?8>-O%t5*8b{tZ(|4#4x#ZMFT%9Ic~ur9 zMH$X%j-b_peq{l|AzfAkl}TPv1wz!y0nLclMO8@)Uf@TA6;L1(1v+IA4GA+RNWpoi zT){Y#4in0E+8(x7Mm)SiycV&u0^*x22*o8Llqj4R1b@^lDS^`J2^Tw-?eZ>Twu3Qs zxYM$1K^Vh+%Y%YyeguVP8NeIB#FQ#K;B}W#tS61b3;GxoUm5YiKP?ciyW)3X?eg|G zJ(=zwn#O;)X#7N1x~M9^%%iBN3Q|@Ow~%P4Dsu=^QeoKDVyjKKlms~HPjh9>4^VCd zqHdSwfPZ1;V1GEE!zu+yeK^IFDL4tYF!am>FRKlL*@T+=H)H9{wt|YU<~YrNfqZgq z=HdX>{_$4Cy(0+JW9S*XyW@q7U_f0CTYy9}%OxL)3L&smh+P7~nI49GI3DxnHinPjd z!vWI;RTWZEjuV8SQdt2R(BW=C=)jI z1(yzFs4YB10x#$z##ir)zqlg2R`JKn6Mz1%V3kp~N-8yg^zxwUyQa(tZ7J%xp2;~vrj)GGSAzj5NFq2k&JL-It8cMBRJ{K2+-+J{)4Sxh{e|dYk*`J>5j;F`R zdkaDpB?xAxDn=^96|IV*fJy_t99GJtD8Lt#+847@VFo~MUg~{bKW@HL#1xqNOn)tA zV6z2nOt=)QPf|a6^+^rCwLIYs5SV>U&rn$uDP1OYbA*0Ls0@h*&IL*;R2KnLzW_64 z%!~((9+ZLDG@U*$`3OZO+=R3JT0T%-NymuGP+eP>&%;H__{M*jmT~t+SNuM#{mrd} zClIAaN4wL>!NR^(Ne^*!9byM6#-U+ zN(??cp<6nFqKdMu0)`Bj{|P3x?fBH8)NwIsTO*fJ-$#~3XPbi@mlzI@dVgO1;(0z~ zE3DM-VRJBn*HL3>#8-2i@BOElq;6*7&tdJWw<7MErhg1W@px~taN!6kw51D8^PE(9 z9u*h{Cyn#mxPh2z8m9@Jn^FxGm4v3SgiOIu&qEMx1QD#jgu)ig6qR+$H%|zlz%t=; zID?0Mm&3Jk|=p3i-BZv-W0t`E-2s8qbXJ zTF2=sj8ND!y_H01mSq*pikcMA4WYnB8Hq6}3tAx^4SIvtI_?c{uhZ@Ho9mX>>orUXV01|>!iY;zib zV|86_VbDy_pc#0aa*jP`IG`{Y(vBB`aii24(oV$YXvt<^iDp%Jx; z*G2UQ%M-qak%BvgX)4YVrBY~?x`Os$s;!RTtGafR3g*}&k6C!&N#&4mXk)kOx4nTg z7&6*wc~ELM2nyPM023u142THbAoQef^R~A!+zdh9r3rsBr}?`N*BZQWQ^Vi5SZi$j zs*(5rCgPLDBY#GcLL*EmA}Eejieg3b5+xKBWr-=2p{;~qnwt{I>T$@}7V}u#JmWW` zW8E(65lPIaCuJrsn5{%THHuj&hCg;VfK8P{aT+CR?!Q$1dS%t1VAku3+qGA#Cx1d&KjElPsGrN3XF28&pa1Ie zfQuOY=RaG-=(U2KudX%z;C8^{>G9s-bbo&lqcP-Ogs~Zy6iUk+IwYiDNoWioBS^=p z$g8p=%ojHQZBaimG;kqM2w5YCHJ+9;H@a}FAmwZXR&avHZ8c-oD-#-;H?z=CFG*Ns zB>~?}Gh9khRnP=MAuTJKrZfW&r@_(TqAc8bIM$}R9vS39(3uOJ9j(o_FeY8keqpA( xJDd20tUoPH_&z;YhoiJ;@%Rq6OJl#VQgvaEV%^Rzkf%s6yhp zSM60VdeO_aN0m^Kkop%O4!_4UlWg{+kUO>aR8$Z)Ius zcix#!w#MCipP#-se{t45`TX?p`T50@_g|d8I_tf4@>|`Lcczd3=wi~n4;PNj(v451 z*}ch8_qX4jZeN~#uzhj%^z7p7#pAQf?d<&d`FoeAFW*0Varxfq<>lG)N6)@AIokf? z@h>~I_4M?A^3`eg!T$94?9tqdyH9rych3%YcaIOA>_6Ilbnxh)d-8B< zZX)M5>Am&tz3$uT^z2VhpTB%|mc$RX9-Th#-cQ~fIeGBLNbjwO^*6_ce!O+KHQ7Aw ze)j&1=JNdMt3NrtINN^t{^Qe&C)slJ?2qC1ncRWbB(KYDulX+PlvZu-Heebb%# zraQl4@&EBU;Z<%uJO9JpN~6(Oh1EED;~&1d()ej(wtOE}*?1eTG#)qZ&&U1?)<6IF zE6zCf3h>398C^?ZQS^7v(dmhzy2t*CD?3{87U|#Vw5YaV9}Fw z0waG~tJRX7cB{2c+bw6^;2q#c|`^dtbci?TwQMZ;t%*H5~aX2JvsN5Wl|! zqQY7w8etvd`8KY3k=EwaoE`O6JsDcO<%(7lwL+$_#DNT3GL)39GGB7p7pqM2b?L!- zAmJ)y&Z^fJGUz%Zp+f47m=MSh@zRKmm4zo2-@ZaLV`ljz;=y=2nT(Ht^QIM%nwc%f zQH-O!P_e-)VvJC>0fUo00~vqo!d70#ZMme`4Ew$Iz@Z2?ouEJJSZl4pdVAPzjnII$ zmPY(~v_Smk72+>o?Ce%69vn^&_x7ff-O0`zF-5e(C`AgDB}y<-Ri0KALR66rl!KGT zBuh!h1EYdR)v2U3lLILEsgk# zE5ws4#IG-b7?n{)FfJUA#?rPJ!bJHVu~ERd&k1qe&~6RA5v|Q7Y~~CDPYSWdD1(2zA{Eew;IbZOBkZ~X z^Eh}czq^}$G^hEuXOI(X!s|$U@Eq1#3HJeSvUgw->i!}W+CbB_ZPY`))9R1f6kCu}@ERTUi7h)F z$=CYbR=3^iy3KzMS+iOp?vKKy84b^j@j4U@_njG49MJ+}mBvI9nowOKO(PW-G)f~~ zkVv5A49hihWJ?P}g50#Z}oIJI^H?P$?+ zEfHEG7zy5A+Edp)TzKlW9X|tOU%lN^cc+uXgX!Mnc#(f?5=b*7Dq{jpsR{_Pq$D~b zG>&mXO2CYjj+B5fnq@|5QjE!-6oSuPGm4<&NaaJAY6gok7jWh)$%MxlRg_^4KC1#1 zs+K(vOA~(PityTsYj-D%OM)VmC1p}#tV^0IU136EO)8RcWdVjmx~vE)lf0q|3{hJSXhytFs!Ce0 zfsY0$pukKN=#)V;B+OhO1?Qo11>;OQOeo)Jd)R+o8u8!?@!E);B@o|aK`1T}p+w=l zAYjds5=gC{aIs_AF7GmCI~Y@kJ1xr=gfZ;5JV>bKBPbNh0CoTqQ>yHM*HuQbo-_(C z=wq<>(uj}#d4YIc6~6^zm$&=r@pSLN6#j!n;U~J%MO6W29z{h}kg@{5g+N19nS+~> z3d4V@7E3L{r6j;nAI&XmK0vt^h`L;w6NZ_C{NaEOqZCZ)!zG?v8u43Kh}UKD(GrL? z0mwGcSCBztgmMr9Ra8YF2y(3vDiEw-2*{Wcf(q)q;(U$Io4rVg+JNxL(r*Z4L@R$aO|-&kNXk zK-L}GrY(0p@ImodLEyP;X-31NhBt$;xeVj&eC2C=C{bi}I9hLZ*1Ln2Yq_m~C0u_L zxLyd25^dU{S9@m=cHPz}w4lV>px6eS2bRCiJAv>h3oItqnPoKI(pBR}KQL9}{tY|+ z9LBzRd%`)MPK>LLudGO`EH@m`T~Jja73DagIfN%fWQ4Mk7EzJS;!tr@(Pt3|J0X+8 zW3?c6jKi`p6tld;ctDx3sV}&6AVPm_;UN-uK_4-`d|mwc72&lNe{^@k{}HS*)~%9C z4IsQc$oj5HGeTR6dah?861rc3JT`j)dKROgR6__?F$zqlRlklp9;JpD5lkB)X1gepo9^iEZbRE8^B6-5D=26Q=$lu1#5E+~JsFJ`I2 zG=SV}>UEw!Ztf{!5=?!i7BjHffHoprisd`0AAa;n8-C;Ngf~E-_cb*`Wl^MbnbgG* z>LsBvBpxUiNU4xr1a$oZ#F!B?9u#`84EUz0^nr;-NHXC%T~C%*JO+O+Jv`i*PWBh_tx9s}p0X?{a9Kh{tY9s2s)_;(nHN+; zAE{~G>DS>0-WULiQNec9v2>28u2?< zh}Tu|Ys(-;2pS5Ns|c_nRASKK3Ej{UBvq7c6wqWq|4%TnZO5k$rH+4#N!uE^l=?oh zEIL~pt8HA)}JeB$kjVXzFWiu@bo?x12tD~=8zc)3WWsF;Mt*l>;d(%ABtKMgw#jA@J$D~>T(Z!)b%=o66S@93>{f3hUL}G z_kMq+A%^eV$g2MdV}EhGuY!Vux-r>b)c#VF1ZzT}2aX7-3QXf7i?giE%&eTES&Yk+ zKGI5>rS+8wtTQm$SZU zE?dmBxS8WOtz%s->lR7OyC-EjF6ga9-8G6?Duy3B96=pjqLupbYcSloX+i^iH$W)r z8K|4MN)W{eGAV^-qrer4q17*FisLj&)Leh5dV7B*)gWQkA^kigYU|?VaVm~1U1`s<{5=el7F&~KXgo@iRLqu1zLufCkIE??B2O{nb6^HTt vfqW3~fr`WUFnhcvF)%OzA^kigYU|?VcVm~1U1`yj_5=el7ARmbGgo?{CLqvPoA+#4%9LB%P0}=Ozio^JZ QKt2fgK*eEvggp!l00^fCD*ylh diff --git a/public/testdata/exampleDBSNP.vcf b/public/testdata/exampleDBSNP.vcf new file mode 100644 index 000000000..9e7e96f51 --- /dev/null +++ b/public/testdata/exampleDBSNP.vcf @@ -0,0 +1,282 @@ +##fileformat=VCFv4.1 +##FILTER= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO=5% minor allele frequency in 1+ populations"> +##INFO=5% minor allele frequency in each and all populations"> +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO=SubSNP->Batch.link_out"> +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##LeftAlignVariants="analysis_type=LeftAlignVariants input_file=[] read_buffer_size=null phone_home=STANDARD read_filter=[] intervals=null excludeIntervals=null interval_set_rule=UNION interval_merging=ALL reference_sequence=/humgen/gsa-hpprojects/GATK/bundle/current/b37/human_g1k_v37.fasta rodBind=[] nonDeterministicRandomSeed=false downsampling_type=BY_SAMPLE downsample_to_fraction=null downsample_to_coverage=1000 baq=OFF baqGapOpenPenalty=40.0 performanceLog=null useOriginalQualities=false defaultBaseQualities=-1 validation_strictness=SILENT unsafe=null num_threads=1 read_group_black_list=null pedigree=[] pedigreeString=[] pedigreeValidationType=STRICT allow_intervals_with_unindexed_bam=false disable_experimental_low_memory_sharding=false logging_level=INFO log_to_file=null help=false variant=(RodBinding name=variant source=00-All.vcf) out=org.broadinstitute.sting.gatk.io.stubs.VCFWriterStub NO_HEADER=org.broadinstitute.sting.gatk.io.stubs.VCFWriterStub sites_only=org.broadinstitute.sting.gatk.io.stubs.VCFWriterStub filter_mismatching_base_and_quals=false" +##contig= +##phasing=partial +##reference=GRCh37.3 +##reference=file:///humgen/gsa-hpprojects/GATK/bundle/current/b37/human_g1k_v37.fasta +##source=dbSNP +##variationPropertyDocumentationUrl=ftp://ftp.ncbi.nlm.nih.gov/snp/specs/dbSNP_BitField_latest.pdf +#CHROM POS ID REF ALT QUAL FILTER INFO +chr1 10144 rs144773400 TA T . PASS ASP;RSPOS=10145;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10228 rs143255646 TA T . PASS ASP;RSPOS=10229;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10234 rs145599635 C T . PASS ASP;RSPOS=10234;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 10248 rs148908337 A T . PASS ASP;RSPOS=10248;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 10254 rs140194106 TA T . PASS ASP;RSPOS=10255;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10291 rs145427775 C T . PASS ASP;RSPOS=10291;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 10327 rs112750067 T C . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10327;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=132 +chr1 10329 rs150969722 AC A . PASS ASP;RSPOS=10330;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10351 rs145072688 CTA C,CA . PASS ASP;RSPOS=10352;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10382 rs147093981 AAC A,AC . PASS ASP;RSPOS=10383;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10433 rs56289060 A AC . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10433;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 10439 rs112766696 AC A . PASS ASP;GENEINFO=LOC100652771:100652771;GNO;RSPOS=10440;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000100000200;WGT=0;dbSNPBuildID=132 +chr1 10439 rs138941843 AC A . PASS ASP;RSPOS=10440;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=134 +chr1 10440 rs112155239 C A . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10440;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=132 +chr1 10492 rs55998931 C T . PASS ASP;GENEINFO=LOC100652771:100652771;GMAF=0.0617001828153565;RSPOS=10492;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040000000100;WGT=0;dbSNPBuildID=129 +chr1 10519 rs62636508 G C . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10519;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=129 +chr1 10583 rs58108140 G A . PASS ASP;GENEINFO=LOC100652771:100652771;GMAF=0.270566727605119;KGPilot123;RSPOS=10583;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040010000100;WGT=0;dbSNPBuildID=129 +chr1 10611 rs189107123 C G . PASS KGPilot123;RSPOS=10611;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 10828 rs10218492 G A . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10828;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=119 +chr1 10904 rs10218493 G A . PASS ASP;GENEINFO=LOC100652771:100652771;GNO;RSPOS=10904;SAO=0;SSR=0;VC=SNV;VP=050000000004000100000100;WGT=0;dbSNPBuildID=119 +chr1 10927 rs10218527 A G . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10927;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=119 +chr1 10938 rs28853987 G A . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=10938;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=125 +chr1 11014 rs28484712 G A . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=11014;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=125 +chr1 11022 rs28775022 G A . PASS ASP;GENEINFO=LOC100652771:100652771;RSPOS=11022;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=125 +chr1 11081 rs10218495 G T . PASS CFL;GENEINFO=LOC100652771:100652771;GNO;RSPOS=11081;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=119 +chr1 11863 rs187669455 C A . PASS RSPOS=11863;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=135 +chr1 13302 rs180734498 C T . PASS KGPilot123;RSPOS=13302;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 13327 rs144762171 G C . PASS ASP;KGPilot123;RSPOS=13327;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 13684 rs71260404 C T . PASS GENEINFO=LOC100652771:100652771;GNO;RSPOS=13684;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000000100000100;WGT=0;dbSNPBuildID=130 +chr1 13980 rs151276478 T C . PASS ASP;KGPilot123;RSPOS=13980;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 14889 rs142444908 G A . PASS ASP;RSPOS=14889;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 14907 rs79585140 A G . PASS GNO;RSPOS=14907;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000000040100000100;WGT=0;dbSNPBuildID=131 +chr1 14930 rs75454623 A G . PASS GNO;RSPOS=14930;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000000040100000100;WGT=0;dbSNPBuildID=131 +chr1 14976 rs71252251 G A . PASS ASP;GNO;RSPOS=14976;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000100000100;WGT=0;dbSNPBuildID=130 +chr1 15061 rs71268703 T TG . PASS ASP;GNO;RSPOS=15061;RV;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000100000200;WGT=0;dbSNPBuildID=130 +chr1 15118 rs71252250 A G . PASS ASP;GNO;RSPOS=15118;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000100000100;WGT=0;dbSNPBuildID=130 +chr1 15211 rs144718396 T G . PASS ASP;RSPOS=15211;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 15211 rs78601809 T G . PASS ASP;GNO;RSPOS=15211;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040100000100;WGT=0;dbSNPBuildID=131 +chr1 16257 rs78588380 G C . PASS ASP;GNO;RSPOS=16257;SAO=0;SSR=0;VC=SNV;VP=050000000004000100000100;WGT=0;dbSNPBuildID=131 +chr1 16378 rs148220436 T C . PASS ASP;RSPOS=16378;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 16495 rs141130360 G C . PASS ASP;RSPOS=16495;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 16497 rs150723783 A G . PASS ASP;RSPOS=16497;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 17519 rs192890528 G T . PASS RSPOS=17519;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=135 +chr1 19226 rs138930629 T A . PASS ASP;RSPOS=19226;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 20141 rs56336884 G A . PASS HD;RSPOS=20141;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000000400000100;WGT=0;dbSNPBuildID=129 +chr1 20144 rs143346096 G A . PASS ASP;RSPOS=20144;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 20206 rs71262675 C T . PASS GNO;RSPOS=20206;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000000100000100;WGT=0;dbSNPBuildID=130 +chr1 20245 rs71262674 G A . PASS GMAF=0.256398537477148;GNO;RSPOS=20245;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000000100000100;WGT=0;dbSNPBuildID=130 +chr1 20304 rs71262673 G C . PASS GMAF=0.338208409506399;GNO;RSPOS=20304;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000000100000100;WGT=0;dbSNPBuildID=130 +chr1 26999 rs147506580 A G . PASS ASP;RSPOS=26999;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 29436 rs2462493 G A . PASS GNO;RSPOS=29436;SAO=0;SSR=0;VC=SNV;VP=050000000000000100000100;WGT=0;dbSNPBuildID=100 +chr1 30923 rs140337953 G T . PASS ASP;KGPilot123;RSPOS=30923;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 33487 rs77459554 C T . PASS ASP;GNO;RSPOS=33487;SAO=0;SSR=0;VC=SNV;VP=050000000004000100000100;WGT=0;dbSNPBuildID=131 +chr1 33495 rs75468675 C T . PASS ASP;GNO;RSPOS=33495;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040100000100;WGT=0;dbSNPBuildID=131 +chr1 33505 rs75627161 T C . PASS ASP;GNO;RSPOS=33505;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040100000100;WGT=0;dbSNPBuildID=131 +chr1 33508 rs75609629 A T . PASS ASP;GNO;RSPOS=33508;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040100000100;WGT=0;dbSNPBuildID=131 +chr1 33521 rs76098219 T A . PASS GNO;RSPOS=33521;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000000040100000100;WGT=0;dbSNPBuildID=131 +chr1 33593 rs557585 G A . PASS RSPOS=33593;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=83 +chr1 33648 rs62028204 G T . PASS RSPOS=33648;RV;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=129 +chr1 33656 rs113821789 T C . PASS RSPOS=33656;RV;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=132 +chr1 51476 rs187298206 T C . PASS KGPilot123;RSPOS=51476;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 51479 rs116400033 T A . PASS ASP;G5;G5A;GMAF=0.113802559414991;KGPilot123;RSPOS=51479;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004070010000100;WGT=0;dbSNPBuildID=132 +chr1 51803 rs62637812 T C . PASS GMAF=0.468921389396709;RSPOS=51803;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000000040000000100;WGT=0;dbSNPBuildID=129 +chr1 51898 rs76402894 C A . PASS GMAF=0.0731261425959781;GNO;RSPOS=51898;SAO=0;SSR=0;VC=SNV;VP=050000000000000100000100;WGT=0;dbSNPBuildID=131 +chr1 51914 rs190452223 T G . PASS KGPilot123;RSPOS=51914;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 51928 rs78732933 G A . PASS GNO;RSPOS=51928;SAO=0;SSR=0;VC=SNV;VP=050000000000000100000100;WGT=0;dbSNPBuildID=131 +chr1 51935 rs181754315 C T . PASS KGPilot123;RSPOS=51935;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 51954 rs185832753 G C . PASS KGPilot123;RSPOS=51954;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 52058 rs62637813 G C . PASS GMAF=0.0342778793418647;KGPilot123;RSPOS=52058;SAO=0;SSR=1;VC=SNV;VLD;VP=050000000000040010000140;WGT=0;dbSNPBuildID=129 +chr1 52144 rs190291950 T A . PASS KGPilot123;RSPOS=52144;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 52238 rs150021059 T G . PASS ASP;KGPilot123;RSPOS=52238;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 54353 rs140052487 C A . PASS ASP;KGPilot123;RSPOS=54353;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 54421 rs146477069 A G . PASS ASP;KGPilot123;RSPOS=54421;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 54490 rs141149254 G A . PASS ASP;KGPilot123;RSPOS=54490;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 54676 rs2462492 C T . PASS ASP;GMAF=0.191956124314442;GNO;HD;KGPilot123;RSPOS=54676;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040510000100;WGT=0;dbSNPBuildID=100 +chr1 54753 rs143174675 T G . PASS ASP;KGPilot123;RSPOS=54753;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 54788 rs59861892 CC C,CCT . PASS ASP;RSPOS=54789;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 54795 rs58014817 T A . PASS ASP;RSPOS=54795;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=129 +chr1 55164 rs3091274 C A . PASS G5;G5A;GMAF=0.145338208409506;GNO;KGPilot123;RSPOS=55164;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000030110000100;WGT=0;dbSNPBuildID=103 +chr1 55299 rs10399749 C T . PASS G5;G5A;GMAF=0.278793418647166;GNO;KGPilot123;PH2;RSPOS=55299;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000000030112000100;WGT=0;dbSNPBuildID=119 +chr1 55302 rs3091273 C T . PASS RSPOS=55302;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=103 +chr1 55313 rs182462964 A T . PASS KGPilot123;RSPOS=55313;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55322 rs3107974 C T . PASS RSPOS=55322;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=103 +chr1 55326 rs3107975 T C . PASS GNO;HD;KGPilot123;RSPOS=55326;SAO=0;SSR=0;VC=SNV;VP=050000000000000510000100;WGT=0;dbSNPBuildID=103 +chr1 55330 rs185215913 G A . PASS KGPilot123;RSPOS=55330;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55367 rs190850374 G A . PASS KGPilot123;RSPOS=55367;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55388 rs182711216 C T . PASS KGPilot123;RSPOS=55388;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55394 rs2949420 T A . PASS GNO;KGPilot123;PH2;RSPOS=55394;SAO=0;SSR=0;VC=SNV;VP=050000000000000112000100;WGT=0;dbSNPBuildID=101 +chr1 55416 rs193242050 G A . PASS KGPilot123;RSPOS=55416;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55427 rs183189405 T C . PASS KGPilot123;RSPOS=55427;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55545 rs28396308 C T . PASS GNO;RSPOS=55545;SAO=0;SSR=0;VC=SNV;VP=050000000000000100000100;WGT=0;dbSNPBuildID=125 +chr1 55816 rs187434873 G A . PASS KGPilot123;RSPOS=55816;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55850 rs191890754 C G . PASS KGPilot123;RSPOS=55850;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 55852 rs184233019 G C . PASS KGPilot123;RSPOS=55852;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 56644 rs143342222 A C . PASS ASP;KGPilot123;RSPOS=56644;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 57952 rs189727433 A C . PASS KGPilot123;RSPOS=57952;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 58771 rs140128481 T C . PASS ASP;RSPOS=58771;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 58814 rs114420996 G A . PASS ASP;G5;GMAF=0.0982632541133455;KGPilot123;RSPOS=58814;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004050010000100;WGT=0;dbSNPBuildID=132 +chr1 59040 rs149755937 T C . PASS ASP;KGPilot123;RSPOS=59040;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 60718 rs78395614 G A . PASS CFL;GNO;RSPOS=60718;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=131 +chr1 60726 rs192328835 C A . PASS KGPilot123;RSPOS=60726;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 60791 rs76199781 A G . PASS CFL;GNO;RSPOS=60791;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=131 +chr1 61442 rs74970982 A G . PASS CFL;GMAF=0.076782449725777;GNO;KGPilot123;RSPOS=61442;SAO=0;SSR=0;VC=SNV;VP=050000000008000110000100;WGT=0;dbSNPBuildID=131 +chr1 61462 rs56992750 T A . PASS CFL;G5;G5A;GMAF=0.0383912248628885;GNO;KGPilot123;RSPOS=61462;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000008030110000100;WGT=0;dbSNPBuildID=129 +chr1 61480 rs75526266 G C . PASS CFL;GNO;RSPOS=61480;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=131 +chr1 61499 rs75719746 G A . PASS CFL;GNO;RSPOS=61499;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=131 +chr1 61743 rs184286948 G C . PASS KGPilot123;RSPOS=61743;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 61920 rs62637820 G A . PASS CFL;GMAF=0.0255941499085923;RSPOS=61920;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040000000100;WGT=0;dbSNPBuildID=129 +chr1 61987 rs76735897 A G . PASS CFL;GMAF=0.292961608775137;GNO;KGPilot123;RSPOS=61987;SAO=0;SSR=0;VC=SNV;VP=050000000008000110000100;WGT=0;dbSNPBuildID=131 +chr1 61989 rs77573425 G C . PASS CFL;GMAF=0.309414990859232;GNO;KGPilot123;RSPOS=61989;SAO=0;SSR=0;VC=SNV;VP=050000000008000110000100;WGT=0;dbSNPBuildID=131 +chr1 61993 rs190553843 C T . PASS KGPilot123;RSPOS=61993;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 62156 rs181864839 C T . PASS KGPilot123;RSPOS=62156;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 62157 rs10399597 G A . PASS CFL;GMAF=0.00228519195612431;KGPilot123;RSPOS=62157;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040010000100;WGT=0;dbSNPBuildID=119 +chr1 62162 rs140556834 G A . PASS ASP;KGPilot123;RSPOS=62162;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 62203 rs28402963 T C . PASS CFL;KGPilot123;RSPOS=62203;SAO=0;SSR=0;VC=SNV;VP=050000000008000010000100;WGT=0;dbSNPBuildID=125 +chr1 62271 rs28599927 A G . PASS CFL;GMAF=0.138482632541133;RSPOS=62271;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040000000100;WGT=0;dbSNPBuildID=125 +chr1 63268 rs75478250 T C . PASS CFL;GNO;RSPOS=63268;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=131 +chr1 63276 rs185977555 G A . PASS KGPilot123;RSPOS=63276;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 63297 rs188886746 G A . PASS KGPilot123;RSPOS=63297;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 63671 rs116440577 G A . PASS ASP;G5;GMAF=0.170018281535649;KGPilot123;RSPOS=63671;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004050010000100;WGT=0;dbSNPBuildID=132 +chr1 63737 rs77426996 TACT T,TCTA . PASS CFL;RSPOS=63738;SAO=0;SSR=0;VC=DIV;VP=050000000008000000000200;WGT=0;dbSNPBuildID=131 +chr1 64649 rs181431124 A C . PASS KGPilot123;RSPOS=64649;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 66008 rs2691286 C G . PASS CFL;GNO;RSPOS=66008;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000008000100000100;WGT=0;dbSNPBuildID=100 +chr1 66162 rs62639105 A T . PASS CFL;GMAF=0.320383912248629;GNO;KGPilot123;RSPOS=66162;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000008000110000100;WGT=0;dbSNPBuildID=129 +chr1 66176 rs28552463 T A . PASS CFL;GMAF=0.0484460694698355;KGPilot123;RSPOS=66176;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040010000100;WGT=0;dbSNPBuildID=125 +chr1 66219 rs181028663 A T . PASS KGPilot123;RSPOS=66219;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 66238 rs113961546 T A . PASS CFL;GNO;RSPOS=66238;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000008000100000100;WGT=0;dbSNPBuildID=132 +chr1 66314 rs28534012 T A . PASS CFL;RSPOS=66314;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=125 +chr1 66331 rs186063952 A C . PASS KGPilot123;RSPOS=66331;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 66334 rs28464214 T A . PASS CFL;RSPOS=66334;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=125 +chr1 66442 rs192044252 T A . PASS KGPilot123;RSPOS=66442;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 66457 rs13328655 T A . PASS CFL;GMAF=0.0795246800731261;KGPilot123;RSPOS=66457;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040010000100;WGT=0;dbSNPBuildID=121 +chr1 66503 rs112350669 T A . PASS CFL;RSPOS=66503;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=132 +chr1 66507 rs12401368 T A . PASS CFL;GMAF=0.479890310786106;KGPilot123;RSPOS=66507;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040010000100;WGT=0;dbSNPBuildID=120 +chr1 66651 rs2257270 A T . PASS CFL;GNO;RSPOS=66651;SAO=0;SSR=0;VC=SNV;VP=050000000008000100000100;WGT=0;dbSNPBuildID=100 +chr1 67179 rs149952626 C G . PASS ASP;KGPilot123;RSPOS=67179;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 67181 rs77662731 A G . PASS ASP;G5;G5A;GENEINFO=OR4F5:79501;GMAF=0.0470749542961609;GNO;KGPilot123;RSPOS=67181;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004070110000100;WGT=0;dbSNPBuildID=131 +chr1 67223 rs78676975 C A . PASS ASP;GENEINFO=OR4F5:79501;GNO;RSPOS=67223;SAO=0;SSR=0;VC=SNV;VP=050000000004000100000100;WGT=0;dbSNPBuildID=131 +chr1 69428 rs140739101 T G . PASS ASP;RSPOS=69428;S3D;SAO=0;SSR=0;VC=SNV;VLD;VP=050200000004040000000100;WGT=0;dbSNPBuildID=134 +chr1 69453 rs142004627 G A . PASS ASP;RSPOS=69453;S3D;SAO=0;SSR=0;VC=SNV;VP=050200000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 69476 rs148502021 T C . PASS ASP;RSPOS=69476;S3D;SAO=0;SSR=0;VC=SNV;VLD;VP=050200000004040000000100;WGT=0;dbSNPBuildID=134 +chr1 69496 rs150690004 G A . PASS ASP;RSPOS=69496;S3D;SAO=0;SSR=0;VC=SNV;VLD;VP=050200000004040000000100;WGT=0;dbSNPBuildID=134 +chr1 69511 rs75062661 A G . PASS GENEINFO=OR4F5:79501;GMAF=0.193784277879342;GNO;KGPilot123;RSPOS=69511;S3D;SAO=0;SSR=0;VC=SNV;VP=050200000000000110000100;WGT=0;dbSNPBuildID=131 +chr1 69534 rs190717287 T C . PASS KGPilot123;RSPOS=69534;S3D;SAO=0;SSR=0;VC=SNV;VP=050200000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 69552 rs55874132 G C . PASS GENEINFO=OR4F5:79501;HD;RSPOS=69552;S3D;SAO=0;SLO;SSR=0;VC=SNV;VLD;VP=050300000000040400000100;WGT=0;dbSNPBuildID=129 +chr1 69590 rs141776804 T A . PASS ASP;RSPOS=69590;S3D;SAO=0;SSR=0;VC=SNV;VP=050200000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 69594 rs144967600 T C . PASS ASP;RSPOS=69594;S3D;SAO=0;SSR=0;VC=SNV;VP=050200000004000000000100;WGT=0;dbSNPBuildID=134 +chr1 72148 rs182862337 C T . PASS KGPilot123;RSPOS=72148;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 73841 rs143773730 C T . PASS ASP;KGPilot123;RSPOS=73841;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 74651 rs62641291 G A . PASS RSPOS=74651;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=129 +chr1 74681 rs13328683 G T . PASS CFL;GMAF=0.286106032906764;RSPOS=74681;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000008040000000100;WGT=0;dbSNPBuildID=121 +chr1 74709 rs62641292 T A . PASS CFL;RSPOS=74709;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=129 +chr1 74771 rs13328675 A G . PASS CFL;RSPOS=74771;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=121 +chr1 74790 rs13328700 C G . PASS CFL;RSPOS=74790;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=121 +chr1 74792 rs13328684 G A . PASS CFL;RSPOS=74792;SAO=0;SSR=0;VC=SNV;VP=050000000008000000000100;WGT=0;dbSNPBuildID=121 +chr1 77462 rs188023513 G A . PASS KGPilot123;RSPOS=77462;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 77470 rs192898053 T C . PASS KGPilot123;RSPOS=77470;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 77874 rs184538873 G A . PASS KGPilot123;RSPOS=77874;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 77961 rs78385339 G A . PASS GMAF=0.125685557586837;KGPilot123;RSPOS=77961;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000000040010000100;WGT=0;dbSNPBuildID=131 +chr1 79033 rs62641298 A G . PASS GMAF=0.438299817184644;GNO;HD;KGPilot123;RSPOS=79033;SAO=0;SSR=0;VC=SNV;VP=050000000000000510000100;WGT=0;dbSNPBuildID=129 +chr1 79050 rs62641299 G T . PASS GMAF=0.224405850091408;GNO;KGPilot123;RSPOS=79050;SAO=0;SSR=0;VC=SNV;VP=050000000000000110000100;WGT=0;dbSNPBuildID=129 +chr1 79137 rs143777184 A T . PASS ASP;KGPilot123;RSPOS=79137;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 79417 rs184768190 C T . PASS KGPilot123;RSPOS=79417;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 79418 rs2691296 G C . PASS GMAF=0.0178244972577697;RSPOS=79418;RV;SAO=0;SLO;SSR=0;VC=SNV;VLD;VP=050100000000040000000100;WGT=0;dbSNPBuildID=100 +chr1 79538 rs2691295 C T . PASS RSPOS=79538;RV;SAO=0;SSR=0;VC=SNV;VP=050000000000000000000100;WGT=0;dbSNPBuildID=100 +chr1 79772 rs147215883 C G . PASS ASP;KGPilot123;RSPOS=79772;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 79872 rs189224661 T G . PASS KGPilot123;RSPOS=79872;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 80323 rs3942603 G C . PASS CFL;GNO;RSPOS=80323;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000008000100000100;WGT=0;dbSNPBuildID=108 +chr1 80386 rs3878915 C A . PASS GMAF=0.0118829981718464;RSPOS=80386;RV;SAO=0;SLO;SSR=0;VC=SNV;VLD;VP=050100000000040000000100;WGT=0;dbSNPBuildID=108 +chr1 80454 rs144226842 G C . PASS ASP;KGPilot123;RSPOS=80454;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 81836 rs2259560 A T . PASS ASP;GNO;RSPOS=81836;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000100000100;WGT=0;dbSNPBuildID=100 +chr1 81949 rs181567186 T C . PASS KGPilot123;RSPOS=81949;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 81962 rs4030308 T TAA . PASS ASP;RSPOS=81962;RV;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000000000200;WGT=0;dbSNPBuildID=108 +chr1 82102 rs4030307 C T . PASS ASP;RSPOS=82102;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000000000100;WGT=0;dbSNPBuildID=108 +chr1 82103 rs2020400 T C . PASS ASP;RSPOS=82103;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000000000100;WGT=0;dbSNPBuildID=92 +chr1 82126 rs1815133 C T . PASS ASP;RSPOS=82126;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000000000100;WGT=0;dbSNPBuildID=92 +chr1 82133 rs4030306 CA C,CAAAAAAAAAAAAAAA . PASS ASP;RSPOS=82136;RV;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000000000200;WGT=0;dbSNPBuildID=108 +chr1 82154 rs4477212 A G . PASS ASP;HD;RSPOS=82154;SAO=0;SSR=0;VC=SNV;VP=050000000004000400000100;WGT=0;dbSNPBuildID=111 +chr1 82162 rs1815132 C A . PASS ASP;GMAF=0.0351919561243144;GNO;RSPOS=82162;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000100000100;WGT=0;dbSNPBuildID=92 +chr1 82163 rs139113303 G A . PASS ASP;KGPilot123;RSPOS=82163;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 82196 rs112844054 A T . PASS ASP;RSPOS=82196;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=132 +chr1 82249 rs1851945 A G . PASS ASP;GMAF=0.0452468007312614;KGPilot123;RSPOS=82249;RV;SAO=0;SLO;SSR=0;VC=SNV;VLD;VP=050100000004040010000100;WGT=0;dbSNPBuildID=92 +chr1 82282 rs3871775 G A . PASS ASP;RSPOS=82282;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000000000100;WGT=0;dbSNPBuildID=108 +chr1 82303 rs3871776 T C . PASS ASP;RSPOS=82303;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000000000100;WGT=0;dbSNPBuildID=108 +chr1 82316 rs4030305 A C . PASS ASP;GNO;RSPOS=82316;RV;SAO=0;SLO;SSR=0;VC=SNV;VP=050100000004000100000100;WGT=0;dbSNPBuildID=108 +chr1 82609 rs149189449 C G . PASS ASP;KGPilot123;RSPOS=82609;SAO=0;SSR=0;VC=SNV;VP=050000000004000010000100;WGT=0;dbSNPBuildID=134 +chr1 82676 rs185237834 T G . PASS KGPilot123;RSPOS=82676;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 82734 rs4030331 T C . PASS ASP;GMAF=0.261882998171846;KGPilot123;RSPOS=82734;RV;SAO=0;SLO;SSR=0;VC=SNV;VLD;VP=050100000004040010000100;WGT=0;dbSNPBuildID=108 +chr1 82957 rs189774606 C T . PASS KGPilot123;RSPOS=82957;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 83084 rs181193408 T A . PASS KGPilot123;RSPOS=83084;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 83088 rs186081601 G C . PASS KGPilot123;RSPOS=83088;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 83107 rs4405097 G C . PASS ASP;RSPOS=83107;SAO=0;SSR=0;VC=SNV;VP=050000000004000000000100;WGT=0;dbSNPBuildID=111 +chr1 83119 rs4030324 AA A,ATAAC . PASS ASP;RSPOS=83120;RV;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000000000200;WGT=0;dbSNPBuildID=108 +chr1 83771 rs189906733 T G . PASS KGPilot123;RSPOS=83771;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 83786 rs58520670 T TA . PASS ASP;RSPOS=83794;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83815 rs58857344 GAGAA G . PASS ASP;RSPOS=83827;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83826 rs71281475 AAAGA A,AAA . PASS ASP;GNO;RSPOS=83827;RV;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000100000200;WGT=0;dbSNPBuildID=130 +chr1 83855 rs59596480 GAA G . PASS ASP;RSPOS=83857;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83872 rs59556914 AA A,AAGA . PASS ASP;RSPOS=83873;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83884 rs59586754 GAAA G . PASS ASP;RSPOS=83885;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83897 rs61330047 GAA G . PASS ASP;RSPOS=83899;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83901 rs58254183 GAAAGAA G . PASS ASP;RSPOS=83903;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83921 rs61338823 GAA G . PASS ASP;RSPOS=83923;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83930 rs71281474 AG A,AGA . PASS ASP;GNO;RSPOS=83931;RV;SAO=0;SLO;SSR=0;VC=DIV;VP=050100000004000100000200;WGT=0;dbSNPBuildID=130 +chr1 83934 rs59235392 AG A,AGAAA . PASS ASP;RSPOS=83935;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 83977 rs180759811 A G . PASS KGPilot123;RSPOS=83977;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84002 rs28850140 G A . PASS ASP;GMAF=0.138939670932358;KGPilot123;RSPOS=84002;SAO=0;SSR=0;VC=SNV;VLD;VP=050000000004040010000100;WGT=0;dbSNPBuildID=125 +chr1 84010 rs186443818 G A . PASS KGPilot123;RSPOS=84010;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84018 rs61352176 GAA G . PASS ASP;RSPOS=84020;SAO=0;SSR=0;VC=DIV;VP=050000000004000000000200;WGT=0;dbSNPBuildID=129 +chr1 84079 rs190867312 T C . PASS KGPilot123;RSPOS=84079;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84139 rs183605470 A T . PASS KGPilot123;RSPOS=84139;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84156 rs188652299 A C . PASS KGPilot123;RSPOS=84156;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84244 rs191297051 A C . PASS KGPilot123;RSPOS=84244;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84295 rs183209871 G A . PASS KGPilot123;RSPOS=84295;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84346 rs187855973 T C . PASS KGPilot123;RSPOS=84346;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84453 rs191379015 C G . PASS KGPilot123;RSPOS=84453;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 +chr1 84705 rs183470350 T G . PASS KGPilot123;RSPOS=84705;SAO=0;SSR=0;VC=SNV;VP=050000000000000010000100;WGT=0;dbSNPBuildID=135 From ed91461c49ccf0660fd6d6a484487db483be0ab8 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 12 Dec 2011 00:24:51 -0500 Subject: [PATCH 248/380] Data Processing Pipeline Test * Added standard pipeline test for the DPP * Added a full BWA pipeline test for the DPP * Included the extra files for the reference needed by BWA (to be used by DPP and PPP tests) --- .../pipeline/DataProcessingPipelineTest.scala | 67 ++++++++++++++++++ public/testdata/exampleFASTA.fasta.amb | 1 + public/testdata/exampleFASTA.fasta.ann | 3 + public/testdata/exampleFASTA.fasta.bwt | Bin 0 -> 37548 bytes public/testdata/exampleFASTA.fasta.pac | Bin 0 -> 25002 bytes public/testdata/exampleFASTA.fasta.rbwt | Bin 0 -> 37548 bytes public/testdata/exampleFASTA.fasta.rpac | Bin 0 -> 25002 bytes public/testdata/exampleFASTA.fasta.rsa | Bin 0 -> 12528 bytes public/testdata/exampleFASTA.fasta.sa | Bin 0 -> 12528 bytes 9 files changed, 71 insertions(+) create mode 100644 public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala create mode 100644 public/testdata/exampleFASTA.fasta.amb create mode 100644 public/testdata/exampleFASTA.fasta.ann create mode 100644 public/testdata/exampleFASTA.fasta.bwt create mode 100644 public/testdata/exampleFASTA.fasta.pac create mode 100644 public/testdata/exampleFASTA.fasta.rbwt create mode 100644 public/testdata/exampleFASTA.fasta.rpac create mode 100644 public/testdata/exampleFASTA.fasta.rsa create mode 100644 public/testdata/exampleFASTA.fasta.sa diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala new file mode 100644 index 000000000..483a0b60e --- /dev/null +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala @@ -0,0 +1,67 @@ +package org.broadinstitute.sting.queue.pipeline + +/* + * 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. + */ + +import org.testng.annotations.Test +import org.broadinstitute.sting.BaseTest + +class DataProcessingPipelineTest { + @Test + def testSimpleBAM { + val projectName = "test1" + val testOut = projectName + ".exampleBAM.bam.clean.dedup.recal.bam" + val spec = new PipelineTestSpec + spec.name = "DataProcessingPipeline" + spec.args = Array( + " -S public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala", + " -R " + BaseTest.testDir + "exampleFASTA.fasta", + " -i " + BaseTest.testDir + "exampleBAM.bam", + " -D " + BaseTest.testDir + "exampleDBSNP.vcf", + " -nv ", + " -p " + projectName).mkString + spec.fileMD5s += testOut -> "69ba216bcf1e2dd9b6bd631ef99efda9" + PipelineTest.executeTest(spec) + } + + @Test + def testBWAPEBAM { + val projectName = "test2" + val testOut = projectName + ".exampleBAM.bam.clean.dedup.recal.bam" + val spec = new PipelineTestSpec + spec.name = "DataProcessingPipeline" + spec.args = Array( + " -S public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala", + " -R " + BaseTest.testDir + "exampleFASTA.fasta", + " -i " + BaseTest.testDir + "exampleBAM.bam", + " -D " + BaseTest.testDir + "exampleDBSNP.vcf", + " -nv ", + " -bwa /home/unix/carneiro/bin/bwa", + " -bwape ", + " -p " + projectName).mkString + spec.fileMD5s += testOut -> "3134cbeae1561ff8e6b559241f9ed7f5" + PipelineTest.executeTest(spec) + } + +} diff --git a/public/testdata/exampleFASTA.fasta.amb b/public/testdata/exampleFASTA.fasta.amb new file mode 100644 index 000000000..986e6d603 --- /dev/null +++ b/public/testdata/exampleFASTA.fasta.amb @@ -0,0 +1 @@ +100000 1 0 diff --git a/public/testdata/exampleFASTA.fasta.ann b/public/testdata/exampleFASTA.fasta.ann new file mode 100644 index 000000000..642ddb6d7 --- /dev/null +++ b/public/testdata/exampleFASTA.fasta.ann @@ -0,0 +1,3 @@ +100000 1 11 +0 chr1 (null) +0 100000 0 diff --git a/public/testdata/exampleFASTA.fasta.bwt b/public/testdata/exampleFASTA.fasta.bwt new file mode 100644 index 0000000000000000000000000000000000000000..fe74222804293ab93b9d56642f6cbee8df87d462 GIT binary patch literal 37548 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

y~A_Xps7 zVtTBML=OYUe%Nb`xsLMatRWR_(HVxe-*44rewD$wex68a`Qh|ITi=87op1|^Lr33U z#}B)m{S)R<7tZOrr2dYBx4DLgtCT{~mo!!sO)q@*W)8Zx)gq$La5rK(uank}?g;+2 zo%YwV{b=VuH~ZY6%vC%qx#i(S3E83bG)Lx6EbUs9l{z|^z9?LEvZ!dc*R{<#3=#SE zL7l2~YgV@l9XLVNyRjRLrI2NjIV)sqFno)hlfE6)5vAj>#O}$6$ISZnT#S~7Qy$QA zO>MGYj~4#k_W>m+J>(D1*XYtDk!h4u`ENHeJ4BAGPd===HXFYqZkgVCn%Ps5@F3pO z-tk7#N3rJLRBy6dt~xJu_OBFRuysg>lCxb%e2_?z@xJ&5t97sT*Ctq_f-1k7ke?MEzEqnFTOR2-bE8L`d?7ee14#XF)p>@xzkU*XUoNzKXrVy zOrbsEeE+*#Uwgc`;(JDc)7XwR-z4)s^1e}eM|F_noKtFPeRHGJ_RCzxPbglk;b>=b zMKhmMk%TVq05{&>jIf`}r##%fW3k?%+^Rm6Fuis-RoL4~7ObY6!)nb~WN`T6s_k5l zu-5jic=Kg)Qif8u!lA0gG0D-ecBT74$k?XShmy`XT1V0-b|jra`78G2ds{`K4R(YR zvU)lrWIsAB9UIPaB8N3SRFOBxJ7Xp71~z1K=K2R46^O%{40Ok@_#Zp2S(mlmPw)TA z0zCI#LqHOH9#^etI)lzC`0cR|W+sR;U(9a`q5pZ>4ISBNN<$fY{dA^!COnqGYt z>lADC!A61oTxdx9yWiFHcYfBkW*GL!I!29H$Q6B^0D82Uek5*9?yu-z zHt#rbip(m?XMV_XRd|PSrVV$<{wNfXi-}_?T(Q5sA7cL7Lzb6%XP4h{vYZKe#KHt+ zW(Al}h@8T|QP%8k<^MkWVP~X6hJWD#rMa*0)|z!&8>zMSfU`v>H5%4=Fpd7D8&j+6 zUA+xfHf+lUVMUn+HYmf7I$db(;nv@PpJLRKePpR$P2J`Vo@oWS*Xxrx3URe{5n+5` z3)#N6`?!&R3y%fn?7_Fl)ueXhDPlWaVSo0XlUH+59PQ-9gmVmVS}^1e!zXn^(eE1@ zGV_Tt$FK*s8y+zw;3Gp_*h;mo>ah$Q^e4e+Zl9C>*0G5jWrcTpTYp)WW-#}*6{s9| zzY=hS*=4nxIK)@8mlNibxxwL1?`*Jh3PO-TSZ7qYS(pwwmdrystsZzLMX<}QXc?FS zY$KXnQ?>=)#MWBW{21Df{B=Yj&GANNU3>bsoqdhyB~eB~PxjXoY~_&Q{RcA+k4)p= z0qmpm-v!ZAa~68ZjqOH_h3LLN*3wzlw4Xy;S=q@^-zd^DLx_x@|13@?FcYdpTN^G3 zJy{KIQ>345Wt~t>PN-_DxwiApZa%4j)@&e}i}wdPa_AK17e?Xi=y-meYfz)(Z!|}ECDrn{)1D#c0LlDWVCH=mK);^1sTgSEz7$BY< z=k#>ArBl9+4VJt^WXCSvPS2%(KIroXuIu4-J*{8lAI`>-`@+d1dtO=2v=5vc$$O2q zb``4}J&LQ7J(k%LRGVHHwUfm7LvOgl_N`be*ssE^Ld~(TWJUsOo-W#@-1pJ%L3GAE zr673?wpGBw_nS<?@m@QIItcYA4SSlJxJ6fvX~#~aS7Z8)1Y}W}q)6>gxItEx zgK*`*rKZ?OEzftH<$29!h7nI61o_QSa_uncbVtS8O-E~mKeuvdeMBY{;FM+!_u_E;yma%^g5x;A3L;?zQ%ubk0shy6st5Ikp9h>>xBTPmxA~(aH<#1gzvlIHe+VE&j zHutSKmgTrVNT}y>U`mnR>(Ia?TU8vmTh07xQ<2WF4WiZ4R|}XC=24<_q|X&xVx)OT zT=8`Minuj1)RD3@m$1A!r)Dk+yi4ATsHydKU6XAvjbSf0{wVjxPn05{@?hJXS^a@i zRLXqXmhi1)NnDlJTT9t4oIcC?`_A^{cqu)!X5#})#;!mpZLQ$_CO6M9DY&h{!29JI z=5lrNXoV?#ow@Qf9E1>M4^V->yY8IK2OYX< ztcw2okml?3Sf}tlDL)XI5EYYF?w)PG5%#w6?R_+Li{FC%-re_3#(GViE6fSh-2U;M z4&(g?!Hq?8sLnuQdi+hptu2eH<^;vnoF%t#QOG$C!ApKzfd$ZYJW|Iv>;b03ZgV|w z6a0`oxD6r$gmRMy5;|9pf%Ph6uvU=P&3&n>ix1ykS*YF$p`<#zZ41IeIvKm2g~(i< zE^9l0^r9WTs1iPq$yiH_-cV4uvB^e$`TFU3kX<yEcmVk0qhR|T$H&AH4Cy;`KK7E@@UYFD=;ZY8pQp?E(hInT`B6wFTedbPbiUoS9e&8*y<8x*Y&iLy1q$@C4kt=8wS>7fk_v%%3&L95q=3l`9F$sg zZqrABfs<`B>lW}2t7zpu_STpjaG(1!oPQW+^aQ#QLnO7L0MtI%OGunNJ?a(U;)rdN z9HIa^Wl4;ZN>to&V^hK%fl#8q{duB=Uoy;d}yk1g_pB+dQL zhpwt~x4x)WEZiOxnOtKzEr}YB48F;9yw*Vfb>|$a?@_|LZM1^F|I}QO(NOUDC-wAO zA5(s6_zT6g?WbEe%s(sm_<2S(+5SA6k=oL3_;Ih(=e3kVgTJ>I#QY`c;~)OBCH$u! z|LIzABc%Pe-+o~de*RhO(j)A9qITBx>!O8f)`R%5e_o?i$KJlF_`Ldy>*A1q*;k7i zYCpPO|IzZt_H~a^mvJ;uN@MLmDX0F(OL2~;t zGju4Bf~0pEt}WEX=IC!{2PK7BuQh?YN@Xt9u7_P3?Hy=fx(-pqWLXqImtE@($0#Pc zhA0AkBdFKhhw}te(;U`@A-7VBxBUGzhvvIMae0l|F6PvI-_nu8oP?U>)AoHnh(PMn zjBq$VcGmi28mESyzfJ!feW$)4rs%ZTem6K^%|B|91O)?cBBJ}rA^Xq6M|U09ekkeDP=l z&zd)Ejm;W@NE4YgsocYxZk$`TVm1SaRj?|kb|*0 z%(1o5%jt7^xmVX{$mP6uhzz@{FK98s4kUCMI8g?IsI)+DOhwVqhyd06GE+|_y6hahreM+G5z4ysb&7K%AdR3fY6^5G z_}rc}0D4J4;6=9jxO|KNN<8&wsY)JhjL?1@q4u0tXzXA$&ycy zcWZo@hI!}pWOqeWM|Xqm$$wM{HpELE;TEkd${PAkoElm2V_SW=xfiDzwHmv9`^bTH zU>>8`Ovtc5$L2MrJ24V6oz_5&_Y63jq18KE9|cexzE4gTH&dP?<9YEf4;wk z%>3VwO9ev>0H8x-`~ZRr z`>ow}=DD^w6@(oguFXGa<*(&8Hdqfec5*)L*zKeh8WyHroDwp;fgj!t?^y1H-w32` z|E>|p$mne?v?v$CrCJ9=(p{)P|1Qhi&5UPy+||zyJnwp8VWEZzPF@%fZxqK7V+4k; zJay&>iEEQ!6Oh;Poe6JHlU94+Y!T{4ltG&|q`Y-wZ4-W1h9mmP+~Bh^@DS|H7>X$H z!$@l_rq#mxt?bmRpt_jC+p~-H6h0d3SJq;Hu1#-uifM1%>)0)6Do5j3dReGWbt9lV> z+M`*Yj)cr$cdwMK%_5yMH_(1Tov(dRNeeL+Z>!I9vt8ZHcU!!6uyuyxhT}tCDUg%h z>Dp7$Khg^5AL@|{k!4Pr28W>#a_^4aUZzuiQ5g&b$qc(4W3|rVSE$luS?h_Lf;?CXGW3Gm8_?C&zEqaMO{&JwaS^vy1+X>9_56 z4*QY`b=Vx%GzR50IGU*U>9Kd|4ePaN!{V93PMGw}2hm1xIlqt2Z|&Q$vvj^q4~)0S zppp|-M&gy63j-8m=S-ek_(wdUGx(I11o#alvTx)1zDM|2kmE`PKOjf{aue%|C zw8qLRxh6R*XG~x`H%t}n zyygnvW%TSQx+u^mQq9d)iY3XHPP}#%Ik-GaV-!`adM~wxmL+Y9C_z96b`xso6F}@b z8tpR;YNqC7ws@eeC*f&#_Fsx@Ai5BTULx1J2c5ClPFxx-Q#vbRbJkS#>d6xbNE{wk z%52OUM=a>)Gzg6vou2kWLUhZ=b)TDZ<9=)`lGjeJov=J)${7<#zn~$=4fZILYH}gM zuKJ%yl9ULB5-(y(ry}YzCQ?tbNw=f@;#x8(nee(`1$;DMK5F_U&e=Vfn5LM&>+Kvo zYE7HOWvMmGF^Q36tUa*m;waI-OH=^@<%tS1@e!lsJ@~saCcI6vIvF5`)!*ez-C8?^*+lr~5?(cwA>S z7717etm#4JmT>ASoLT0|dFhpQRIe0}NmqdZb9}>wFV+}l>QOwdZw$wHG&Ej3Z0PBg z<~_Ca@K(tDPJ#izZroj~0)T3irI(Rr`HA46@C1Z@;_hi`z1@a`a!&zLpwYH=oIDQC zBbds;#Pa}sBFXPH4r=oR{HzQ%IWk>YI31{J#$rHWdftGGKx-zq5#yc!6l{aV5nt!>E1*!DA0SXTcAt6t2dq3ASghBjbcrY{Nbf1h~REizHb90m^~v{ zujA?CJxwbhK5lsoVq}~TaAM8dmR$wQbXR;QEsu3|+yP;BI`#2x996yU%?8RH0T2Mq zhuKcY_yNR}K!EpYFnR@qOFH%CLp7wE^D!Sct$U&-gI*!<-5>$(zsU~@Cj#$RU@6di z^&CI&DyRi{)dTREiL%tpH9Q>=n1T5R2_(ib?4FziS+T=d&Y?@(fj09qkA0F1j7lS~ z$K)ku?@qoUHJlnl4?s;`9!Sf8lcq-hxKp_hGVWXXvyPuD>pyp9#EbiWiuYGyTcX$q{Y0AAr*eU?>Do zXR5Xn0CFV6&)?4oKoHu%k5$kSnHgMwtHjw(=j%X>NdmAAJ{NkTAFB0$zyw-gvT`dx zdLkHsPWgJ*yl6n0%v)_3z%1?Hgs}O%zsZhseD^|!KEUBk2_8BgR1-77-AS8)84%b# zLO=wx0-YXh;lE$c%DO`b0bz6~b!mRSf8?pt-&!mMijy)9h{q*ZyE2RRWtS0&22&A4 z3|J}R-2!Ek`x`pU3m$Jq?8~x8W1SPF1p3`b#3+xnc4+;Wwqhe31reV| zyMT{E7K+Dt5gL20@N6bxIJ!1kBqzF{zUdko=H)#$$u%{dpO+)Z+K87h9C6!S7Pb!b zOHaB*Q^C0c03^+8LwTSkCjVo~?99(#NpYbB^1N0}{2W4W(?h(y+r4IjJ4=3(HIxuI zCtJMY5Zx83I7oHU6^bh_gSKRwh1sB2l60Y#y#~mpt=zpH)6wAI#+t{7rj*<$*dt5R z)ICc-E2(in1CM~es_ogX^B+TDcr!4MA$otPH94Un4Ed~^_bV1@2tx=eTUt_%s*?k= zr^K<^Of_q2!B1_>Ebvo~dX0pFCy&er4AI0Cp8(Sf>m|u{-h|(9vD7PPHaqJ8fA_#t z5&DBIDf%R;|HlPwqAzH-&6T(;jvUXcYo{h;`b27Q7!6M10%Np5{0txZ&B1^@*^2eS zlv_HeWpNg!Z#XdxEfCl^>c%nh-~Or@qV^``f1ziLAYp*h+-)zY4zxZ4NkZL=Aw5AN z62pA?U}BZ^@G=6NGPUD?Ysi9++5(7pKcKG+6%Ea2lTdd@rw0&~-T;V+x5gZ!e840# zA%Ds%l2&oKXazwJop-CTUp5`%x{#Bk=?`cw>@mY)gDj6I5?E+9`fot+eg#kkpL>l<3NMF|Hv1t^KG7(fDCnXoBJM(t^MV_Tqkq^3i4{~&O1NdF1VfX(cNE@FI zK4GzNUgWMzcOVPjm)?&G2pE)ldQ zF=pwy=(+~f<&pn%4$!_p4@>s7K4VYWZL%|oXnrRmG^_uGY;(=}(C89pM1T9!jn>Z@ z^Yn5}T@>^?CQx9Z;D#rMQso*Edvx$$}5jqCq<9=_G#_{k3izBlyRek#at zwde0tePk8R`HueGosXE820k~yga4G3=rp=<7aB4Aos2qjwbNRC)c5Y6`&Xhnuh_0s z{0?sYfluW_2!=J#2FFV_lSYz5V>2QZ&%$f^NlQUHrX`Zw--?@ZX;IWfkUbIV!K(~ zJ5z$ni*BhMdG<=1c1X2Jz$?4@?KO!yy3v04i+O4C*_3%^YVj!bGDe90 z)lxL?P8+ys<8dm55Q+2MmrmTdLE&sY-ry$_3nJE*pr%5xpXxRJ!|K#$GRM zoWYZDs1aTZsHrlwRB#MMA$0T!(62WP82WsT&FjXHt@1g5 zaKUy+Bt{#mO^jNUF9K`23ZUC;!A+RrPIv~^eW^zBQ~nv#L>NLp-BSvNhZeh=LH>*a zd1dC~yV|y4sKGewMYAyYbaU$hJc(rbp>_V0xJ5k3EM-9~ z#74rKvNP4}yti+ja!Gc!Wh{!z23x&uS2lzovpHO4ViKq9xgxVibJ{PC+IsccSWj4; zqVYOsXhX-9o;SNfsPu4^M_h`@+g7ID1yT9`C2_J+p}o{QE|;rvs(3UzeYRHDLt?Em zFAda*~#m$d=4Wncczg z{g~9F)H4Jf%;7=ZiCmWZYq~Mi*uzD>m>h#nHYJGVYOg0DmWqSn#KZ&QIV8`0HtoA) zdKNhGGUkW?cmVOzRPXz1>mZ@CXUg7Wojt;o@8q+p1zS#bHdh zwW(>aB6)dO?ba`(rp#+5gNe^iq-X3`Ug2uDdFm0RFo~wOwvg+V9Jen_dSjV*#MTU3 zA`?i&lam(*iunz#(M&YvzUJpai!|~)*qsysq-~hqfeCMwWIHG&znPhJj$9SoQfNn5 z+O#D&T#}LDkz~Z~Sn;&<_UCTY8w4+St;H-k=Ne>!r>y40?ERZ>m%Ob`WPuCy?15^a zMw5{W(AVc{C_45h#)xQT7l#wW%Fq`sCzi69Cu-;)23xkwBNU6{ia)vY&WlR)Pmvzy=%2kIHgiq)bGv~(S4K5`%8pq%t6P5j3d@$4? zD0`t}D%A0U>C3$8oP3d2Y7xTR)(y=$LRq2K7qv>2aDQ66aFxx|S&sprBrW_IZRVt* z3Dga6NxbB6x{0O!c>(feCCyofc<-w8m+P#iW@okc5EvpDulEBHy3GrzsbCh7o>6m7w1AMqTXO}7p zLRsQ=Ib8<_bKxF+&)JNPdw%uF3;0}PIo{!d+aWXs=lVH#BGSEm!UvePEC29Z~(M@JnXqd-zQ6%1Tjz}I^<|qJm~fyXG*~{JsRi7qQAWfZ2W=Xk5<2uYXk)ayv})eDhxt3ol7 zD>+g}Ya;UTraG?E0P_m?=F(jMJZuU!d8V>od1zq-BA7{IgX_8LQN*cCt>mr0S}qo) zCrIQ=B(*wto;#@ECac(79slV&8LX4gyQja7bDJk_pOp>?Ww0{B{D6Q@Jn1>MbRQ(R z?GnXY2zX@5%4ES-$4q^tY#Kf{0c}_!O(U-O+xi7?rRVTkIb;PNda}-cKHht-n!NCu zP$BQ?*c7K!2%`N@>H|;ID#0t^G`#b){)&s4ZgR$4I1Wo)Sh1kDjby3VIH~BtjAAJU z&K;Chrxow6r{EtzTqt+Tr(s4scY&!JKtTE|t;(Sf{wBU_9EIx!HdJ^q?Dee2#Cc+k zh2W5R%Vz$K99L`sCDjloanMtD=AZD^mT6W-NVPJrvYX3{+czQwPrB$#A*$Nq;s``) z7`TpCK>Q-l%Z{J>3=~%mo?UYj0HYhiK0B0$vG8DFIn!T3!cR%EL;+*?4YkHhb|3X67zReBmEbP@y4R?GpLDf2aAsT`9U)s6ft%ag1SV)G?qm-mFLMj} zjcn$2wvCPcRn}=9eQ^a)y7&oT0e&ziqbo%cs$^pCcqy$YI8jUTmkHwJ;u&sQm4=`Z z0Kg=^3jxBPfKx$eE}Hwqfh#Zu5I3c8UFp;5;EN-wAA64|!zhMP%YO`W9%}i zU}1j58C4P4#aUHD51_aUfUA{gHMZ!bq=Tx~+>O4^yuO^>u=t2|quaq>T=R>q+qI-b zhnQ`Kk7x(96KG#IQS>>oq~}xi>No4LN#E4ajp~|oe_9EQzE<-@Uz)d7x}xOqD5{`rJcC_;gyNXaZ71Yae1_#l|M!+g#KNHz! za#o?^rW(`%Hnb{?6ncA-2$e(UZ!q-_s2%I`_YN+7$O9bFTzTn35;`1I7p02-9FW;m zR^GSqy_a|K$T}vXY5slR@@%aH-W5qzDIZxcyh}|2mrju?C80lmIqUH|*CDC4Hac3p z`1KAq!O|t*E?iqRe@k-_!uT1?h)c5>z6HYPYJ&NWD>IQ=@Z$DgOm(Sy>-=)8VAI!Z z<_=>$#Cvx7*7v5`D`)9|HQ-eSDxrc?^7hqspfg?V#YiO3g;#pP4yQ(5VZ3j0;ivlp zT6`wnz|;hfzx`IFniHzF?&2y#8~lh8U-0zyf-erbrkaFbfyEdbLUf68Rlt~R;4bXR zBi;Iez+@voEhYUTn4lTGjAh;X@>YWRCcY+Er&}K6k6WaDogKLyPVQ9f-B%L>uy+T1 zdody89n&sTg1+7cwb%4%;w{;g5dd(vny}iJm%$IFk8HHMCdO%-IpFLQ1L8L>Rx(i3 zs0Gl4MH7h(KH8khmjLBN*ydrDjp5c?1nQ>%Q!Yhd@z8DWk)dwjnrZ0vzGB)^dkGI; zbXr+CgsLnvd`AQ&BplQYk-IjeI={ppm3Z~vgU&DS8VpmDw3Fcfs1({*Nd_#unpDJn z8zKHelUd3IFv;W07kA6Ro?u53_^|AN9ZCU|{X$c{bt z)XRk$X;?3l#q_CCK>7Zbk@BS@H`V6+@(R3eg1s=XlKZSr2ZYd#6r%F-co!IL0AkHo z>Gv!zZ8_4PdNE|N%!7eSGks-Q&@4V2x-v3QmZz5^H}czRMqGR%PGYi`Tj5gf5?$pk zR?{US%HR9dN3kpgOFMZrf|oPYNR`auoDm*l_lgd&+R^$`2+oQq&~0!qr1Q0cb^o|x zlSB9+O^>uWMqjeFJ(|QuIuh}Cyx-&){xTmG;_=W)Ic`#jpB%wM1(SI2FSQZeWm$gt znAG}B{+Gbz1CED|T=v65h4M+ke|I1Xe`&c2+0tP=6KNTJ^(nDEP}V7wfE zDSasfPuW{hKyZsY-Vm)tH*#l368-Wf%yr*x+0cp$wHIU&X0r0Y2<0MCyE38$Tu|Qi zD=2yA3#^D)L55S%c0I5Db{qs2Yf?~e{L}r9UfFw(8*#Ug-nWt-1Tw`t_>)7(q)kwO zcykH{&Kpr3z#>)7X^Zf3oAnup?sGjo7i<{Zi;M#&{(NCQvp(T1C;I*F9wry%?Fu_t zmoKD^U+xiEfNQ~^kmF<7@t90Qb; z_3JFYJ~jzkKz2>Rrm2%w9*xFix@+8h?MBtjQnQn(>4jRXMQ$8u|*CfTU4aI77nxua7X9a z#F*No|NdQfgN`n;~liE z0S>oEoIK9cNuk2{p?rJ(s};FnWL4XhUjDe>dd{i;%7N;NyWhA`=#!!X)H)94sDZCc z;*@v9OWBfa!&e)HX^L%ck9S}E3Tid|BHf>;e85guQuJvSVj@AR?ONP!_<5+oLJ}~q z+c&i;vY1_pd#c#7*ITj*@aoCTc0tAmc^|E6=V+f9MM-AbH!tS!%H&YVUfYY37vjjO zU2gu}$+GF7_|cn|!PAFjpB?@u)i;y8o42E0`1gydmp`6;9XghM_&m7q9~0Vu)$G)V z9{QX6VXsG9?fmwr_mF$NvxicyGfXS_ihuFK*Gu&b46&;-# zy4*-^6CyH8(=|setToFiuCBRP+%{n=gqp=islUm5_-ZQ>^9%$ZZ(C?(&b}-MO&*Zp>RP09gsuQ4BOgDJ`zpbB_BQGMmrp!HDrF*@yt0lILJ-sMu=Hp6z}GRK6%7;!X}0M_LdSL znFB&-1FTucP3OohG7_5?ae1(zMP4|f^i|q<`30bgACb?)sjs#SD@Xbf*M0eVSA=0i z)(Y`9YG5}iDWMJ9+{|;^_99#X6UMlclcSS|`_ucEIeBvQ7ZQ5qy=C-hM{I_bZKH=_ z1mw^F`Qa!d?(tAvNR)nvm+4#KQp%q_*~vBZqz@?xoridlBC{GlIKLMtB6`2L<w&Yre=U8KPx-5)#U=(fU-bYG>oW5c6RErWcuZ3@e)#7*TYfB^-3gHt#KrJDR!I z_M_>(H8P7J@R=kIovEOA)AgIJB!JW#7gPUOyWPNRnujV-7vAo}X? zP~#}fKSbn=L5-M0_lHib)Ct)vPto$t<-MLEWt8e5djA{3F)Kmp%s1^5?y047Qh4ZF z+zv~>a=tDDRI;j83LPr(n!TnAGwPT;b-_s5ZtmQ)aG*&$gqH6j=%a4Y2iiR#xJ%7# z$NO~p!t08Q9uDbe4Qf!HG;3w7Yuz2A4Mhm1AJs3SvY#cHl@kPMTri#oNQ0K<{Ol^r zu7K}mLFbfulVA_%vPCfQuBV=~#9*Ia7;-t~D!(z=(A%ek@|=ywZl;8*gsQF1KirCa z^>fr4zlT#ISXx4SQhZ3P?sJ;!x_^SWKkmg+z)5y&S`i=rgLF?T&H2=AjPJd!aGQ^N zeiM|x!kpJm*F|~#^UsLB!A;=J@e@xk7L%P-&K^BKl;* zFQ*&UrEvSiGri%TbT!49wW0QAEBu$~YVo1vpHFKxX>tqZmzPt&fARmU-X^ z#^HD!bc`)v7VWnLEeQkFoRa)?%}=%fs~N#@*LvHZ)W8Vj-r}lGtUYwz8rBSO4E&Q< z^qYf_P1Q@Yp@}D^uIEO}Rqb#`Y%Wjmz~2oE!n%MD7@Dx(dtcyFf%t$Wdy<#fVBJ7} zj?_cBJW1at86zqsSIrP%q_6@~T+uC&KjPv9k_WtgYaDoF_?MOY?3qS=gh$3q0}j%J zK>0#U7q+4wHsl4rzXGcot2P`tvYXlTj0?IMd`CHpItTP^1cUg77@!~{Ag0@MI0k}n zXsrC;W_q^H{&l0A?@5fe06W36T&WaVrtZK+3S?VKmE8`?xK>b!$?w31g! zJ5i(Hhk=r2d=@}7eVESOOASuUo=UO9?*LbUk@fPK9{&J?0PXyhU-6!O`_uo^#g|5< z)!zGV*jNz}5VZE~>wt&_hbW5HSx~Ve8t2sa^hCU;!|8O|8pYxaakvr_jWOiddm5Ff zsb*4>=y6O$Ay;360}hL}uP7?kAr7c`aR50x?ONxqd!G;c!`|!v<$2cn@89$P4WBxa zxEmP{uUeZup1H-XizRy3_@SJnKUk#m?EpD&45^R@Z7;EDx6Wz`x>{>nZWqoiGljawBU#?Y*D`ro?QP9c{nxMdXUV9&{9M6>3yYWv*|VH`xRgl>uvTF? z$|fQd5E5wyK4@+h%$XI+U>`I~`WC(Yz~91>7~F^*K^yXnYiPNI{+~z@LQ9N&(=9{F zSYDxMJ-*Ic)ixUs+LChM93nM>4o&bKfqS!*x~MCd;W%((&Yz4O1LkkVB(I70oJS~AS8tXUJ*$MVG72(^=n1xzAL~MSTh}4 zCIMF<bcld6Iq83Sf&Z> zf`0l)$rbhCUvs)&X>P>h$=obFa;T7DP&JqnvRv|yqB&x2V`u)$D9F$kyH-6nZtWh= z$VXApY1gnUyFzTkC%bgSql;!X)seK^d&}oRDLEFOn#iuPiNCQ_bo+)~M!sEpygen+ znLU4=krBUMa-fVDUB9}Tk_5|$UY`sQ0CMBMkds6vO^N90ZXY2tQ` zYXUzjZ9NlqW8(&beCMVED=h(En6Z@Bj5tj|H{?6ChFWC2h?2TzZ8~SkhbHU`Ka^P_ zP-Nr{9|0smLr@z2oC-%GezU(6+V_EVqwp{`GB*&d zOCk{c7*vK@0LVuKcX+fT8K?krJpz)Qs^=UhXixo>OnlPoPc+bj?iwSRq!BZOQZZT( zAGINrgXhVe$O^VlkAVRo3WhACMD%nIi=^7ReOeZH3?Y7eD{*>_I2VB|B z&pagcSsTkM+AZ2n^%Qb1-RRGUct|YeS>&fbHufx0B04*2I7H={UFUz+95$?fRXZ5h zn8vMV4^*e~05}|WGr!^rdp3KzeJducJ*G*GI~+v($9f@UvG*~CCVhD?FlZx4RgRD~ zHrmZ`uA>FF;%9mG3$+pvySjRq`SxSR!hFn_u?bTOMChLu_n0VO5@dDW8FD+rXo)$& zZdL{cjOhK0#!B?@B8Tkk7SV z-p}=ILjUy%}*w#ua`Kob_z*GQ4?Y$!)&B*k1M4*9N zfvlk(CfY$$E&(r0Y?^JjED+dFFd+b4c@k5~g3UZ);E`yItD5UgzIO(OR{{dMWxrHf zf_#j$h$gPlK@f@7n2jhofGy|{ddFA;zAna+7mNPLD{`|CZsTLQ;l|Vi>-)>e5)2%J zW5UsQa$Jjhq5_DUa(M-JsCE4r)pb@QJh+0FrZD0F9NjMhpd{SZ&q?Sww+P#4TgSt> z$T<}A4`F9kWD>moUl)$xwLIErWrM%=#44#S4cS5k+P~;EGTXAeeb>mMrf_Nc3voGR zDmYo^TYv-*#ZgRbp5?aNqf+Z|s`QAk1SzA4Boc{8g2Um&|GPv;B=Ya?;3N{9L?psd z(04e1L;}M9Z;{Cqo%O=#%Z}H2H(N6RRA;m0mH;9bJ~n4TfPiItlHBw`>L7)iTNI$P{4SE=T^3WLQw%e1X7>>l71a6P}~}1$rEO ztJ6<2o2UiA@*?|Nykt7gX7VC{wuc_~0n!OAL?J;!L!2MnZx4BQOp~X8xQ9X}Q!55v zvx9_$yy+Qac#Y(00Eylu?h>|&D_SleVqY^L3=w3E<5>^PT3NwqBS%O+@Kp#Tz8eSx z>3R$XL$HDAo&z$h&4$ce{m$&mJQHXV%c==>=Vt&7y}|@%!P4LQ z_0@nt3cqsoR6Fh4du$K-3|EEnil5G974dumKR6_mCP6$!{NFb1uJ0^B_9LUW=vUQS zVVj&>HB4oN`-AF-Sx>ppd7OI$i&O2N0`CRucW0Xq>NaSZhF-Ov>9Qgv00G=8y=eW) zXf~R4z`5**m?T=CF_*VU9@YmmS!>IW7V0h_#1wUysCpZ z!%Nk^LyK{L!N`mSw_VAtg&8ut9v@rF%71qX+iid3gx}%XTNdPc#BK3xIfHXM z|IB^8!dI(f|Brwhlh?pqTU^}DUAy60s_IPjDDKZI+=_9y@n=GM2T;&dFu~8XZ{ZI` zWMus-3SSy%qBwc5-88-G(nZPlXKU`r(Tm_$e=%gm*?#r(XZDQqg2+O04(0 zdj5(-M%G`l$QgH=hIiuOKe};URgjg+{~7;5n$Pdoo|5nU@b=HvL8?kt)biB2T3(d0OCPf?IAGNj6A$8^m-X|v9&J}q9{m6X-5$Ax zr@Nh%eY`#>7AGcQtZ+-!T8>|uUs_RbTKHOQwQy^Y37H9*#eA{dh(mK}DAn~^*0rqt zm6S9cRJtvx?Bjnfjr#A>mH($S4l1_|=;J zPenCc?R?=@|C@q$rS|-0RlMpina_BVfUu;3>rRu)u#0yWc3r>jV6Dh9>OU*ZUO)(s z;}-oLNNM(K2;qJ9rvt}78trNrEOt-31vy8^t(ZSTSGMiuhV_)%FXA37R(Fc-hyKO|yrl)j6zN%^T$p zx2FxolX~mhEq)#U60(b4%yc{t~=x()Z*)hZ9mZle-O2_^4@9vLm`4*Nx14b=_j~5rfBv`$9HDYzLMII3( zt@1I(@mHoUabxB`5g~c&j%jVdYk0|s2Z-o>zRt2P;6lk`%$Dm**Z9X_zI*~un8J=& zS;46DaX75*wlsV}kexzrTAEqnG%X%XHoGQoI{B(%V`TDcDKVyBn=j3ymuA=2)uw&B zj;_BAX@lZF^W&Qb`tN<_$4?=CJbdb4)A2f6W`1k-7CmRg(s$B=)i$eD3$;)-T2_ir z(S7{zou363WsGi`#{zjxdYfcJ>9TEp`cnKi7|Vb2zV8?ly9B;E-C3N^9>|V{^fjSj z>>fc523WECozrR~_)mFHn~!U)bDX})lw9{J9bC~D8gB8pQBijJti_o^FGuDVLb5SXuXKD==M~pq!tx#bT)1@T{PL3 zsM4m6DXaKT`u2wfuHJAqC8cAPu-quIA+XL*^|R(3kaLbI%ee0;tzp*Wn+1?=_VYUH zt@u6veMU#C7TOUHrIu2dzpMWZv?H9)>NrPE><7D_?a0h#hS7XHSv{;T9uc>_T2vQg z4XCuJN^KS4qli9O%+Ln)B(+N+=`gd?za0ipv#XrS9%-bDl1Aj)>cI6ka8G5(_PCeB zGj*JtD0YiaC7C344(%spQ9qsbq7V$%7 zOH3_L$D8w5p`5O7AtZ#f>(2f8s6wy)^K>7sd6*-th$^}hogta8vvc*+M%UbirKdb` zLg50)80>iKmrbE7f@a>sj?VT3No`2rCijV|kliS1Q5U9qOuc;OD>+--G*Da*US%ef zwSTI7M4rccB?MR!u`Fmn8d zh_=DrQQDyqDeS4~?s^RP&0ItJ3JT}kv}Ytei->pOyE}JR2J=$fSKg$up4gKT4|SB= zc9#F)@+)s2%j#~Ud32&hmy91f#J`L87~9>(_dm?iHUU>LF28@aU8xf_ePM3;Z4#WZ^&*FKK-O`TiwVEkq3r7IN+RxkJE`ep>(OwUlE*N8o*>*oex8f9tT zqNGt=*HR{Wq*R*o4Uf;al9SWSQtWl(q9m&&7wDCDm2y1fyRnJpWw9XHHKu`G4m8GK z!`DKSj_;-PL?-Wn1LopS?_A=S3z9!sho2P`mn7c+kMbP2-jM;-&>f69DHJvZai zRymt-Z*F52beX>jHw-do?gi&eN8#ye3Y!%3HMjtdZ*8dr*FfBtmr}QIbBkPH;0eds zTbfhW;MRJUZPEfa?Lk_gZ?3O0_?x@$M`*oaw|6~7=J~r~PScVxATchpWntU;EhdaT zm{HN1dx0$cjelF?#|jzBj9qD(6TArn-ZKK|1H!fnL4nuI0*jaP08f2CfEyODQ9xJ+%>qu|FdO~3fH&d{ySJYRj^eNhgmBgkdnsa> zY)!&UVs5!93aDMdK2MYhdrcGSbq;$2va;ce5n5%OC zCMtBye*Yit-(BtyZxhNXnwaufZxBV4S+KoY-zVHzkBo)fzc;&V$*wDNP?Q9iLc=ar zgh|i?F#uhj1QjsWL!yT`0cEZn-=EqwK0!>0@`F-s#Q!u2vg+ zi}}0r9rMxYwR8wMHpV0FAXR@K_6H>Gf)sy9yaB!)pF!Y&$30$}D9gZO;Sh+KMWZ$3 z4)^b_DT8{JpxHMxWn?Q0UQyq%)3{wx?8DTttj@r zNE8`0~Ed#qxu$)WwiaMk=NCb#k_pO_{H6Sx`O1tqrbB;-N^eM9 zbJN#7TxDe5U%hgpXiZssIz07v(_kavNpwOgs)bcKIssb{!%SYr4AI*Ir}bf%wMe zp^)NhX}A7blhfX6({eklX1lwg^{d(kpO4~Mg{Bbw4Zg1b}1vdB{Z z_4ksLozV_sT1ilTOSA%!qgR6@P*U8pIy?Nma19T$?E?lEwPr z!+G{KiptaGoa(80DGKW9B$F{2$`BV-3i}Vdx;&sPht<#Nuf>n~*>KD@0Yc@qynVw1 z*A!cq*51RIU_TzaTxk5kkF5!XM0ZO2zC{G!|l}z#HI3IHvpITvLy5!Nser+()6Vo-}L$qsh%d{c~qR?n8OZ0)K&Fab(?x>iP#U_)#JY!axPsq%!FMBCfsM&& zsMTmB`2N#99KrHFZP_pYP-bA>bqCWQ3CL#5oI)`PsTO!>wEqd;k_0Zl&!$7aoovTh z15TQBXv7JxnTrktIuq1SNxtfWVJ6pNHS)%IxYC2_mwQ8~xE%iAovsiy2 zHpewv`!2L4VOzoK`KZugX@Ky7brlOAynP!^rgU2QH0tC+V!1*b1BSO(Hs~9S6XBHi z#^>lBXF^q(mlxf?Ct_wlTR641hAZ6(BD7s7m#y7{JI4L>%~ze{x`3zj?74>6?G98)@YZAHnd(sZ>7=9 zrGHKaz#OTy@$mGfljlNwsg=IJco>Sak+OcOoIo$RBtTFh1Gf0mJXRvjV$;^`ir#r+ zTMp02d6Uc`Q7%%tGPf3%J?>a?|59_cy`Y*{XMO_5)T>Z@RBjPVaOy2KxV+q&n5OhC zKF=ad9;(;Im`S$yf@?$=`%I?iSu#`$%aHNSn-#e>YZOmeCJL)87bb5qzmFY8ybbX_ zm_h>&4a}JY=lkctgOa$f6(}E}Ej+lV&_xe-yrW2w;k@+yxw9X~xcTIoYuPOq31fC^ng{#tD+${Q`X&fiOK%GWKdc*$3- zvzN-!mt4v!T$Tcy^hoDyfi>gR{3%9xS`4&s#dD3T^0+*sS^r5zi^@?LVm8JO&ovW` zi{lufknq&2VMpoEvt(CF?Wlp$FdWo+fMJ9m7lj?vL{pV#ZE@?1>%AV@f!;%Hs+R9Q zwMNQSPD3|vprVzyMGy(wN|qPiIb@Iq2!R<^1(PDTrf|AWyOyM_-lcv<64U65K%F$Y z&ZJ#9O+|c#8}5i33e(kxM4A^z6f1AsYSK5Tq2=FE3ue#O(vq4Hyn$PB53Vj<=95Vw z@!Tmoz(Fkc&7DLLkLLO($*GqD6G|l9ir#pp8SwPH=Hzm_J9DadCTf;01lg$JzQA4SD)b`gn_z7C5im@yApJ zPDqyi+78_o;LH5;=JGOlH(jF3Qai?LlGay$Hi^|M(BjCiE0m zmcu<=OUfPBZO4h=cn?s?DiI=BQ#GE{Ze;S&%+^3COm4wAQ)uaQ0^OG2M}MoM>%x-_ z0yBx;t(;M=WOOvhhc1=Zf)~ii{@J%x&*thgxwF@n5_~(4zNU-Q`y5VMjMhR6zj2HQ ZB}yj)2zrcEXk-#$HWum9$aWL}{1-qp7c~F? literal 0 HcmV?d00001 diff --git a/public/testdata/exampleFASTA.fasta.rbwt b/public/testdata/exampleFASTA.fasta.rbwt new file mode 100644 index 0000000000000000000000000000000000000000..f623b8c3941a33feb692883a4fb568c49bd8e7f3 GIT binary patch literal 37548 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

f#$}C~p*;^M2)v6WS0u*HvXPZ$8b-s4p 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෇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 literal 0 HcmV?d00001 diff --git a/public/testdata/exampleFASTA.fasta.rsa b/public/testdata/exampleFASTA.fasta.rsa new file mode 100644 index 0000000000000000000000000000000000000000..6e7e213df66b0cb88049c39e405c6b57db0c0d49 GIT binary patch literal 12528 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 literal 0 HcmV?d00001 From 7cf27bb66e06db348581218d3afc28381126cb1d Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 12 Dec 2011 12:22:43 +0100 Subject: [PATCH 249/380] Updated md5sum for MendelianViolationEvaluator test to reflect the change in column alignment in VariantEval. --- .../gatk/walkers/varianteval/VariantEvalIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0f22e033d..4e3d38c4f 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 @@ -298,7 +298,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { WalkerTestSpec spec = new WalkerTestSpec("-T VariantEval -R "+b37KGReference+" --eval " + variantEvalTestDataRoot + vcfFile + " -ped "+ variantEvalTestDataRoot + pedFile +" -noEV -EV MendelianViolationEvaluator -L 1:10109-10315 -o %s -mvq 0 -noST", 1, - Arrays.asList("85a8fc01a1f50839667bfcd04155f735")); + Arrays.asList("66e72c887124f40933d32254b2dd44a3")); executeTestParallel("testVEMendelianViolationEvaluator" + vcfFile, spec); } From 52c64b971fa771c1c7725e7f4efa3934b0bc60d7 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 12 Dec 2011 09:48:58 -0500 Subject: [PATCH 250/380] Updating MD5s -- really dont know why it didn't update before --- .../sting/queue/pipeline/DataProcessingPipelineTest.scala | 4 ++-- .../sting/queue/pipeline/PacbioProcessingPipelineTest.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala index 483a0b60e..0a5784d51 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala @@ -41,7 +41,7 @@ class DataProcessingPipelineTest { " -D " + BaseTest.testDir + "exampleDBSNP.vcf", " -nv ", " -p " + projectName).mkString - spec.fileMD5s += testOut -> "69ba216bcf1e2dd9b6bd631ef99efda9" + spec.fileMD5s += testOut -> "a9769a85f15e77505835068353ce4788" PipelineTest.executeTest(spec) } @@ -60,7 +60,7 @@ class DataProcessingPipelineTest { " -bwa /home/unix/carneiro/bin/bwa", " -bwape ", " -p " + projectName).mkString - spec.fileMD5s += testOut -> "3134cbeae1561ff8e6b559241f9ed7f5" + spec.fileMD5s += testOut -> "fad1fd2e69287c1beb423ce17fa464d6" PipelineTest.executeTest(spec) } diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala index 355420a93..b0ac5a6c5 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala @@ -39,7 +39,7 @@ class PacbioProcessingPipelineTest { " -i " + BaseTest.testDir + "exampleBAM.bam", " -blasr ", " -D " + BaseTest.testDir + "exampleDBSNP.vcf").mkString - spec.fileMD5s += testOut -> "91a88b51d00cec40596d6061aa0c9938" + spec.fileMD5s += testOut -> "3a23c96063743ddbc35897331433e205" PipelineTest.executeTest(spec) } } From d03425df2fc729a3f50efca10f226022babf313a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 12 Dec 2011 14:46:45 -0500 Subject: [PATCH 251/380] TODO optimization targets --- .../walkers/genotyper/ExactAFCalculationModel.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 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 77a940dcf..ed86897f2 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 @@ -56,7 +56,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } private static final ArrayList getGLs(GenotypesContext GLs) { - ArrayList genotypeLikelihoods = new ArrayList(); + ArrayList genotypeLikelihoods = new ArrayList(); // TODO -- initialize with size of GLs genotypeLikelihoods.add(new double[]{0.0,0.0,0.0}); // dummy for ( Genotype sample : GLs.iterateInSampleNameOrder() ) { @@ -364,7 +364,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { else { // all possible likelihoods for a given cell from which to choose the max final int numPaths = set.ACsetIndexToPLIndex.size() + 1; - final double[] log10ConformationLikelihoods = new double[numPaths]; + final double[] log10ConformationLikelihoods = new double[numPaths]; // TODO can be created just once, since you initialize it for ( int j = 1; j < set.log10Likelihoods.length; j++ ) { final double[] gl = genotypeLikelihoods.get(j); @@ -372,6 +372,8 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // initialize for ( int i = 0; i < numPaths; i++ ) + // TODO -- Arrays.fill? + // todo -- is this even necessary? Why not have as else below? log10ConformationLikelihoods[i] = Double.NEGATIVE_INFINITY; // deal with the AA case first @@ -417,6 +419,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } private static double determineCoefficient(int PLindex, final int j, final int[] ACcounts, final int totalK) { + // todo -- arent' there a small number of fixed values that this function can adopt? + // todo -- at a minimum it'd be good to partially compute some of these in ACCounts for performance + // todo -- need to cache PLIndex -> two alleles, compute looping over each PLIndex. Note all other operations are efficient + // todo -- this can be computed once at the start of the all operations // the closed form representation generalized for multiple alleles is as follows: // AA: (2j - totalK) * (2j - totalK - 1) From a70a0f25fb9f7f791ab2b153e5c3d71cdcd1a21c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 12 Dec 2011 17:55:39 -0500 Subject: [PATCH 253/380] Better debug output for SAMDataSource output the name and number of the files being loaded by the GATK instead of "coordinate sorted". --- .../sting/gatk/datasources/reads/SAMDataSource.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 2b163ecbd..d70c63bd2 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 @@ -711,6 +711,8 @@ public class SAMDataSource { * @param validationStringency validation stringency. */ public SAMReaders(Collection readerIDs, SAMFileReader.ValidationStringency validationStringency) { + int totalNumberOfFiles = readerIDs.size(); + int readerNumber = 1; for(SAMReaderID readerID: readerIDs) { File indexFile = findIndexFile(readerID.samFile); @@ -728,8 +730,7 @@ public class SAMDataSource { reader.enableFileSource(true); reader.setValidationStringency(validationStringency); - final SAMFileHeader header = reader.getFileHeader(); - logger.debug(String.format("Sort order is: " + header.getSortOrder())); + logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, readerID.samFile)); readers.put(readerID,reader); } From a3c3d72313e199c722cb5ed0be7895c079547ace Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 12 Dec 2011 12:35:36 -0500 Subject: [PATCH 254/380] Added test mode to DPP * in test mode, no @PG tags are output to the final bam file * updated pipeline test to use -test mode. * MD5s are now dependent on BWA version --- .../sting/queue/qscripts/DataProcessingPipeline.scala | 6 ++++++ .../sting/queue/pipeline/DataProcessingPipelineTest.scala | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala index ccbe648d6..621afe817 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala @@ -83,6 +83,10 @@ class DataProcessingPipeline extends QScript { @Input(doc="Define the default platform for Count Covariates -- useful for techdev purposes only.", fullName="default_platform", shortName="dp", required=false) var defaultPlatform: String = "" + @Hidden + @Input(doc="Run the pipeline in test mode only", fullName = "test_mode", shortName = "test", required=false) + var testMode: Boolean = false + /**************************************************************************** * Global Variables @@ -335,6 +339,7 @@ class DataProcessingPipeline extends QScript { this.known ++= qscript.indels this.consensusDeterminationModel = cleanModelEnum this.compress = 0 + this.noPGTag = qscript.testMode; this.scatterCount = nContigs this.analysisName = queueLogDir + outBam + ".clean" this.jobName = queueLogDir + outBam + ".clean" @@ -360,6 +365,7 @@ class DataProcessingPipeline extends QScript { this.out = outBam if (!qscript.intervalString.isEmpty()) this.intervalsString ++= List(qscript.intervalString) else if (qscript.intervals != null) this.intervals :+= qscript.intervals + this.no_pg_tag = qscript.testMode this.scatterCount = nContigs this.isIntermediate = false this.analysisName = queueLogDir + outBam + ".recalibration" diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala index 0a5784d51..7e1d09b70 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/DataProcessingPipelineTest.scala @@ -40,8 +40,9 @@ class DataProcessingPipelineTest { " -i " + BaseTest.testDir + "exampleBAM.bam", " -D " + BaseTest.testDir + "exampleDBSNP.vcf", " -nv ", + " -test ", " -p " + projectName).mkString - spec.fileMD5s += testOut -> "a9769a85f15e77505835068353ce4788" + spec.fileMD5s += testOut -> "1f85e76de760167a77ed1d9ab4da2936" PipelineTest.executeTest(spec) } @@ -57,10 +58,11 @@ class DataProcessingPipelineTest { " -i " + BaseTest.testDir + "exampleBAM.bam", " -D " + BaseTest.testDir + "exampleDBSNP.vcf", " -nv ", + " -test ", " -bwa /home/unix/carneiro/bin/bwa", " -bwape ", " -p " + projectName).mkString - spec.fileMD5s += testOut -> "fad1fd2e69287c1beb423ce17fa464d6" + spec.fileMD5s += testOut -> "57416a0abdf9524bc92834d466529708" PipelineTest.executeTest(spec) } From 663184ee9d8560860e9551ca109a72f12c582c7c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 12 Dec 2011 18:08:29 -0500 Subject: [PATCH 255/380] Added test mode to PPP * in test mode, no @PG tags are output to the final bam file * updated pipeline test to use -test mode. * MD5s updated accordingly --- .../sting/queue/qscripts/PacbioProcessingPipeline.scala | 5 +++++ .../sting/queue/pipeline/PacbioProcessingPipelineTest.scala | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala index 1d3fb2622..4896eaed3 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/PacbioProcessingPipeline.scala @@ -47,6 +47,10 @@ class PacbioProcessingPipeline extends QScript { @Input(shortName="bwastring", required=false) var bwastring: String = "" + @Hidden + @Input(shortName = "test", fullName = "test_mode", required = false) + var testMode: Boolean = false + val queueLogDir: String = ".qlog/" def script = { @@ -170,6 +174,7 @@ class PacbioProcessingPipeline extends QScript { this.input_file :+= inBam this.recal_file = inRecalFile this.out = outBam + this.no_pg_tag = testMode this.isIntermediate = false this.analysisName = queueLogDir + outBam + ".recalibration" this.jobName = queueLogDir + outBam + ".recalibration" diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala index b0ac5a6c5..50aa66367 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala @@ -38,8 +38,9 @@ class PacbioProcessingPipelineTest { " -R " + BaseTest.testDir + "exampleFASTA.fasta", " -i " + BaseTest.testDir + "exampleBAM.bam", " -blasr ", + " -test ", " -D " + BaseTest.testDir + "exampleDBSNP.vcf").mkString - spec.fileMD5s += testOut -> "3a23c96063743ddbc35897331433e205" + spec.fileMD5s += testOut -> "f0adce660b55cb91d5f987f9a145471e" PipelineTest.executeTest(spec) } } From 5cc1e72fdbb179a54d295f835b52bf11399edc59 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 12 Dec 2011 18:41:04 -0500 Subject: [PATCH 256/380] Parallelized SelectVariants * can now use -nt with SelectVariants for significant speedup in large files * added parallelization integration tests for SelectVariants --- .../walkers/variantutils/SelectVariants.java | 8 ++++++- .../SelectVariantsIntegrationTest.java | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) 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 fc01dae9f..d20fb54aa 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 @@ -27,6 +27,7 @@ package org.broadinstitute.sting.gatk.walkers.variantutils; import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.gatk.walkers.TreeReducible; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; @@ -179,7 +180,7 @@ import java.util.*; * * */ -public class SelectVariants extends RodWalker { +public class SelectVariants extends RodWalker implements TreeReducible { @ArgumentCollection protected StandardVariantContextInputArgumentCollection variantCollection = new StandardVariantContextInputArgumentCollection(); /** @@ -609,6 +610,11 @@ public class SelectVariants extends RodWalker { @Override public Integer reduce(Integer value, Integer sum) { return value + sum; } + @Override + public Integer treeReduce(Integer lhs, Integer rhs) { + return lhs + rhs; + } + public void onTraversalDone(Integer result) { logger.info(result + " records processed."); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java index 6e994be3a..72a07bd0e 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java @@ -115,4 +115,26 @@ public class SelectVariantsIntegrationTest extends WalkerTest { executeTest("testUsingDbsnpName--" + testFile, spec); } + + @Test + public void testParallelization() { + String testfile = validationDataLocation + "test.filtered.maf_annotated.vcf"; + String samplesFile = validationDataLocation + "SelectVariants.samples.txt"; + WalkerTestSpec spec; + + spec = new WalkerTestSpec( + baseTestString(" -sn A -se '[CDH]' -sf " + samplesFile + " -env -ef -select 'DP < 250' --variant " + testfile + " -nt 2"), + 1, + Arrays.asList("d18516c1963802e92cb9e425c0b75fd6") + ); + executeTest("testParallelization (2 threads)--" + testfile, spec); + + spec = new WalkerTestSpec( + baseTestString(" -sn A -se '[CDH]' -sf " + samplesFile + " -env -ef -select 'DP < 250' --variant " + testfile + " -nt 4"), + 1, + Arrays.asList("d18516c1963802e92cb9e425c0b75fd6") + ); + + executeTest("testParallelization (4 threads)--" + testfile, spec); + } } From e47a113c9f18bc75564091554517a297ff9e2ca1 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 12 Dec 2011 23:02:45 -0500 Subject: [PATCH 257/380] Enabled multi-allelic SNP discovery in the UG. Needs loads of testing so do not use yet. While working in the UG engine, I removed the extraneous and unnecessary MultiallelicGenotypeLikelihoods class: now a VariantContext with PL-annotated Genotypes is passed around instead. Integration tests pass so it must all work, right? --- .../BiallelicGenotypeLikelihoods.java | 94 --------- .../walkers/genotyper/DiploidGenotype.java | 21 +- .../GenotypeLikelihoodsCalculationModel.java | 19 +- ...elGenotypeLikelihoodsCalculationModel.java | 60 ++++-- .../MultiallelicGenotypeLikelihoods.java | 52 ----- ...NPGenotypeLikelihoodsCalculationModel.java | 182 ++++++++++++------ .../genotyper/UnifiedGenotyperEngine.java | 79 +------- 7 files changed, 183 insertions(+), 324 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/BiallelicGenotypeLikelihoods.java delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/MultiallelicGenotypeLikelihoods.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/BiallelicGenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/BiallelicGenotypeLikelihoods.java deleted file mode 100644 index fbd9c1dbf..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/BiallelicGenotypeLikelihoods.java +++ /dev/null @@ -1,94 +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.broadinstitute.sting.utils.variantcontext.Allele; - -public class BiallelicGenotypeLikelihoods { - - private String sample; - private double[] GLs; - private Allele A, B; - private int depth; - - /** - * Create a new object for sample with given alleles and genotype likelihoods - * - * @param sample sample name - * @param A allele A - * @param B allele B - * @param log10AALikelihoods AA likelihoods - * @param log10ABLikelihoods AB likelihoods - * @param log10BBLikelihoods BB likelihoods - * @param depth the read depth used in creating the likelihoods - */ - public BiallelicGenotypeLikelihoods(String sample, - Allele A, - Allele B, - double log10AALikelihoods, - double log10ABLikelihoods, - double log10BBLikelihoods, - int depth) { - this.sample = sample; - this.A = A; - this.B = B; - this.GLs = new double[]{log10AALikelihoods, log10ABLikelihoods, log10BBLikelihoods}; - this.depth = depth; - } - - public String getSample() { - return sample; - } - - public double getAALikelihoods() { - return GLs[0]; - } - - public double getABLikelihoods() { - return GLs[1]; - } - - public double getBBLikelihoods() { - return GLs[2]; - } - - public double[] getLikelihoods() { - return GLs; - } - - public Allele getAlleleA() { - return A; - } - - public Allele getAlleleB() { - return B; - } - - public int getDepth() { - return depth; - } -} - 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 106bb1982..09936c112 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 @@ -27,13 +27,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.broadinstitute.sting.utils.BaseUtils; -/** - * Created by IntelliJ IDEA. - * User: depristo - * Date: Aug 4, 2009 - * Time: 6:46:09 PM - * To change this template use File | Settings | File Templates. - */ public enum DiploidGenotype { AA ('A', 'A'), AC ('A', 'C'), @@ -110,6 +103,20 @@ public enum DiploidGenotype { return conversionMatrix[index1][index2]; } + /** + * create a diploid genotype, given 2 base indexes which may not necessarily be ordered correctly + * @param baseIndex1 base1 + * @param baseIndex2 base2 + * @return the diploid genotype + */ + public static DiploidGenotype createDiploidGenotype(int baseIndex1, int baseIndex2) { + if ( baseIndex1 == -1 ) + throw new IllegalArgumentException(baseIndex1 + " does not represent a valid base character"); + if ( baseIndex2 == -1 ) + throw new IllegalArgumentException(baseIndex2 + " does not represent a valid base character"); + return conversionMatrix[baseIndex1][baseIndex2]; + } + private static final DiploidGenotype[][] conversionMatrix = { { DiploidGenotype.AA, DiploidGenotype.AC, DiploidGenotype.AG, DiploidGenotype.AT }, { DiploidGenotype.AC, DiploidGenotype.CC, DiploidGenotype.CG, DiploidGenotype.CT }, 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 74c55dbfe..b30a25414 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 @@ -35,6 +35,7 @@ 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; @@ -79,19 +80,17 @@ public abstract class GenotypeLikelihoodsCalculationModel implements Cloneable { * @param contexts stratified alignment contexts * @param contextType stratified context type * @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 should we use the BAQed pileup or the raw one? - * @return genotype likelihoods per sample for AA, AB, BB + * @return variant context where genotypes are no-called but with GLs */ - public abstract Allele getLikelihoods(RefMetaDataTracker tracker, - ReferenceContext ref, - Map contexts, - AlignmentContextUtils.ReadOrientation contextType, - GenotypePriors priors, - Map GLs, - Allele alternateAlleleToUse, - boolean useBAQedPileup); + public abstract VariantContext getLikelihoods(RefMetaDataTracker tracker, + ReferenceContext ref, + Map contexts, + AlignmentContextUtils.ReadOrientation contextType, + GenotypePriors priors, + Allele alternateAlleleToUse, + boolean useBAQedPileup); protected int getFilteredDepth(ReadBackedPileup pileup) { int count = 0; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java index 14d647b6d..653a6f6e7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java @@ -34,6 +34,7 @@ import org.broadinstitute.sting.gatk.walkers.indels.PairHMMIndelErrorModel; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.Haplotype; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.pileup.ExtendedEventPileupElement; import org.broadinstitute.sting.utils.pileup.PileupElement; @@ -41,8 +42,7 @@ import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.sam.ReadUtils; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; import java.util.*; @@ -243,7 +243,7 @@ public class IndelGenotypeLikelihoodsCalculationModel extends GenotypeLikelihood // get deletion length int dLen = Integer.valueOf(bestAltAllele.substring(1)); // get ref bases of accurate deletion - int startIdxInReference = (int)(1+loc.getStart()-ref.getWindow().getStart()); + int startIdxInReference = 1+loc.getStart()-ref.getWindow().getStart(); //System.out.println(new String(ref.getBases())); byte[] refBases = Arrays.copyOfRange(ref.getBases(),startIdxInReference,startIdxInReference+dLen); @@ -270,19 +270,17 @@ public class IndelGenotypeLikelihoodsCalculationModel extends GenotypeLikelihood private final static EnumSet allowableTypes = EnumSet.of(VariantContext.Type.INDEL, VariantContext.Type.MIXED); - public Allele getLikelihoods(RefMetaDataTracker tracker, - ReferenceContext ref, - Map contexts, - AlignmentContextUtils.ReadOrientation contextType, - GenotypePriors priors, - Map GLs, - Allele alternateAlleleToUse, - boolean useBAQedPileup) { + public VariantContext getLikelihoods(RefMetaDataTracker tracker, + ReferenceContext ref, + Map contexts, + AlignmentContextUtils.ReadOrientation contextType, + GenotypePriors priors, + Allele alternateAlleleToUse, + boolean useBAQedPileup) { if ( tracker == null ) return null; - GenomeLoc loc = ref.getLocus(); Allele refAllele, altAllele; VariantContext vc = null; @@ -368,10 +366,17 @@ public class IndelGenotypeLikelihoodsCalculationModel extends GenotypeLikelihood haplotypeMap = Haplotype.makeHaplotypeListFromAlleles(alleleList, loc.getStart(), ref, hsize, numPrefBases); + // start making the VariantContext + final int endLoc = calculateEndPos(alleleList, refAllele, loc); + final VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), endLoc, alleleList).referenceBaseForIndel(ref.getBase()); + + // create the genotypes; no-call everyone for now + GenotypesContext genotypes = GenotypesContext.create(); + final List noCall = new ArrayList(); + noCall.add(Allele.NO_CALL); + // For each sample, get genotype likelihoods based on pileup // compute prior likelihoods on haplotypes, and initialize haplotype likelihood matrix with them. - // initialize the GenotypeLikelihoods - GLs.clear(); for ( Map.Entry sample : contexts.entrySet() ) { AlignmentContext context = AlignmentContextUtils.stratify(sample.getValue(), contextType); @@ -384,11 +389,12 @@ public class IndelGenotypeLikelihoodsCalculationModel extends GenotypeLikelihood if (pileup != null ) { final double[] genotypeLikelihoods = pairModel.computeReadHaplotypeLikelihoods( pileup, haplotypeMap, ref, eventLength, getIndelLikelihoodMap()); + GenotypeLikelihoods likelihoods = GenotypeLikelihoods.fromLog10Likelihoods(genotypeLikelihoods); - GLs.put(sample.getKey(), new MultiallelicGenotypeLikelihoods(sample.getKey(), - alleleList, - genotypeLikelihoods, - getFilteredDepth(pileup))); + HashMap attributes = new HashMap(); + attributes.put(VCFConstants.DEPTH_KEY, getFilteredDepth(pileup)); + attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, likelihoods); + genotypes.add(new Genotype(sample.getKey(), noCall, Genotype.NO_LOG10_PERROR, null, attributes, false)); if (DEBUG) { System.out.format("Sample:%s Alleles:%s GL:",sample.getKey(), alleleList.toString()); @@ -399,9 +405,25 @@ public class IndelGenotypeLikelihoodsCalculationModel extends GenotypeLikelihood } } - return refAllele; + return builder.genotypes(genotypes).make(); } + private int calculateEndPos(Collection alleles, Allele refAllele, GenomeLoc loc) { + // for indels, stop location is one more than ref allele length + boolean hasNullAltAllele = false; + for ( Allele a : alleles ) { + if ( a.isNull() ) { + hasNullAltAllele = true; + break; + } + } + + int endLoc = loc.getStart() + refAllele.length(); + if( !hasNullAltAllele ) + endLoc--; + + return endLoc; + } public static HashMap> getIndelLikelihoodMap() { return indelLikelihoodMap.get(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/MultiallelicGenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/MultiallelicGenotypeLikelihoods.java deleted file mode 100755 index 4f378b24a..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/MultiallelicGenotypeLikelihoods.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.genotyper; - -import org.broadinstitute.sting.utils.exceptions.StingException; -import org.broadinstitute.sting.utils.variantcontext.Allele; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by IntelliJ IDEA. - * User: delangel - * Date: 6/1/11 - * Time: 10:38 AM - * To change this template use File | Settings | File Templates. - */ -public class MultiallelicGenotypeLikelihoods { - private String sample; - private double[] GLs; - private List alleleList; - private int depth; - - public MultiallelicGenotypeLikelihoods(String sample, - List A, - double[] log10Likelihoods, int depth) { - /* Check for consistency between likelihood vector and number of alleles */ - int numAlleles = A.size(); - if (log10Likelihoods.length != numAlleles*(numAlleles+1)/2) - throw new StingException(("BUG: Incorrect length of GL vector when creating MultiallelicGenotypeLikelihoods object!")); - - this.sample = sample; - this.alleleList = A; - this.GLs = log10Likelihoods; - this.depth = depth; - } - - public String getSample() { - return sample; - } - - public double[] getLikelihoods() { - return GLs; - } - - public List getAlleles() { - return alleleList; - } - - public int getDepth() { - return depth; - } - -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java index 9bdc754e9..4087443f8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java @@ -31,107 +31,147 @@ import org.broadinstitute.sting.gatk.contexts.AlignmentContextUtils; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.BaseUtils; +import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.baq.BAQ; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.pileup.ReadBackedPileupImpl; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; public class SNPGenotypeLikelihoodsCalculationModel extends GenotypeLikelihoodsCalculationModel { - // the alternate allele with the largest sum of quality scores - protected Byte bestAlternateAllele = null; + private static final int MIN_QUAL_SUM_FOR_ALT_ALLELE = 50; + + private boolean ALLOW_MULTIPLE_ALLELES; private final boolean useAlleleFromVCF; protected SNPGenotypeLikelihoodsCalculationModel(UnifiedArgumentCollection UAC, Logger logger) { super(UAC, logger); + ALLOW_MULTIPLE_ALLELES = UAC.MULTI_ALLELIC; useAlleleFromVCF = UAC.GenotypingMode == GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES; } - public Allele getLikelihoods(RefMetaDataTracker tracker, - ReferenceContext ref, - Map contexts, - AlignmentContextUtils.ReadOrientation contextType, - GenotypePriors priors, - Map GLs, - Allele alternateAlleleToUse, - boolean useBAQedPileup) { + public VariantContext getLikelihoods(RefMetaDataTracker tracker, + ReferenceContext ref, + Map contexts, + AlignmentContextUtils.ReadOrientation contextType, + GenotypePriors priors, + Allele alternateAlleleToUse, + boolean useBAQedPileup) { if ( !(priors instanceof DiploidSNPGenotypePriors) ) throw new StingException("Only diploid-based SNP priors are supported in the SNP GL model"); - byte refBase = ref.getBase(); - Allele refAllele = Allele.create(refBase, true); + final boolean[] basesToUse = new boolean[4]; + final byte refBase = ref.getBase(); + final int indexOfRefBase = BaseUtils.simpleBaseToBaseIndex(refBase); - // find the alternate allele with the largest sum of quality scores + // start making the VariantContext + final GenomeLoc loc = ref.getLocus(); + final List alleles = new ArrayList(); + alleles.add(Allele.create(refBase, true)); + final VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), loc.getStop(), alleles); + + // find the alternate allele(s) that we should be using if ( alternateAlleleToUse != null ) { - bestAlternateAllele = alternateAlleleToUse.getBases()[0]; + basesToUse[BaseUtils.simpleBaseToBaseIndex(alternateAlleleToUse.getBases()[0])] = true; } else if ( useAlleleFromVCF ) { - VariantContext vc = UnifiedGenotyperEngine.getVCFromAllelesRod(tracker, ref, ref.getLocus(), true, logger, UAC.alleles); + final VariantContext vc = UnifiedGenotyperEngine.getVCFromAllelesRod(tracker, ref, ref.getLocus(), true, logger, UAC.alleles); - // ignore places where we don't have a variant - if ( vc == null ) + // ignore places where we don't have a SNP + if ( vc == null || !vc.isSNP() ) return null; - if ( !vc.isBiallelic() ) { - // for multi-allelic sites go back to the reads and find the most likely alternate allele - initializeBestAlternateAllele(refBase, contexts, useBAQedPileup); - } else { - bestAlternateAllele = vc.getAlternateAllele(0).getBases()[0]; - } + for ( Allele allele : vc.getAlternateAlleles() ) + basesToUse[BaseUtils.simpleBaseToBaseIndex(allele.getBases()[0])] = true; } else { - initializeBestAlternateAllele(refBase, contexts, useBAQedPileup); + + determineAlternateAlleles(basesToUse, refBase, contexts, useBAQedPileup); + + // how many alternate alleles are we using? + int alleleCounter = countSetBits(basesToUse); + + // if there are no non-ref alleles... + if ( alleleCounter == 0 ) { + // if we only want variants, then we don't need to calculate genotype likelihoods + if ( UAC.OutputMode == UnifiedGenotyperEngine.OUTPUT_MODE.EMIT_VARIANTS_ONLY ) + return builder.make(); + + // otherwise, choose any alternate allele (it doesn't really matter) + basesToUse[indexOfRefBase == 0 ? 1 : 0] = true; + } } - // if there are no non-ref bases... - if ( bestAlternateAllele == null ) { - // if we only want variants, then we don't need to calculate genotype likelihoods - if ( UAC.OutputMode == UnifiedGenotyperEngine.OUTPUT_MODE.EMIT_VARIANTS_ONLY ) - return refAllele; - - // otherwise, choose any alternate allele (it doesn't really matter) - bestAlternateAllele = (byte)(refBase != 'A' ? 'A' : 'C'); + // create the alternate alleles and the allele ordering (the ordering is crucial for the GLs) + final int numAltAlleles = countSetBits(basesToUse); + final int[] alleleOrdering = new int[numAltAlleles + 1]; + alleleOrdering[0] = indexOfRefBase; + int alleleOrderingIndex = 1; + int numLikelihoods = 1; + for ( int i = 0; i < 4; i++ ) { + if ( i != indexOfRefBase && basesToUse[i] ) { + alleles.add(Allele.create(BaseUtils.baseIndexToSimpleBase(i), false)); + alleleOrdering[alleleOrderingIndex++] = i; + numLikelihoods += alleleOrderingIndex; + } } + builder.alleles(alleles); - Allele altAllele = Allele.create(bestAlternateAllele, false); + // create the genotypes; no-call everyone for now + GenotypesContext genotypes = GenotypesContext.create(); + final List noCall = new ArrayList(); + noCall.add(Allele.NO_CALL); for ( Map.Entry sample : contexts.entrySet() ) { ReadBackedPileup pileup = AlignmentContextUtils.stratify(sample.getValue(), contextType).getBasePileup(); - if( useBAQedPileup ) { pileup = createBAQedPileup( pileup ); } + if ( useBAQedPileup ) + pileup = createBAQedPileup( pileup ); // create the GenotypeLikelihoods object - DiploidSNPGenotypeLikelihoods GL = new DiploidSNPGenotypeLikelihoods((DiploidSNPGenotypePriors)priors, UAC.PCR_error); - int nGoodBases = GL.add(pileup, true, true, UAC.MIN_BASE_QUALTY_SCORE); + final DiploidSNPGenotypeLikelihoods GL = new DiploidSNPGenotypeLikelihoods((DiploidSNPGenotypePriors)priors, UAC.PCR_error); + final int nGoodBases = GL.add(pileup, true, true, UAC.MIN_BASE_QUALTY_SCORE); if ( nGoodBases == 0 ) continue; - double[] likelihoods = GL.getLikelihoods(); + final double[] allLikelihoods = GL.getLikelihoods(); + final double[] myLikelihoods = new double[numLikelihoods]; - DiploidGenotype refGenotype = DiploidGenotype.createHomGenotype(refBase); - DiploidGenotype hetGenotype = DiploidGenotype.createDiploidGenotype(refBase, bestAlternateAllele); - DiploidGenotype homGenotype = DiploidGenotype.createHomGenotype(bestAlternateAllele); - ArrayList aList = new ArrayList(); - aList.add(refAllele); - aList.add(altAllele); - double[] dlike = new double[]{likelihoods[refGenotype.ordinal()],likelihoods[hetGenotype.ordinal()],likelihoods[homGenotype.ordinal()]} ; + int myLikelihoodsIndex = 0; + for ( int i = 0; i <= numAltAlleles; i++ ) { + for ( int j = i; j <= numAltAlleles; j++ ) { + myLikelihoods[myLikelihoodsIndex++] = allLikelihoods[DiploidGenotype.createDiploidGenotype(alleleOrdering[i], alleleOrdering[j]).ordinal()]; + } + } // normalize in log space so that max element is zero. - GLs.put(sample.getKey(), new MultiallelicGenotypeLikelihoods(sample.getKey(), - aList, MathUtils.normalizeFromLog10(dlike, false, true), getFilteredDepth(pileup))); + GenotypeLikelihoods likelihoods = GenotypeLikelihoods.fromLog10Likelihoods(MathUtils.normalizeFromLog10(myLikelihoods, false, true)); + + HashMap attributes = new HashMap(); + attributes.put(VCFConstants.DEPTH_KEY, getFilteredDepth(pileup)); + attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, likelihoods); + genotypes.add(new Genotype(sample.getKey(), noCall, Genotype.NO_LOG10_PERROR, null, attributes, false)); } - return refAllele; + return builder.genotypes(genotypes).make(); } - protected void initializeBestAlternateAllele(byte ref, Map contexts, boolean useBAQedPileup) { + private int countSetBits(boolean[] array) { + int counter = 0; + for ( int i = 0; i < array.length; i++ ) { + if ( array[i] ) + counter++; + } + return counter; + } + + // fills in the allelesToUse array + protected void determineAlternateAlleles(boolean[] allelesToUse, byte ref, Map contexts, boolean useBAQedPileup) { int[] qualCounts = new int[4]; for ( Map.Entry sample : contexts.entrySet() ) { @@ -139,7 +179,7 @@ public class SNPGenotypeLikelihoodsCalculationModel extends GenotypeLikelihoodsC ReadBackedPileup pileup = useBAQedPileup ? createBAQedPileup( sample.getValue().getBasePileup() ) : sample.getValue().getBasePileup(); for ( PileupElement p : pileup ) { // ignore deletions - if ( p.isDeletion() || (! p.isReducedRead() && p.getQual() < UAC.MIN_BASE_QUALTY_SCORE )) + if ( p.isDeletion() || (!p.isReducedRead() && p.getQual() < UAC.MIN_BASE_QUALTY_SCORE) ) continue; final int index = BaseUtils.simpleBaseToBaseIndex(p.getBase()); @@ -149,17 +189,31 @@ public class SNPGenotypeLikelihoodsCalculationModel extends GenotypeLikelihoodsC } } - // set the non-ref base with maximum quality score sum - int maxCount = 0; - bestAlternateAllele = null; - for ( byte altAllele : BaseUtils.BASES ) { - if ( altAllele == ref ) - continue; - int index = BaseUtils.simpleBaseToBaseIndex(altAllele); - if ( qualCounts[index] > maxCount ) { - maxCount = qualCounts[index]; - bestAlternateAllele = altAllele; + if ( ALLOW_MULTIPLE_ALLELES ) { + for ( byte altAllele : BaseUtils.BASES ) { + if ( altAllele == ref ) + continue; + int index = BaseUtils.simpleBaseToBaseIndex(altAllele); + if ( qualCounts[index] >= MIN_QUAL_SUM_FOR_ALT_ALLELE ) { + allelesToUse[index] = true; + } } + } else { + // set the non-ref base which has the maximum quality score sum + int maxCount = 0; + int indexOfMax = 0; + for ( byte altAllele : BaseUtils.BASES ) { + if ( altAllele == ref ) + continue; + int index = BaseUtils.simpleBaseToBaseIndex(altAllele); + if ( qualCounts[index] > maxCount ) { + maxCount = qualCounts[index]; + indexOfMax = index; + } + } + + if ( maxCount > 0 ) + allelesToUse[indexOfMax] = true; } } 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 3a86743de..21aaeffba 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 @@ -219,14 +219,7 @@ public class UnifiedGenotyperEngine { glcm.set(getGenotypeLikelihoodsCalculationObject(logger, UAC)); } - Map GLs = new HashMap(); - - Allele refAllele = glcm.get().get(model).getLikelihoods(tracker, refContext, stratifiedContexts, type, getGenotypePriors(model), GLs, alternateAlleleToUse, useBAQedPileup && BAQEnabledOnCMDLine); - - if ( refAllele != null ) - return createVariantContextFromLikelihoods(refContext, refAllele, GLs); - else - return null; + return glcm.get().get(model).getLikelihoods(tracker, refContext, stratifiedContexts, type, getGenotypePriors(model), alternateAlleleToUse, useBAQedPileup && BAQEnabledOnCMDLine); } private VariantCallContext generateEmptyContext(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, AlignmentContext rawContext) { @@ -261,40 +254,6 @@ public class UnifiedGenotyperEngine { return new VariantCallContext(vc, false); } - private VariantContext createVariantContextFromLikelihoods(ReferenceContext refContext, Allele refAllele, Map GLs) { - // no-call everyone for now - List noCall = new ArrayList(); - noCall.add(Allele.NO_CALL); - - Set alleles = new LinkedHashSet(); - alleles.add(refAllele); - boolean addedAltAlleles = false; - - GenotypesContext genotypes = GenotypesContext.create(); - for ( MultiallelicGenotypeLikelihoods GL : GLs.values() ) { - if ( !addedAltAlleles ) { - addedAltAlleles = true; - // ordering important to maintain consistency - for (Allele a: GL.getAlleles()) { - alleles.add(a); - } - } - - HashMap attributes = new HashMap(); - //GenotypeLikelihoods likelihoods = new GenotypeLikelihoods(GL.getLikelihoods()); - GenotypeLikelihoods likelihoods = GenotypeLikelihoods.fromLog10Likelihoods(GL.getLikelihoods()); - attributes.put(VCFConstants.DEPTH_KEY, GL.getDepth()); - attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, likelihoods); - - 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 VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), endLoc, alleles).genotypes(genotypes).referenceBaseForIndel(refContext.getBase()).make(); - } - public VariantCallContext calculateGenotypes(VariantContext vc, final GenotypeLikelihoodsCalculationModel.Model model) { return calculateGenotypes(null, null, null, null, vc, model); } @@ -494,42 +453,6 @@ public class UnifiedGenotyperEngine { return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } - private int calculateEndPos(Collection alleles, Allele refAllele, GenomeLoc loc) { - // TODO - temp fix until we can deal with extended events properly - // for indels, stop location is one more than ref allele length - boolean isSNP = true, hasNullAltAllele = false; - for (Allele a : alleles){ - if (a.length() != 1) { - isSNP = false; - break; - } - } - for (Allele a : alleles){ - if (a.isNull()) { - hasNullAltAllele = true; - break; - } - } - // standard deletion: ref allele length = del length. endLoc = startLoc + refAllele.length(), alt allele = null - // standard insertion: ref allele length = 0, endLos = startLoc - // mixed: want end loc = start Loc for case {A*,AT,T} but say {ATG*,A,T} : want then end loc = start loc + refAllele.length - // So, in general, end loc = startLoc + refAllele.length, except in complex substitutions where it's one less - // - // todo - this is unnecessarily complicated and is so just because of Tribble's arbitrary vc conventions, should be cleaner/simpler, - // the whole vc processing infrastructure seems too brittle and riddled with special case handling - - - int endLoc = loc.getStart(); - if ( !isSNP) { - endLoc += refAllele.length(); - if(!hasNullAltAllele) - endLoc--; - - } - - return endLoc; - } - private Map getFilteredAndStratifiedContexts(UnifiedArgumentCollection UAC, ReferenceContext refContext, AlignmentContext rawContext, final GenotypeLikelihoodsCalculationModel.Model model) { Map stratifiedContexts = null; From 7fa1ab1bae051d1d4973f8c5e244a28b3c093e72 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Tue, 13 Dec 2011 17:19:40 -0500 Subject: [PATCH 259/380] Fix to allow haplotype caller to call indels after UG engine entry points were unified. Adding Haplotype Caller integration test --- .../sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 21aaeffba..2308e1759 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 @@ -371,8 +371,11 @@ public class UnifiedGenotyperEngine { builder.log10PError(phredScaledConfidence/-10.0); if ( ! passesCallThreshold(phredScaledConfidence) ) builder.filters(filter); - if ( !limitedContext ) + if ( limitedContext ) { + builder.referenceBaseForIndel(vc.getReferenceBaseForIndel()); + } else { builder.referenceBaseForIndel(refContext.getBase()); + } // create the genotypes GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse); From 079932ba2a2c0c26965640c79247d3cf1b6faec5 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 13 Dec 2011 23:36:10 -0500 Subject: [PATCH 263/380] The log10cache needs to be larger if we want to handle 10K samples in the UG. --- .../java/src/org/broadinstitute/sting/utils/MathUtils.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/MathUtils.java b/public/java/src/org/broadinstitute/sting/utils/MathUtils.java index f92d4be78..759e1649d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/MathUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/MathUtils.java @@ -1033,14 +1033,15 @@ public class MathUtils { public static final double JACOBIAN_LOG_TABLE_STEP = 0.1; public static final double INV_JACOBIAN_LOG_TABLE_STEP = 1.0/JACOBIAN_LOG_TABLE_STEP; public static final double MAX_JACOBIAN_TOLERANCE = 10.0; - private static final int MAXN = 10000; + private static final int MAXN = 11000; + private static final int LOG10_CACHE_SIZE = 4 * MAXN; // we need to be able to go up to 2*(2N) when calculating some of the coefficients static { - log10Cache = new double[2*MAXN]; + log10Cache = new double[LOG10_CACHE_SIZE]; jacobianLogTable = new double[JACOBIAN_LOG_TABLE_SIZE]; log10Cache[0] = Double.NEGATIVE_INFINITY; - for (int k=1; k < 2*MAXN; k++) + for (int k=1; k < LOG10_CACHE_SIZE; k++) log10Cache[k] = Math.log10(k); for (int k=0; k < JACOBIAN_LOG_TABLE_SIZE; k++) { From d3f4a5a9017b914894a73134abb8c7dbd5751ee6 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 10:37:38 -0500 Subject: [PATCH 265/380] Fail gracefully when encountering malformed VCFs without enough data columns --- .../org/broadinstitute/sting/utils/codecs/vcf/VCF3Codec.java | 2 ++ .../src/org/broadinstitute/sting/utils/codecs/vcf/VCFCodec.java | 2 ++ 2 files changed, 4 insertions(+) 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 aaa2e63a7..b3329c708 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 @@ -120,6 +120,8 @@ public class VCF3Codec extends AbstractVCFCodec { genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); + if ( nParts != genotypeParts.length ) + generateException("there are " + (nParts-1) + " genotypes while the header requires that " + (genotypeParts.length-1) + " genotypes be present for all records", lineNo); ArrayList genotypes = new ArrayList(nParts); 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 4c1bb1d9e..453155be7 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 @@ -147,6 +147,8 @@ public class VCFCodec extends AbstractVCFCodec { genotypeParts = new String[header.getColumnCount() - NUM_STANDARD_FIELDS]; int nParts = ParsingUtils.split(str, genotypeParts, VCFConstants.FIELD_SEPARATOR_CHAR); + if ( nParts != genotypeParts.length ) + generateException("there are " + (nParts-1) + " genotypes while the header requires that " + (genotypeParts.length-1) + " genotypes be present for all records", lineNo); ArrayList genotypes = new ArrayList(nParts); From 09a5a9eac08ec4b26983bff3c73a0373a91ca688 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 10:43:52 -0500 Subject: [PATCH 266/380] Don't update lineNo for decodeLoc - only for decode (otherwise they get double-counted). Even still, because of the way the GATK currently utilizes Tribble we can parse the same line multiple times, which knocks the line counter out of sync. For now, I've added a TODO in the code to remind us and the error messages note that it's an approximate line number. --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 3 ++- .../broadinstitute/sting/utils/exceptions/UserException.java | 4 ++-- 2 files changed, 4 insertions(+), 3 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 3009c236b..b902f220f 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 @@ -184,7 +184,6 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec { * @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; @@ -279,6 +278,8 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec { builder.source(getName()); // increment the line count + // TODO -- because of the way the engine utilizes Tribble, we can parse a line multiple times (especially when + // TODO -- the first record is far along the contig) and the line counter can get out of sync lineNo++; // parse out the required fields 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 c599d4759..a2816b58f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java +++ b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java @@ -184,11 +184,11 @@ public class UserException extends ReviewedStingException { public static class MalformedVCF extends UserException { public MalformedVCF(String message, String line) { - super(String.format("The provided VCF file is malformed at line %s: %s", line, message)); + super(String.format("The provided VCF file is malformed at approximately line %s: %s", line, message)); } public MalformedVCF(String message, int lineNo) { - super(String.format("The provided VCF file is malformed at line number %d: %s", lineNo, message)); + super(String.format("The provided VCF file is malformed at approximately line number %d: %s", lineNo, message)); } } From 9497e9492cb6904fac16349ae5cdaadb8a4350d8 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 11:21:28 -0500 Subject: [PATCH 267/380] Bug fix for complex records: do not ever reverse clip out a complete allele. --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 5 +++++ 1 file changed, 5 insertions(+) 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 b902f220f..e44c10f1f 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 @@ -595,6 +595,11 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec { if ( a.isSymbolic() ) continue; + // we need to ensure that we don't reverse clip out all of the bases from an allele because we then will have the wrong + // position set for the VariantContext (although it's okay to forward clip it all out, because the position will be fine). + if ( a.length() - clipping == 0 ) + return clipping - 1; + if ( a.length() - clipping <= forwardClipping || a.length() - forwardClipping == 0 ) stillClipping = false; else if ( ref.length() == clipping ) From 76485217184bc6f6891f7a3a76380b38a3fceb6f Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 11:26:43 -0500 Subject: [PATCH 268/380] Add check for mixed genotype so that we don't exception out for a valid record --- .../gatk/walkers/varianteval/evaluators/CountVariants.java | 2 ++ 1 file changed, 2 insertions(+) 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 c740eb78c..e5e8dfaf5 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 @@ -182,6 +182,8 @@ public class CountVariants extends VariantEvaluator implements StandardEval { nHomDerived++; } + break; + case MIXED: break; default: throw new ReviewedStingException("BUG: Unexpected genotype type: " + g); From 106bf13056969d13e01582a7088d168142d73519 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 12:05:50 -0500 Subject: [PATCH 269/380] Use a thread local result object to collect the results of the exact calculation instead of passing in multiple pre-allocated arrays. --- .../AlleleFrequencyCalculationModel.java | 7 +-- .../genotyper/ExactAFCalculationModel.java | 22 +++---- .../genotyper/UnifiedGenotyperEngine.java | 58 +++++++++---------- 3 files changed, 39 insertions(+), 48 deletions(-) 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 7d3e7047d..681cc1fa6 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 @@ -28,7 +28,6 @@ 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.GenotypesContext; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; import java.util.List; @@ -65,11 +64,9 @@ public abstract class AlleleFrequencyCalculationModel implements Cloneable { * @param GLs genotype likelihoods * @param Alleles Alleles corresponding to GLs * @param log10AlleleFrequencyPriors priors - * @param log10AlleleFrequencyLikelihoods array (pre-allocated) to store likelihoods results - * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store posteriors results + * @param result (pre-allocated) object to store likelihoods results */ protected abstract void getLog10PNonRef(GenotypesContext GLs, List Alleles, double[][] log10AlleleFrequencyPriors, - double[][] log10AlleleFrequencyLikelihoods, - double[][] log10AlleleFrequencyPosteriors); + AlleleFrequencyCalculationResult result); } \ 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 ed86897f2..33634ce28 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 @@ -47,12 +47,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public void getLog10PNonRef(final GenotypesContext GLs, final List alleles, final double[][] log10AlleleFrequencyPriors, - final double[][] log10AlleleFrequencyLikelihoods, - final double[][] log10AlleleFrequencyPosteriors) { + final AlleleFrequencyCalculationResult result) { final int numAlleles = alleles.size(); //linearExact(GLs, log10AlleleFrequencyPriors[0], log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); - linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors, false); + linearExactMultiAllelic(GLs, numAlleles - 1, log10AlleleFrequencyPriors, result, false); } private static final ArrayList getGLs(GenotypesContext GLs) { @@ -196,8 +195,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { public static void linearExactMultiAllelic(final GenotypesContext GLs, final int numAlternateAlleles, final double[][] log10AlleleFrequencyPriors, - final double[][] log10AlleleFrequencyLikelihoods, - final double[][] log10AlleleFrequencyPosteriors, + final AlleleFrequencyCalculationResult result, final boolean preserveData) { final ArrayList genotypeLikelihoods = getGLs(GLs); @@ -221,7 +219,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { while ( !ACqueue.isEmpty() ) { // compute log10Likelihoods final ExactACset set = ACqueue.remove(); - final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); + final double log10LofKs = calculateAlleleCountConformation(set, genotypeLikelihoods, maxLog10L, numChr, preserveData, ACqueue, indexesToACset, log10AlleleFrequencyPriors, result); // adjust max likelihood seen if needed maxLog10L = Math.max(maxLog10L, log10LofKs); @@ -236,14 +234,13 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final Queue ACqueue, final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, - final double[][] log10AlleleFrequencyLikelihoods, - final double[][] log10AlleleFrequencyPosteriors) { + final AlleleFrequencyCalculationResult result) { if ( DEBUG ) System.out.printf(" *** computing LofK for set=%s%n", set.ACcounts); // compute the log10Likelihoods - computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors); + computeLofK(set, genotypeLikelihoods, indexesToACset, log10AlleleFrequencyPriors, result); // clean up memory if ( !preserveData ) { @@ -349,8 +346,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final ArrayList genotypeLikelihoods, final HashMap indexesToACset, final double[][] log10AlleleFrequencyPriors, - final double[][] log10AlleleFrequencyLikelihoods, - final double[][] log10AlleleFrequencyPosteriors) { + final AlleleFrequencyCalculationResult result) { set.log10Likelihoods[0] = 0.0; // the zero case final int totalK = set.getACsum(); @@ -410,11 +406,11 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // update the likelihoods/posteriors vectors which are collapsed views of each of the various ACs for ( int i = 0; i < set.ACcounts.getCounts().length; i++ ) { int AC = set.ACcounts.getCounts()[i]; - log10AlleleFrequencyLikelihoods[i][AC] = approximateLog10SumLog10(log10AlleleFrequencyLikelihoods[i][AC], log10LofK); + result.log10AlleleFrequencyLikelihoods[i][AC] = approximateLog10SumLog10(result.log10AlleleFrequencyLikelihoods[i][AC], log10LofK); // for k=0 we still want to use theta final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][AC]; - log10AlleleFrequencyPosteriors[i][AC] = approximateLog10SumLog10(log10AlleleFrequencyPosteriors[i][AC], log10LofK + prior); + result.log10AlleleFrequencyPosteriors[i][AC] = approximateLog10SumLog10(result.log10AlleleFrequencyPosteriors[i][AC], log10LofK + prior); } } 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 2308e1759..5d271cdb1 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 @@ -75,14 +75,13 @@ public class UnifiedGenotyperEngine { // the model used for calculating p(non-ref) private ThreadLocal afcm = new ThreadLocal(); + // the allele frequency likelihoods (allocated once as an optimization) + private ThreadLocal alleleFrequencyCalculationResult = new ThreadLocal(); + // because the allele frequency priors are constant for a given i, we cache the results to avoid having to recompute everything private final double[][] log10AlleleFrequencyPriorsSNPs; private final double[][] log10AlleleFrequencyPriorsIndels; - // the allele frequency likelihoods (allocated once as an optimization) - private ThreadLocal log10AlleleFrequencyLikelihoods = new ThreadLocal(); - private ThreadLocal log10AlleleFrequencyPosteriors = new ThreadLocal(); - // the priors object private final GenotypePriors genotypePriorsSNPs; private final GenotypePriors genotypePriorsIndels; @@ -264,9 +263,8 @@ public class UnifiedGenotyperEngine { // initialize the data for this thread if that hasn't been done yet if ( afcm.get() == null ) { - log10AlleleFrequencyLikelihoods.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); - log10AlleleFrequencyPosteriors.set(new double[UAC.MAX_ALTERNATE_ALLELES][N+1]); afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); + alleleFrequencyCalculationResult.set(new AlleleFrequencyCalculationResult(UAC.MAX_ALTERNATE_ALLELES, N)); } // don't try to genotype too many alternate alleles @@ -285,9 +283,9 @@ public class UnifiedGenotyperEngine { } // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) - clearAFarray(log10AlleleFrequencyLikelihoods.get()); - clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); // is the most likely frequency conformation AC=0 for all alternate alleles? boolean bestGuessIsRef = true; @@ -299,7 +297,7 @@ public class UnifiedGenotyperEngine { // determine which alternate alleles have AF>0 boolean[] altAllelesToUse = new boolean[vc.getAlternateAlleles().size()]; for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { - int indexOfBestAC = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()[i]); + int indexOfBestAC = MathUtils.maxElementIndex(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[i]); // if the most likely AC is not 0, then this is a good alternate allele to use if ( indexOfBestAC != 0 ) { @@ -320,7 +318,7 @@ public class UnifiedGenotyperEngine { // calculate p(f>0) // TODO -- right now we just calculate it for the alt allele with highest AF, but the likelihoods need to be combined correctly over all AFs - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()[indexOfHighestAlt]); + double[] normalizedPosteriors = MathUtils.normalizeFromLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[indexOfHighestAlt]); double sum = 0.0; for (int i = 1; i <= N; i++) sum += normalizedPosteriors[i]; @@ -330,15 +328,15 @@ public class UnifiedGenotyperEngine { if ( !bestGuessIsRef || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { phredScaledConfidence = QualityUtils.phredScaleErrorRate(normalizedPosteriors[0]); if ( Double.isInfinite(phredScaledConfidence) ) - phredScaledConfidence = -10.0 * log10AlleleFrequencyPosteriors.get()[0][0]; + phredScaledConfidence = -10.0 * alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][0]; } else { phredScaledConfidence = QualityUtils.phredScaleErrorRate(PofF); if ( Double.isInfinite(phredScaledConfidence) ) { sum = 0.0; for (int i = 1; i <= N; i++) { - if ( log10AlleleFrequencyPosteriors.get()[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) + if ( alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) break; - sum += log10AlleleFrequencyPosteriors.get()[0][i]; + sum += alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][i]; } phredScaledConfidence = (MathUtils.compareDoubles(sum, 0.0) == 0 ? 0 : -10.0 * sum); } @@ -396,31 +394,31 @@ public class UnifiedGenotyperEngine { // the overall lod VariantContext vcOverall = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, vc.getAlternateAllele(0), false, model); - clearAFarray(log10AlleleFrequencyLikelihoods.get()); - clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); //double overallLog10PofNull = log10AlleleFrequencyPosteriors.get()[0]; - double overallLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); + double overallLog10PofF = MathUtils.log10sumLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0], 1); //if ( DEBUG_SLOD ) System.out.println("overallLog10PofF=" + overallLog10PofF); // the forward lod VariantContext vcForward = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.FORWARD, vc.getAlternateAllele(0), false, model); - clearAFarray(log10AlleleFrequencyLikelihoods.get()); - clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); //double[] normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); - double forwardLog10PofNull = log10AlleleFrequencyPosteriors.get()[0][0]; - double forwardLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); + double forwardLog10PofNull = alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][0]; + double forwardLog10PofF = MathUtils.log10sumLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0], 1); //if ( DEBUG_SLOD ) System.out.println("forwardLog10PofNull=" + forwardLog10PofNull + ", forwardLog10PofF=" + forwardLog10PofF); // the reverse lod VariantContext vcReverse = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.REVERSE, vc.getAlternateAllele(0), false, model); - clearAFarray(log10AlleleFrequencyLikelihoods.get()); - clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyLikelihoods.get(), log10AlleleFrequencyPosteriors.get()); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); + clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); //normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); - double reverseLog10PofNull = log10AlleleFrequencyPosteriors.get()[0][0]; - double reverseLog10PofF = MathUtils.log10sumLog10(log10AlleleFrequencyPosteriors.get()[0], 1); + double reverseLog10PofNull = alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][0]; + double reverseLog10PofF = MathUtils.log10sumLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0], 1); //if ( DEBUG_SLOD ) System.out.println("reverseLog10PofNull=" + reverseLog10PofNull + ", reverseLog10PofF=" + reverseLog10PofF); double forwardLod = forwardLog10PofF + reverseLog10PofNull - overallLog10PofF; @@ -587,10 +585,10 @@ public class UnifiedGenotyperEngine { AFline.append(i + "/" + N + "\t"); AFline.append(String.format("%.2f\t", ((float)i)/N)); AFline.append(String.format("%.8f\t", getAlleleFrequencyPriors(model)[i])); - if ( log10AlleleFrequencyPosteriors.get()[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED) + if ( alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED) AFline.append("0.00000000\t"); else - AFline.append(String.format("%.8f\t", log10AlleleFrequencyPosteriors.get()[i])); + AFline.append(String.format("%.8f\t", alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[i])); AFline.append(String.format("%.8f\t", normalizedPosteriors[i])); verboseWriter.println(AFline.toString()); } From 988d60091f79b1a58117f423e649e9d7ed4b5813 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 13:37:15 -0500 Subject: [PATCH 271/380] Forgot to add in the new result class --- .../AlleleFrequencyCalculationResult.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java new file mode 100644 index 000000000..66106f658 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Created by IntelliJ IDEA. + * User: ebanks + * Date: Dec 14, 2011 + * + * Useful helper class to communicate the results of the allele frequency calculation + */ +public class AlleleFrequencyCalculationResult { + + final double[][] log10AlleleFrequencyLikelihoods; + final double[][] log10AlleleFrequencyPosteriors; + + AlleleFrequencyCalculationResult(int maxAltAlleles, int numChr) { + log10AlleleFrequencyLikelihoods = new double[maxAltAlleles][numChr+1]; + log10AlleleFrequencyPosteriors = new double[maxAltAlleles][numChr+1]; + } +} \ No newline at end of file From 1e90d602a4c1628a0dde9764466f2c41c8aa8f9b Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 13:38:20 -0500 Subject: [PATCH 272/380] Optimization: cache up front the PL index to the pair of alleles it represents for all possible numbers of alternate alleles. --- .../genotyper/ExactAFCalculationModel.java | 26 +++++++----------- .../genotyper/UnifiedGenotyperEngine.java | 27 +++++++++++++++++-- .../ExactAFCalculationModelUnitTest.java | 11 ++++---- 3 files changed, 40 insertions(+), 24 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 33634ce28..bc27916dd 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 @@ -55,7 +55,7 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } private static final ArrayList getGLs(GenotypesContext GLs) { - ArrayList genotypeLikelihoods = new ArrayList(); // TODO -- initialize with size of GLs + ArrayList genotypeLikelihoods = new ArrayList(GLs.size()); genotypeLikelihoods.add(new double[]{0.0,0.0,0.0}); // dummy for ( Genotype sample : GLs.iterateInSampleNameOrder() ) { @@ -198,6 +198,10 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { final AlleleFrequencyCalculationResult result, final boolean preserveData) { + // make sure the PL cache has been initialized + if ( UnifiedGenotyperEngine.PLIndexToAlleleIndex == null ) + UnifiedGenotyperEngine.calculatePLcache(5); + final ArrayList genotypeLikelihoods = getGLs(GLs); final int numSamples = genotypeLikelihoods.size()-1; final int numChr = 2*numSamples; @@ -415,10 +419,6 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { } private static double determineCoefficient(int PLindex, final int j, final int[] ACcounts, final int totalK) { - // todo -- arent' there a small number of fixed values that this function can adopt? - // todo -- at a minimum it'd be good to partially compute some of these in ACCounts for performance - // todo -- need to cache PLIndex -> two alleles, compute looping over each PLIndex. Note all other operations are efficient - // todo -- this can be computed once at the start of the all operations // the closed form representation generalized for multiple alleles is as follows: // AA: (2j - totalK) * (2j - totalK - 1) @@ -434,25 +434,19 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { if ( PLindex <= numAltAlleles ) return MathUtils.log10Cache[2*ACcounts[PLindex-1]] + MathUtils.log10Cache[2*j-totalK]; - int subtractor = numAltAlleles+1; - int subtractions = 0; - do { - PLindex -= subtractor; - subtractor--; - subtractions++; - } - while ( PLindex >= subtractor ); + // find the 2 alternate alleles that are represented by this PL index + int[] alleles = UnifiedGenotyperEngine.PLIndexToAlleleIndex[numAltAlleles][PLindex]; - final int k_i = ACcounts[subtractions-1]; + final int k_i = ACcounts[alleles[0]-1]; // subtract one because ACcounts doesn't consider the reference allele // the hom var case (e.g. BB, CC, DD) final double coeff; - if ( PLindex == 0 ) { + if ( alleles[0] == alleles[1] ) { coeff = MathUtils.log10Cache[k_i] + MathUtils.log10Cache[k_i - 1]; } // the het non-ref case (e.g. BC, BD, CD) else { - final int k_j = ACcounts[subtractions+PLindex-1]; + final int k_j = ACcounts[alleles[1]-1]; coeff = MathUtils.log10Cache[2] + MathUtils.log10Cache[k_i] + MathUtils.log10Cache[k_j]; } 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 5d271cdb1..221d00410 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 @@ -102,6 +102,8 @@ public class UnifiedGenotyperEngine { private final GenomeLocParser genomeLocParser; private final boolean BAQEnabledOnCMDLine; + // a cache of the PL index to the 2 alleles it represents over all possible numbers of alternate alleles + protected static int[][][] PLIndexToAlleleIndex; // --------------------------------------------------------------------------------------------------------- @@ -135,6 +137,27 @@ public class UnifiedGenotyperEngine { genotypePriorsIndels = createGenotypePriors(GenotypeLikelihoodsCalculationModel.Model.INDEL); filter.add(LOW_QUAL_FILTER_NAME); + calculatePLcache(UAC.MAX_ALTERNATE_ALLELES); + } + + protected static void calculatePLcache(int maxAltAlleles) { + PLIndexToAlleleIndex = new int[maxAltAlleles+1][][]; + PLIndexToAlleleIndex[0] = null; + int numLikelihoods = 1; + + // for each count of alternate alleles + for ( int altAlleles = 1; altAlleles <= maxAltAlleles; altAlleles++ ) { + numLikelihoods += altAlleles + 1; + PLIndexToAlleleIndex[altAlleles] = new int[numLikelihoods][]; + int PLindex = 0; + + // for all possible combinations of the 2 alt alleles + for ( int allele1 = 0; allele1 <= altAlleles; allele1++ ) { + for ( int allele2 = allele1; allele2 <= altAlleles; allele2++ ) { + PLIndexToAlleleIndex[altAlleles][PLindex++] = new int[]{ allele1, allele2 }; + } + } + } } /** @@ -751,8 +774,8 @@ public class UnifiedGenotyperEngine { * * @return genotypes */ - public GenotypesContext assignGenotypes(VariantContext vc, - boolean[] allelesToUse) { + public GenotypesContext assignGenotypes(final VariantContext vc, + final boolean[] allelesToUse) { final GenotypesContext GLs = vc.getGenotypes(); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java index 9640a8963..dec6ecb79 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java @@ -83,21 +83,20 @@ public class ExactAFCalculationModelUnitTest extends BaseTest { @Test(dataProvider = "getGLs") public void testGLs(GetGLsTest cfg) { - final double[][] log10AlleleFrequencyLikelihoods = new double[2][2*numSamples+1]; - final double[][] log10AlleleFrequencyPosteriors = new double[2][2*numSamples+1]; + final AlleleFrequencyCalculationResult result = new AlleleFrequencyCalculationResult(2, 2*numSamples); for ( int i = 0; i < 2; i++ ) { for ( int j = 0; j < 2*numSamples+1; j++ ) { - log10AlleleFrequencyLikelihoods[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; - log10AlleleFrequencyPosteriors[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; + result.log10AlleleFrequencyLikelihoods[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; + result.log10AlleleFrequencyPosteriors[i][j] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; } } - ExactAFCalculationModel.linearExactMultiAllelic(cfg.GLs, cfg.numAltAlleles, priors, log10AlleleFrequencyLikelihoods, log10AlleleFrequencyPosteriors, false); + ExactAFCalculationModel.linearExactMultiAllelic(cfg.GLs, cfg.numAltAlleles, priors, result, false); int nameIndex = 1; for ( int allele = 0; allele < cfg.numAltAlleles; allele++, nameIndex+=2 ) { int expectedAlleleCount = Integer.valueOf(cfg.name.substring(nameIndex, nameIndex+1)); - int calculatedAlleleCount = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors[allele]); + int calculatedAlleleCount = MathUtils.maxElementIndex(result.log10AlleleFrequencyPosteriors[allele]); Assert.assertEquals(calculatedAlleleCount, expectedAlleleCount); } } From 35fc2e13c3677a2ff241447bf74e53a077d2b12d Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 15:31:09 -0500 Subject: [PATCH 273/380] Using the new PL cache, fix a bug: when only a subset of the genotyped alleles are used for assigning genotypes (because the exact model determined that they weren't all real) the PLs need to be adjusted to reflect this. While fixing this I discovered that the integration tests are busted because ref calls (ALT=.) were getting annotated with PLs, which makes no sense at all. --- ...NPGenotypeLikelihoodsCalculationModel.java | 14 +-- .../genotyper/UnifiedGenotyperEngine.java | 107 +++++++++--------- .../org/broadinstitute/sting/utils/Utils.java | 9 ++ 3 files changed, 66 insertions(+), 64 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java index 4087443f8..57cc5594a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/SNPGenotypeLikelihoodsCalculationModel.java @@ -33,6 +33,7 @@ import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.StingException; @@ -95,7 +96,7 @@ public class SNPGenotypeLikelihoodsCalculationModel extends GenotypeLikelihoodsC determineAlternateAlleles(basesToUse, refBase, contexts, useBAQedPileup); // how many alternate alleles are we using? - int alleleCounter = countSetBits(basesToUse); + int alleleCounter = Utils.countSetBits(basesToUse); // if there are no non-ref alleles... if ( alleleCounter == 0 ) { @@ -109,7 +110,7 @@ public class SNPGenotypeLikelihoodsCalculationModel extends GenotypeLikelihoodsC } // create the alternate alleles and the allele ordering (the ordering is crucial for the GLs) - final int numAltAlleles = countSetBits(basesToUse); + final int numAltAlleles = Utils.countSetBits(basesToUse); final int[] alleleOrdering = new int[numAltAlleles + 1]; alleleOrdering[0] = indexOfRefBase; int alleleOrderingIndex = 1; @@ -161,15 +162,6 @@ public class SNPGenotypeLikelihoodsCalculationModel extends GenotypeLikelihoodsC return builder.genotypes(genotypes).make(); } - private int countSetBits(boolean[] array) { - int counter = 0; - for ( int i = 0; i < array.length; i++ ) { - if ( array[i] ) - counter++; - } - return counter; - } - // fills in the allelesToUse array protected void determineAlternateAlleles(boolean[] allelesToUse, byte ref, Map contexts, boolean useBAQedPileup) { int[] qualCounts = new int[4]; 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 221d00410..f185af0c3 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 @@ -103,6 +103,7 @@ public class UnifiedGenotyperEngine { private final boolean BAQEnabledOnCMDLine; // a cache of the PL index to the 2 alleles it represents over all possible numbers of alternate alleles + // the representation is int[number of alternate alleles][PL index][pair of allele indexes (where reference = 0)] protected static int[][][] PLIndexToAlleleIndex; @@ -142,7 +143,7 @@ public class UnifiedGenotyperEngine { protected static void calculatePLcache(int maxAltAlleles) { PLIndexToAlleleIndex = new int[maxAltAlleles+1][][]; - PLIndexToAlleleIndex[0] = null; + PLIndexToAlleleIndex[0] = new int[][]{ new int[]{0, 0} }; int numLikelihoods = 1; // for each count of alternate alleles @@ -399,7 +400,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse); + GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse, myAlleles); // print out stats if we have a writer if ( verboseWriter != null && !limitedContext ) @@ -771,82 +772,82 @@ public class UnifiedGenotyperEngine { /** * @param vc variant context with genotype likelihoods * @param allelesToUse bit vector describing which alternate alleles from the vc are okay to use + * @param newAlleles a list of the final new alleles to use * * @return genotypes */ public GenotypesContext assignGenotypes(final VariantContext vc, - final boolean[] allelesToUse) { + final boolean[] allelesToUse, + final List newAlleles) { + // the no-called genotypes final GenotypesContext GLs = vc.getGenotypes(); + // samples final List sampleIndices = GLs.getSampleNamesOrderedByName(); + // the new called genotypes to create final GenotypesContext calls = GenotypesContext.create(); + // we need to determine which of the alternate alleles (and hence the likelihoods) to use and carry forward + final int numOriginalAltAlleles = allelesToUse.length; + final int numNewAltAlleles = newAlleles.size() - 1; + ArrayList likelihoodIndexesToUse = null; + + // an optimization: if we are supposed to use all (or none in the case of a ref call) of the alleles, + // then we can keep the PLs as is; otherwise, we determine which ones to keep + if ( numNewAltAlleles != numOriginalAltAlleles && numNewAltAlleles > 0 ) { + likelihoodIndexesToUse = new ArrayList(30); + final int[][] PLcache = PLIndexToAlleleIndex[numOriginalAltAlleles]; + + for ( int PLindex = 0; PLindex < PLcache.length; PLindex++ ) { + int[] alleles = PLcache[PLindex]; + // consider this entry only if both of the alleles are good + if ( (alleles[0] == 0 || allelesToUse[alleles[0] - 1]) && (alleles[1] == 0 || allelesToUse[alleles[1] - 1]) ) + likelihoodIndexesToUse.add(PLindex); + } + } + + // create the new genotypes for ( int k = GLs.size() - 1; k >= 0; k-- ) { final String sample = sampleIndices.get(k); final Genotype g = GLs.get(sample); if ( !g.hasLikelihoods() ) continue; - final double[] likelihoods = g.getLikelihoods().getAsVector(); + // create the new likelihoods array from the alleles we are allowed to use + final double[] originalLikelihoods = g.getLikelihoods().getAsVector(); + final double[] newLikelihoods; + if ( likelihoodIndexesToUse == null ) { + newLikelihoods = originalLikelihoods; + } else { + newLikelihoods = new double[likelihoodIndexesToUse.size()]; + int newIndex = 0; + for ( int oldIndex : likelihoodIndexesToUse ) + newLikelihoods[newIndex++] = originalLikelihoods[oldIndex]; + } - // if there is no mass on the likelihoods, then just no-call the sample - if ( MathUtils.sum(likelihoods) > SUM_GL_THRESH_NOCALL ) { + // if there is no mass on the (new) likelihoods and we actually have alternate alleles, then just no-call the sample + if ( MathUtils.sum(newLikelihoods) > SUM_GL_THRESH_NOCALL ) { calls.add(new Genotype(g.getSampleName(), NO_CALL_ALLELES, Genotype.NO_LOG10_PERROR, null, null, false)); continue; } - // genotype likelihoods are a linear vector that can be thought of as a row-wise upper triangular matrix of log10Likelihoods. - // so e.g. with 2 alt alleles the likelihoods are AA,AB,AC,BB,BC,CC and with 3 alt alleles they are AA,AB,AC,AD,BB,BC,BD,CC,CD,DD. - - final int numAltAlleles = allelesToUse.length; - - // start with the assumption that the ideal genotype is homozygous reference - Allele maxAllele1 = vc.getReference(), maxAllele2 = vc.getReference(); - double maxLikelihoodSeen = likelihoods[0]; - int indexOfMax = 0; - - // keep track of some state - Allele firstAllele = vc.getReference(); - int subtractor = numAltAlleles + 1; - int subtractionsMade = 0; - - for ( int i = 1, PLindex = 1; i < likelihoods.length; i++, PLindex++ ) { - if ( PLindex == subtractor ) { - firstAllele = vc.getAlternateAllele(subtractionsMade); - PLindex -= subtractor; - subtractor--; - subtractionsMade++; - - // we can skip this allele if it's not usable - if ( !allelesToUse[subtractionsMade-1] ) { - i += subtractor - 1; - PLindex += subtractor - 1; - continue; - } - } - - // we don't care about the entry if we've already seen better - if ( likelihoods[i] <= maxLikelihoodSeen ) - continue; - - // if it's usable then update the alleles - int alleleIndex = subtractionsMade + PLindex - 1; - if ( allelesToUse[alleleIndex] ) { - maxAllele1 = firstAllele; - maxAllele2 = vc.getAlternateAllele(alleleIndex); - maxLikelihoodSeen = likelihoods[i]; - indexOfMax = i; - } - } + // find the genotype with maximum likelihoods + int PLindex = numNewAltAlleles == 0 ? 0 : MathUtils.maxElementIndex(newLikelihoods); + int[] alleles = PLIndexToAlleleIndex[numNewAltAlleles][PLindex]; ArrayList myAlleles = new ArrayList(); - myAlleles.add(maxAllele1); - myAlleles.add(maxAllele2); + myAlleles.add(newAlleles.get(alleles[0])); + myAlleles.add(newAlleles.get(alleles[1])); - final double qual = GenotypeLikelihoods.getQualFromLikelihoods(indexOfMax, likelihoods); - calls.add(new Genotype(sample, myAlleles, qual, null, g.getAttributes(), false)); + final double qual = numNewAltAlleles == 0 ? Genotype.NO_LOG10_PERROR : GenotypeLikelihoods.getQualFromLikelihoods(PLindex, newLikelihoods); + Map attrs = new HashMap(g.getAttributes()); + if ( numNewAltAlleles == 0 ) + attrs.remove(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY); + else + attrs.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, newLikelihoods); + calls.add(new Genotype(sample, myAlleles, qual, null, attrs, false)); } return calls; diff --git a/public/java/src/org/broadinstitute/sting/utils/Utils.java b/public/java/src/org/broadinstitute/sting/utils/Utils.java index f0eb5d399..b79770eb5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/Utils.java +++ b/public/java/src/org/broadinstitute/sting/utils/Utils.java @@ -388,6 +388,15 @@ public class Utils { return reallocate(pos, z); } + public static int countSetBits(boolean[] array) { + int counter = 0; + for ( int i = 0; i < array.length; i++ ) { + if ( array[i] ) + counter++; + } + return counter; + } + /** * Returns new (reallocated) integer array of the specified size, with content * of the original array orig copied into it. If newSize is From 71b4bb12b7903e1542334b80a96ea3345a486967 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 14 Dec 2011 09:58:23 -0500 Subject: [PATCH 275/380] Bug fix for incorrect logic in subsetSamples -- Now properly handles the case where a sample isn't present (no longer adds a null to the genotypes list) -- Fix for logic failure where if the number of requested samples equals the number of known genotypes then all of the records were returned, which isn't correct when there are missing samples. -- Unit tests added to handle these cases --- .../utils/variantcontext/GenotypesContext.java | 15 ++++++++++----- .../variantcontext/VariantContextUnitTest.java | 3 +++ 2 files changed, 13 insertions(+), 5 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 845c65c9c..85f7cc078 100644 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypesContext.java @@ -410,6 +410,12 @@ public class GenotypesContext implements List { return getGenotypes().get(i); } + /** + * Gets sample associated with this sampleName, or null if none is found + * + * @param sampleName + * @return + */ public Genotype get(final String sampleName) { Integer offset = getSampleI(sampleName); return offset == null ? null : getGenotypes().get(offset); @@ -648,16 +654,15 @@ public class GenotypesContext implements List { @Ensures("result != null") public GenotypesContext subsetToSamples( final Set samples ) { final int nSamples = samples.size(); - final int nGenotypes = size(); - if ( nSamples == nGenotypes ) - return this; - else if ( nSamples == 0 ) + if ( nSamples == 0 ) return NO_GENOTYPES; else { // nGenotypes < nSamples final GenotypesContext subset = create(samples.size()); for ( final String sample : samples ) { - subset.add(get(sample)); + final Genotype g = get(sample); + if ( g != null ) + subset.add(g); } return subset; } 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 fca7440e4..0e75eee14 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -704,11 +704,14 @@ public class VariantContextUnitTest extends BaseTest { public Object[][] MakeSubContextTest() { for ( boolean updateAlleles : Arrays.asList(true, false)) { new SubContextTest(Collections.emptySet(), updateAlleles); + new SubContextTest(Collections.singleton("MISSING"), 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); + new SubContextTest(Arrays.asList("AA", "AT", "MISSING"), updateAlleles); + new SubContextTest(Arrays.asList("AA", "AT", "TT", "MISSING"), updateAlleles); } return SubContextTest.getTests(SubContextTest.class); From 01e547eed3b70f8c1c5fac507136264fe9f08200 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 14 Dec 2011 10:00:21 -0500 Subject: [PATCH 276/380] Parallel SAMDataSource initialization -- Uses 8 threads to load BAM files and indices in parallel, decreasing costs to read thousands of BAM files by a significant amount -- Added logger.info message noting progress and cost of reading low-level BAM data. --- .../gatk/datasources/reads/SAMDataSource.java | 131 +++++++++++++----- 1 file changed, 97 insertions(+), 34 deletions(-) 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 d70c63bd2..aacdf9b95 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 @@ -41,6 +41,7 @@ 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.SimpleTimer; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.baq.BAQSamIterator; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -51,6 +52,7 @@ import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; +import java.util.concurrent.*; /** * User: aaron @@ -199,7 +201,7 @@ public class SAMDataSource { BAQ.QualityMode.DONT_MODIFY, null, // no BAQ (byte) -1); - } + } /** * Create a new SAM data source given the supplied read metadata. @@ -253,17 +255,15 @@ public class SAMDataSource { if(readBufferSize != null) ReadShard.setReadBufferSize(readBufferSize); - for (SAMReaderID readerID : samFiles) { - if (!readerID.samFile.canRead()) - throw new UserException.CouldNotReadInputFile(readerID.samFile,"file is not present or user does not have appropriate permissions. " + - "Please check that the file is present and readable and try again."); - } - resourcePool = new SAMResourcePool(Integer.MAX_VALUE); SAMReaders readers = resourcePool.getAvailableReaders(); // Determine the sort order. for(SAMReaderID readerID: readerIDs) { + if (! readerID.samFile.canRead() ) + throw new UserException.CouldNotReadInputFile(readerID.samFile,"file is not present or user does not have appropriate permissions. " + + "Please check that the file is present and readable and try again."); + // Get the sort order, forcing it to coordinate if unsorted. SAMFileReader reader = readers.getReader(readerID); SAMFileHeader header = reader.getFileHeader(); @@ -711,29 +711,68 @@ public class SAMDataSource { * @param validationStringency validation stringency. */ public SAMReaders(Collection readerIDs, SAMFileReader.ValidationStringency validationStringency) { + final int N_THREADS = 8; int totalNumberOfFiles = readerIDs.size(); int readerNumber = 1; - for(SAMReaderID readerID: readerIDs) { - 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.setValidationStringency(validationStringency); - - logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, readerID.samFile)); - - readers.put(readerID,reader); + ExecutorService executor = Executors.newFixedThreadPool(N_THREADS); + final List inits = new ArrayList(totalNumberOfFiles); + Queue> futures = new LinkedList>(); + for (SAMReaderID readerID: readerIDs) { + logger.debug("Enqueuing for initialization: " + readerID.samFile); + final ReaderInitializer init = new ReaderInitializer(readerID); + inits.add(init); + futures.add(executor.submit(init)); } + + final SimpleTimer timer = new SimpleTimer(); + try { + final int MAX_WAIT = 30 * 1000; + final int MIN_WAIT = 1 * 1000; + + timer.start(); + while ( ! futures.isEmpty() ) { + final int prevSize = futures.size(); + final double waitTime = prevSize * (0.5 / N_THREADS); // about 0.5 seconds to load each file + final int waitTimeInMS = Math.min(MAX_WAIT, Math.max((int) (waitTime * 1000), MIN_WAIT)); + Thread.sleep(waitTimeInMS); + + Queue> pending = new LinkedList>(); + for ( final Future initFuture : futures ) { + if ( initFuture.isDone() ) { + final ReaderInitializer init = initFuture.get(); + if (threadAllocation.getNumIOThreads() > 0) { + inputStreams.put(init.readerID, init.blockInputStream); // get from initializer + } + logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, init.readerID)); + readers.put(init.readerID, init.reader); + } else { + pending.add(initFuture); + } + } + + final int pendingSize = pending.size(); + final int nExecutedInTick = prevSize - pendingSize; + final int nExecutedTotal = totalNumberOfFiles - pendingSize; + final double totalTimeInSeconds = timer.getElapsedTime(); + final double nTasksPerSecond = nExecutedTotal / (1.0*totalTimeInSeconds); + final int nRemaining = pendingSize; + final double estTimeToComplete = pendingSize / nTasksPerSecond; + logger.info(String.format("Init %d BAMs in last %d s, %d of %d in %.2f s / %.2f m (%.2f tasks/s). %d remaining with est. completion in %.2f s / %.2f m", + nExecutedInTick, (int)(waitTimeInMS / 1000.0), + nExecutedTotal, totalNumberOfFiles, totalTimeInSeconds, totalTimeInSeconds / 60, nTasksPerSecond, + nRemaining, estTimeToComplete, estTimeToComplete / 60)); + + futures = pending; + } + } catch ( InterruptedException e ) { + throw new ReviewedStingException("Interrupted SAMReader initialization", e); + } catch ( ExecutionException e ) { + throw new ReviewedStingException("Execution exception during SAMReader initialization", e); + } + + logger.info(String.format("Done initializing BAM readers: total time %.2f", timer.getElapsedTime())); + executor.shutdown(); } /** @@ -806,6 +845,30 @@ public class SAMDataSource { } } + class ReaderInitializer implements Callable { + final SAMReaderID readerID; + BlockInputStream blockInputStream = null; + SAMFileReader reader; + + public ReaderInitializer(final SAMReaderID readerID) { + this.readerID = readerID; + } + + public ReaderInitializer call() { + final File indexFile = findIndexFile(readerID.samFile); + if (threadAllocation.getNumIOThreads() > 0) { + blockInputStream = new BlockInputStream(dispatcher,readerID,false); + reader = new SAMFileReader(blockInputStream,indexFile,false); + } + else + reader = new SAMFileReader(readerID.samFile,indexFile,false); + reader.setSAMRecordFactory(factory); + reader.enableFileSource(true); + reader.setValidationStringency(validationStringency); + return this; + } + } + private class ReleasingIterator implements StingSAMIterator { /** * The resource acting as the source of the data. @@ -988,12 +1051,12 @@ public class SAMDataSource { return // Read ends on a later contig, or... read.getReferenceIndex() > intervalContigIndices[currentBound] || - // Read ends of this contig... - (read.getReferenceIndex() == intervalContigIndices[currentBound] && - // either after this location, or... - (read.getAlignmentEnd() >= intervalStarts[currentBound] || - // read is unmapped but positioned and alignment start is on or after this start point. - (read.getReadUnmappedFlag() && read.getAlignmentStart() >= intervalStarts[currentBound]))); + // Read ends of this contig... + (read.getReferenceIndex() == intervalContigIndices[currentBound] && + // either after this location, or... + (read.getAlignmentEnd() >= intervalStarts[currentBound] || + // read is unmapped but positioned and alignment start is on or after this start point. + (read.getReadUnmappedFlag() && read.getAlignmentStart() >= intervalStarts[currentBound]))); } /** @@ -1005,8 +1068,8 @@ public class SAMDataSource { return // Read starts on a prior contig, or... read.getReferenceIndex() < intervalContigIndices[currentBound] || - // Read starts on this contig and the alignment start is registered before this end point. - (read.getReferenceIndex() == intervalContigIndices[currentBound] && read.getAlignmentStart() <= intervalEnds[currentBound]); + // Read starts on this contig and the alignment start is registered before this end point. + (read.getReferenceIndex() == intervalContigIndices[currentBound] && read.getAlignmentStart() <= intervalEnds[currentBound]); } } From 4fddac9f22335388c13ce4c1719a6c99f56354b3 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 14 Dec 2011 16:24:43 -0500 Subject: [PATCH 278/380] Updating busted integration tests --- .../gatk/walkers/genotyper/UnifiedGenotyperEngine.java | 7 +++++-- .../walkers/genotyper/UnifiedGenotyperIntegrationTest.java | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) 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 f185af0c3..7e5f388b2 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 @@ -817,7 +817,7 @@ public class UnifiedGenotyperEngine { // create the new likelihoods array from the alleles we are allowed to use final double[] originalLikelihoods = g.getLikelihoods().getAsVector(); - final double[] newLikelihoods; + double[] newLikelihoods; if ( likelihoodIndexesToUse == null ) { newLikelihoods = originalLikelihoods; } else { @@ -825,6 +825,9 @@ public class UnifiedGenotyperEngine { int newIndex = 0; for ( int oldIndex : likelihoodIndexesToUse ) newLikelihoods[newIndex++] = originalLikelihoods[oldIndex]; + + // might need to re-normalize + newLikelihoods = MathUtils.normalizeFromLog10(newLikelihoods, false, true); } // if there is no mass on the (new) likelihoods and we actually have alternate alleles, then just no-call the sample @@ -846,7 +849,7 @@ public class UnifiedGenotyperEngine { if ( numNewAltAlleles == 0 ) attrs.remove(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY); else - attrs.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, newLikelihoods); + attrs.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(newLikelihoods)); calls.add(new Genotype(sample, myAlleles, qual, null, attrs, false)); } 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 4ae00431c..6041f80b8 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 @@ -129,8 +129,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testOutputParameter() { HashMap e = new HashMap(); e.put( "-sites_only", "44f3b5b40e6ad44486cddfdb7e0bfcd8" ); - e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "94e53320f14c5ff29d62f68d36b46fcd" ); - e.put( "--output_mode EMIT_ALL_SITES", "73ad1cc41786b12c5f0e6f3e9ec2b728" ); + e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "274bd9d1b9c7857690fa5f0228ffc6d7" ); + e.put( "--output_mode EMIT_ALL_SITES", "594c6d3c48bbc73289de7727d768644d" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( From c85100ce9cee310c013b8564a308dcd608f96098 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 14 Dec 2011 15:34:11 -0500 Subject: [PATCH 279/380] Fix ClippingOp bug when performing multiple hardclip ops bug: When performing multiple hard clip operations in a read that has indels, if the N+1 hardclip requests to clip inside an indel that has been removed by one of the (1..N) previous hardclips, the hard clipper would go out of bounds. fix: dynamically adjust the boundaries according to the new hardclipped read length. (this maintains the current contract that hardclipping will never return a read starting or ending in indels). --- .../sting/utils/clipreads/ReadClipper.java | 9 ++++++++- .../sting/utils/clipreads/ClipReadsTestUtils.java | 2 +- .../sting/utils/clipreads/ReadClipperUnitTest.java | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) 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 8c1061494..c3684034c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -168,7 +168,14 @@ public class ReadClipper { try { GATKSAMRecord clippedRead = (GATKSAMRecord) read.clone(); for (ClippingOp op : getOps()) { - clippedRead = op.apply(algorithm, clippedRead); + //check if the clipped read can still be clipped in the range requested + if (op.start < clippedRead.getReadLength()) { + ClippingOp fixedOperation = op; + if (op.stop > clippedRead.getReadLength()) + fixedOperation = new ClippingOp(op.start, clippedRead.getReadLength() - 1); + + clippedRead = fixedOperation.apply(algorithm, clippedRead); + } } wasClipped = true; ops.clear(); diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java index a5524e6f1..4a5b9107b 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java @@ -48,7 +48,7 @@ public class ClipReadsTestUtils { Assert.assertTrue(read.isEmpty()); } - private static byte[] subtractToArray(byte[] array, int n) { + public static byte[] subtractToArray(byte[] array, int n) { if (array == null) return null; 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 ff33e3184..264b73663 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -231,7 +231,7 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipLowQualEnds() { // Needs a thorough redesign - logger.warn("Executing testHardClipByReferenceCoordinates"); + logger.warn("Executing testHardClipLowQualEnds"); //Clip whole read Assert.assertEquals(readClipper.hardClipLowQualEnds((byte) 64), new GATKSAMRecord(readClipper.read.getHeader())); From 128bdf9c091cf4bb48ee34b2bc5c7f1d454314fe Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 14 Dec 2011 16:51:55 -0500 Subject: [PATCH 280/380] Create artificial reads with "default" parameters * added functions to create synthetic reads for unit testing with reasonable default parameters * added more functions to create synthetic reads based on cigar string + bases and quals. --- .../sting/utils/sam/ArtificialSAMUtils.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java b/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java index 26fabade2..cedd56bdf 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java @@ -200,6 +200,48 @@ public class ArtificialSAMUtils { return rec; } + /** + * Create an artificial read based on the parameters + * + * @param header the SAM header to associate the read with + * @param name the name of the read + * @param refIndex the reference index, i.e. what chromosome to associate it with + * @param alignmentStart where to start the alignment + * @param bases the sequence of the read + * @param qual the qualities of the read + * @param cigar the cigar string of the read + * + * @return the artificial read + */ + public static GATKSAMRecord createArtificialRead( SAMFileHeader header, String name, int refIndex, int alignmentStart, byte[] bases, byte[] qual, String cigar ) { + GATKSAMRecord rec = createArtificialRead(header, name, refIndex, alignmentStart, bases, qual); + rec.setCigarString(cigar); + return rec; + } + + /** + * Create an artificial read with the following default parameters : + * header: + * numberOfChromosomes = 1 + * startingChromosome = 1 + * chromosomeSize = 1000000 + * read: + * name = "default_read" + * refIndex = 0 + * alignmentStart = 1 + * + * @param bases the sequence of the read + * @param qual the qualities of the read + * @param cigar the cigar string of the read + * + * @return the artificial read + */ + public static GATKSAMRecord createArtificialRead( byte[] bases, byte[] qual, String cigar ) { + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000000); + return ArtificialSAMUtils.createArtificialRead(header, "default_read", 0, 1, bases, qual, cigar); + } + + public final static List createPair(SAMFileHeader header, String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { GATKSAMRecord left = ArtificialSAMUtils.createArtificialRead(header, name, 0, leftStart, readLen); GATKSAMRecord right = ArtificialSAMUtils.createArtificialRead(header, name, 0, rightStart, readLen); From 50dee86d7f76db5b57bd2b5c674f3f6b64854661 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 14 Dec 2011 16:52:43 -0500 Subject: [PATCH 281/380] Added unit test to catch Ryan's exception Unit test to catch the special case that broke the clipping op, fixed in the previous commit. --- .../utils/clipreads/ClipReadsTestUtils.java | 12 +++++ .../utils/clipreads/ReadClipperUnitTest.java | 44 ++++++------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java index 4a5b9107b..de9d8fb50 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java @@ -24,6 +24,18 @@ public class ClipReadsTestUtils { final static String BASES = "ACTG"; final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 + public static void assertEqualReads(GATKSAMRecord actual, GATKSAMRecord expected) { + // If they're both not empty, test their contents + if(!actual.isEmpty() && !expected.isEmpty()) { + Assert.assertEquals(actual.getReadBases(), expected.getReadBases()); + Assert.assertEquals(actual.getBaseQualities(), expected.getBaseQualities()); + Assert.assertEquals(actual.getCigarString(), expected.getCigarString()); + } + // Otherwise test if they're both empty + else + Assert.assertEquals(actual.isEmpty(), expected.isEmpty()); + } + public static void testBaseQualCigar(GATKSAMRecord read, byte[] readBases, byte[] baseQuals, String cigar) { // Because quals to char start at 33 for visibility baseQuals = subtractToArray(baseQuals, 33); 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 264b73663..650d3f26e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -30,6 +30,7 @@ import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; import net.sf.samtools.TextCigarCodec; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.sam.ReadUtils; import org.testng.Assert; @@ -230,42 +231,25 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipLowQualEnds() { - // Needs a thorough redesign logger.warn("Executing testHardClipLowQualEnds"); - //Clip whole read - Assert.assertEquals(readClipper.hardClipLowQualEnds((byte) 64), new GATKSAMRecord(readClipper.read.getHeader())); + // Testing clipping that ends inside an insertion + final byte[] BASES = {'A','C','G','T','A','C','G','T'}; + final byte[] QUALS = {2, 2, 2, 2, 20, 20, 20, 2}; + final String CIGAR = "1S1M5I1S"; - 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 + final byte[] CLIPPED_BASES = {}; + final byte[] CLIPPED_QUALS = {}; + final String CLIPPED_CIGAR = ""; - for (TestParameter p : testList) { - init(); - //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); - ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipLowQualEnds((byte) p.inputStart), - ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), - ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), - 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 - 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 + GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(BASES, QUALS, CIGAR); + GATKSAMRecord expected = ArtificialSAMUtils.createArtificialRead(CLIPPED_BASES, CLIPPED_QUALS, CLIPPED_CIGAR); + + ReadClipper lowQualClipper = new ReadClipper(read); + ClipReadsTestUtils.assertEqualReads(lowQualClipper.hardClipLowQualEnds((byte) 2), expected); + - 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); - testBaseQualCigar( readClipper.hardClipLowQualEnds( (byte)p.inputStart ), - BASES.substring(p.substringStart,p.substringStop).getBytes(), - QUALS.substring(p.substringStart,p.substringStop), - p.cigar ); - } - */ } @Test(enabled = false) From 6fb4be1a0985c7895c5cf65099feb920075f97b5 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Wed, 14 Dec 2011 18:05:31 -0500 Subject: [PATCH 283/380] Cache header merger. --- .../sting/gatk/datasources/reads/SAMDataSource.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 d70c63bd2..fdaca54de 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 @@ -95,6 +95,11 @@ public class SAMDataSource { */ private final Map readerPositions = new HashMap(); + /** + * Cached representation of the merged header used to generate a merging iterator. + */ + private final SamFileHeaderMerger headerMerger; + /** * The merged header. */ @@ -287,7 +292,7 @@ public class SAMDataSource { initializeReaderPositions(readers); - SamFileHeaderMerger headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,readers.headers(),true); + headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,readers.headers(),true); mergedHeader = headerMerger.getMergedHeader(); hasReadGroupCollisions = headerMerger.hasReadGroupCollisions(); @@ -535,8 +540,6 @@ public class SAMDataSource { * @return An iterator over the selected data. */ private StingSAMIterator getIterator(SAMReaders readers, Shard shard, boolean enableVerification) { - SamFileHeaderMerger headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,readers.headers(),true); - // Set up merging to dynamically merge together multiple BAMs. MergingSamRecordIterator mergingIterator = new MergingSamRecordIterator(headerMerger,readers.values(),true); From 550fb498beb48681b75b98e59aef5e6d7ead4385 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 14 Dec 2011 18:43:44 -0500 Subject: [PATCH 284/380] Support for NT testing (default up to 4) for CC and UG -- Added convenience function addJobReportBinding to just new binding to the map (x -> y) as well --- .../src/org/broadinstitute/sting/queue/util/QJobReport.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/scala/src/org/broadinstitute/sting/queue/util/QJobReport.scala b/public/scala/src/org/broadinstitute/sting/queue/util/QJobReport.scala index bb14bb6e6..73d1c028a 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/util/QJobReport.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/util/QJobReport.scala @@ -81,6 +81,10 @@ trait QJobReport extends Logging { this.reportFeatures = features.mapValues(_.toString) } + def addJobReportBinding(key: String, value: Any) { + this.reportFeatures += (key -> value.toString) + } + // copy the QJobReport information -- todo : what's the best way to do this? override def copySettingsTo(function: QFunction) { self.copySettingsTo(function) From 62a2e335bc1edb6b95cb2165b3032ea19842b9c1 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 15 Dec 2011 11:08:19 -0500 Subject: [PATCH 289/380] Changing HardClipper contract to allow UNMAPPED reads shifted the contract to functions that operate on reference based coordinates. The clipper should do the right thing with unmapped reads, but it needs more testing (Ryan is using it at the moment and says it works). Will write some unit tests. --- .../org/broadinstitute/sting/utils/clipreads/ClippingOp.java | 2 +- .../org/broadinstitute/sting/utils/clipreads/ReadClipper.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java index 4a253b217..97855080c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -247,7 +247,7 @@ public class ClippingOp { return newCigar; } - @Requires({"start <= stop", "start == 0 || stop == read.getReadLength() - 1", "!read.getReadUnmappedFlag()"}) + @Requires({"start <= stop", "start == 0 || stop == read.getReadLength() - 1"}) private GATKSAMRecord hardClip (GATKSAMRecord read, int start, int stop) { if (start == 0 && stop == read.getReadLength() - 1) return new GATKSAMRecord(read.getHeader()); 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 c3684034c..4df899888 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -58,6 +58,7 @@ public class ReadClipper { return hardClipByReferenceCoordinates(refStart, -1); } + @Requires("!read.getReadUnmappedFlag()") protected GATKSAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart, ReadUtils.ClippingTail.RIGHT_TAIL); int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop, ReadUtils.ClippingTail.LEFT_TAIL); From 4748ae0a140613f56c47013c78a2deb0146c6c40 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 14 Dec 2011 19:17:49 -0500 Subject: [PATCH 290/380] Bugfix: Softclips before Hardclips weren't being accounted for caught a bug in the hard clipper where it does not account for hard clipping softclipped bases in the resulting cigar string, if there is already a hard clipped base immediately after it. * updated unit test for hardClipSoftClippedBases with corresponding test-case. --- .../sting/utils/clipreads/ClippingOp.java | 4 + .../utils/clipreads/ReadClipperUnitTest.java | 97 ++++++++----------- 2 files changed, 42 insertions(+), 59 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java index 97855080c..06985041e 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -373,6 +373,10 @@ public class ClippingOp { while(cigarElementIterator.hasNext()) { cigarElement = cigarElementIterator.next(); alignmentShift += calculateHardClippingAlignmentShift(cigarElement, cigarElement.getLength()); + + // if the read had a HardClip operator in the end, combine it with the Hard Clip we are adding + if (cigarElement.getOperator() == CigarOperator.HARD_CLIP) + totalHardClipCount += cigarElement.getLength(); } newCigar.add(new CigarElement(totalHardClipCount + alignmentShift, CigarOperator.HARD_CLIP)); } 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 650d3f26e..f998a4e78 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -252,77 +252,56 @@ public class ReadClipperUnitTest extends BaseTest { } - @Test(enabled = false) + @Test(enabled = true) public void testHardClipSoftClippedBases() { // Generate a list of cigars to test for (Cigar cigar : ClipReadsTestUtils.generateCigars()) { - //logger.warn("Testing Cigar: "+cigar.toString()); - readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar(cigar)); + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + readClipper = new ReadClipper(read); + GATKSAMRecord clippedRead = readClipper.hardClipSoftClippedBases(); - int clipStart = 0; - int clipEnd = 0; - boolean expectEmptyRead = false; + int sumHardClips = 0; + int sumMatches = 0; - List cigarElements = cigar.getCigarElements(); - int CigarListLength = cigarElements.size(); + boolean tail = true; + for (CigarElement element : read.getCigar().getCigarElements()) { + // Assuming cigars are well formed, if we see S or H, it means we're on the tail (left or right) + if (element.getOperator() == CigarOperator.HARD_CLIP || element.getOperator() == CigarOperator.SOFT_CLIP) + tail = true; - // It will know what needs to be clipped based on the start and end of the string, hardclips and softclips - // are added to the amount to clip - if (cigarElements.get(0).getOperator() == CigarOperator.HARD_CLIP) { - //clipStart += cigarElements.get(0).getLength(); - if (cigarElements.get(1).getOperator() == CigarOperator.SOFT_CLIP) { - clipStart += cigarElements.get(1).getLength(); - // Check for leading indel - if (cigarElements.get(2).getOperator() == CigarOperator.INSERTION) { - expectEmptyRead = true; - } - } - // Check for leading indel - else if (cigarElements.get(1).getOperator() == CigarOperator.INSERTION) { - expectEmptyRead = true; - } - } else if (cigarElements.get(0).getOperator() == CigarOperator.SOFT_CLIP) { - clipStart += cigarElements.get(0).getLength(); - // Check for leading indel - if (cigarElements.get(1).getOperator() == CigarOperator.INSERTION) { - expectEmptyRead = true; - } - } - //Check for leading indel - else if (cigarElements.get(0).getOperator() == CigarOperator.INSERTION) { - expectEmptyRead = true; + // Adds all H, S and D's (next to hard/soft clips). + // All these should be hard clips after clipping. + if (tail && (element.getOperator() == CigarOperator.HARD_CLIP || element.getOperator() == CigarOperator.SOFT_CLIP || element.getOperator() == CigarOperator.DELETION)) + sumHardClips += element.getLength(); + + // this means we're no longer on the tail (insertions can still potentially be the tail because + // of the current contract of clipping out hanging insertions + else if (element.getOperator() != CigarOperator.INSERTION) + tail = false; + + // Adds all matches to verify that they remain the same after clipping + if (element.getOperator() == CigarOperator.MATCH_OR_MISMATCH) + sumMatches += element.getLength(); } - if (cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.HARD_CLIP) { - //clipEnd += cigarElements.get(CigarListLength - 1).getLength(); - if (cigarElements.get(CigarListLength - 2).getOperator() == CigarOperator.SOFT_CLIP) - clipEnd += cigarElements.get(CigarListLength - 2).getLength(); - } else if (cigarElements.get(CigarListLength - 1).getOperator() == CigarOperator.SOFT_CLIP) - clipEnd += cigarElements.get(CigarListLength - 1).getLength(); + for (CigarElement element : clippedRead.getCigar().getCigarElements()) { + // Test if clipped read has Soft Clips (shouldn't have any!) + Assert.assertTrue( element.getOperator() != CigarOperator.SOFT_CLIP, String.format("Cigar %s -> %s -- FAILED (resulting cigar has soft clips)", read.getCigarString(), clippedRead.getCigarString())); - String readBases = readClipper.read.getReadString(); - String baseQuals = readClipper.read.getBaseQualityString(); + // Keep track of the total number of Hard Clips after clipping to make sure everything was accounted for + if (element.getOperator() == CigarOperator.HARD_CLIP) + sumHardClips -= element.getLength(); - // "*" is the default empty-sequence-string and for our test it needs to be changed to "" - if (readBases.equals("*")) - readBases = ""; - if (baseQuals.equals("*")) - baseQuals = ""; + // Make sure all matches are still there + if (element.getOperator() == CigarOperator.MATCH_OR_MISMATCH) + sumMatches -= element.getLength(); + } + Assert.assertTrue( sumHardClips == 0, String.format("Cigar %s -> %s -- FAILED (number of hard clips mismatched by %d)", read.getCigarString(), clippedRead.getCigarString(), sumHardClips)); + Assert.assertTrue( sumMatches == 0, String.format("Cigar %s -> %s -- FAILED (number of matches mismatched by %d)", read.getCigarString(), clippedRead.getCigarString(), sumMatches)); - logger.warn(String.format("Testing cigar %s, expecting Base: %s and Qual: %s", - cigar.toString(), readBases.substring(clipStart, readBases.length() - clipEnd), - baseQuals.substring(clipStart, baseQuals.length() - clipEnd))); - //if (expectEmptyRead) - // testBaseQual( readClipper.hardClipSoftClippedBases(), new byte[0], new byte[0] ); - //else - ClipReadsTestUtils.testBaseQual(readClipper.hardClipSoftClippedBases(), - readBases.substring(clipStart, readBases.length() - clipEnd).getBytes(), - baseQuals.substring(clipStart, baseQuals.length() - clipEnd).getBytes()); - logger.warn("Cigar: " + cigar.toString() + " PASSED!"); + + logger.warn(String.format("Cigar %s -> %s -- PASSED!", read.getCigarString(), clippedRead.getCigarString())); } - // We will use testParameter in the following way - // Right tail, left tail, - } } \ No newline at end of file From e61e5c75891154393af93c9c529b60d446bf4bac Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 15 Dec 2011 13:09:46 -0500 Subject: [PATCH 292/380] Refactor of ReadClipper unit tests * expanded the systematic cigar string space test framework Roger wrote to all tests * moved utility functions into Utils and ReadUtils * cleaned up unused classes --- .../org/broadinstitute/sting/utils/Utils.java | 14 + .../sting/utils/clipreads/ReadClipper.java | 2 +- .../sting/utils/sam/ReadUtils.java | 5 + .../utils/clipreads/CigarStringTestPair.java | 18 -- .../utils/clipreads/ClipReadsTestUtils.java | 306 +++++++++--------- .../utils/clipreads/ClippingOpUnitTest.java | 66 ++-- .../utils/clipreads/ReadClipperUnitTest.java | 233 +++++-------- .../sting/utils/clipreads/TestParameter.java | 24 -- 8 files changed, 280 insertions(+), 388 deletions(-) delete mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java delete mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java diff --git a/public/java/src/org/broadinstitute/sting/utils/Utils.java b/public/java/src/org/broadinstitute/sting/utils/Utils.java index b79770eb5..10bc050da 100755 --- a/public/java/src/org/broadinstitute/sting/utils/Utils.java +++ b/public/java/src/org/broadinstitute/sting/utils/Utils.java @@ -654,4 +654,18 @@ public class Utils { // handle exception } } + + + public static byte [] arrayFromArrayWithLength(byte[] array, int length) { + byte [] output = new byte[length]; + for (int j = 0; j < length; j++) + output[j] = array[(j % array.length)]; + return output; + } + + public static void fillArrayWithByte(byte[] array, byte value) { + for (int i=0; i clippedRead.getReadLength()) + if (op.stop >= clippedRead.getReadLength()) fixedOperation = new ClippingOp(op.start, clippedRead.getReadLength() - 1); clippedRead = fixedOperation.apply(algorithm, clippedRead); 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 8d9018045..ac1f00437 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -910,4 +910,9 @@ public class ReadUtils { return comp.compare(read1, read2); } + // TEST UTILITIES + + + + } diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java deleted file mode 100644 index cc9021fae..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/CigarStringTestPair.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.broadinstitute.sting.utils.clipreads; - -/** - * Created by IntelliJ IDEA. - * User: roger - * Date: 11/29/11 - * Time: 4:53 PM - * To change this template use File | Settings | File Templates. - */ -public class CigarStringTestPair { - public String toTest; - public String expected; - - public CigarStringTestPair(String ToTest, String Expected) { - this.toTest = ToTest; - this.expected = Expected; - } -} diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java index de9d8fb50..c6dc3a833 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java @@ -2,8 +2,8 @@ package org.broadinstitute.sting.utils.clipreads; import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; -import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.TextCigarCodec; +import net.sf.samtools.CigarOperator; +import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; @@ -21,9 +21,149 @@ public class ClipReadsTestUtils { //Should contain all the utils needed for tests to mass produce //reads, cigars, and other needed classes - final static String BASES = "ACTG"; - final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 + final static byte [] BASES = {'A', 'C', 'T', 'G'}; + final static byte [] QUALS = {2, 15, 25, 30}; + final static String CIGAR = "4M"; + final static CigarElement[] cigarElements = { new CigarElement(1, CigarOperator.HARD_CLIP), + new CigarElement(1, CigarOperator.SOFT_CLIP), + new CigarElement(1, CigarOperator.INSERTION), + new CigarElement(1, CigarOperator.DELETION), + new CigarElement(1, CigarOperator.MATCH_OR_MISMATCH)}; + + public static GATKSAMRecord makeReadFromCigar(Cigar cigar) { + return ArtificialSAMUtils.createArtificialRead(Utils.arrayFromArrayWithLength(BASES, cigar.getReadLength()), Utils.arrayFromArrayWithLength(QUALS, cigar.getReadLength()), cigar.toString()); + } + + /** + * This function generates every valid permutation of cigar strings with a given length. + * + * A valid cigar object obeys the following rules: + * - No Hard/Soft clips in the middle of the read + * - No deletions in the beginning / end of the read + * - No repeated adjacent element (e.g. 1M2M -> this should be 3M) + * + * @param maximumLength the maximum number of elements in the cigar + * @return a list with all valid Cigar objects + */ + public static List generateCigarList(int maximumLength) { + int numCigarElements = cigarElements.length; + LinkedList cigarList = new LinkedList(); + byte [] cigarCombination = new byte[maximumLength]; + + Utils.fillArrayWithByte(cigarCombination, (byte) 0); // we start off with all 0's in the combination array. + int currentIndex = 0; + while (true) { + Cigar cigar = createCigarFromCombination(cigarCombination); // create the cigar + cigar = combineAdjacentCigarElements(cigar); // combine adjacent elements + if (isCigarValid(cigar)) { // check if it's valid + cigarList.add(cigar); // add it + } + + boolean currentIndexChanged = false; + while (currentIndex < maximumLength && cigarCombination[currentIndex] == numCigarElements - 1) { + currentIndex++; // find the next index to increment + currentIndexChanged = true; // keep track of the fact that we have changed indices! + } + + if (currentIndex == maximumLength) // if we hit the end of the array, we're done. + break; + + cigarCombination[currentIndex]++; // otherwise advance the current index + + if (currentIndexChanged) { // if we have changed index, then... + for (int i = 0; i < currentIndex; i++) + cigarCombination[i] = 0; // reset everything from 0->currentIndex + currentIndex = 0; // go back to the first index + } + } + + return cigarList; + } + + private static boolean isCigarValid(Cigar cigar) { + if (cigar.isValid(null, -1) == null) { // This should take care of most invalid Cigar Strings (picard's "exhaustive" implementation) + + Stack cigarElementStack = new Stack(); // Stack to invert cigar string to find ending operator + CigarOperator startingOp = null; + CigarOperator endingOp = null; + + // check if it doesn't start with deletions + boolean readHasStarted = false; // search the list of elements for the starting operator + for (CigarElement cigarElement : cigar.getCigarElements()) { + if (!readHasStarted) { + if (cigarElement.getOperator() != CigarOperator.SOFT_CLIP && cigarElement.getOperator() != CigarOperator.HARD_CLIP) { + readHasStarted = true; + startingOp = cigarElement.getOperator(); + } + } + cigarElementStack.push(cigarElement); + } + + readHasStarted = false; // search the inverted list of elements (stack) for the stopping operator + while (!cigarElementStack.empty()) { + CigarElement cigarElement = cigarElementStack.pop(); + if (cigarElement.getOperator() != CigarOperator.SOFT_CLIP && cigarElement.getOperator() != CigarOperator.HARD_CLIP) { + readHasStarted = true; + endingOp = cigarElement.getOperator(); + break; + } + } + + if (startingOp != CigarOperator.DELETION && endingOp != CigarOperator.DELETION && startingOp != CigarOperator.INSERTION && endingOp != CigarOperator.INSERTION) + return true; // we don't accept reads starting or ending in deletions (add any other constraint here) + } + + return false; + } + + private static Cigar createCigarFromCombination(byte[] cigarCombination) { + Cigar cigar = new Cigar(); + for (byte i : cigarCombination) { + cigar.add(cigarElements[i]); + } + return cigar; + } + + + /** + * Combines equal adjacent elements of a Cigar object + * + * @param rawCigar the cigar object + * @return a combined cigar object + */ + private static Cigar combineAdjacentCigarElements(Cigar rawCigar) { + Cigar combinedCigar = new Cigar(); + CigarElement lastElement = null; + int lastElementLength = 0; + for (CigarElement cigarElement : rawCigar.getCigarElements()) { + if (lastElement != null && lastElement.getOperator() == cigarElement.getOperator()) + lastElementLength += cigarElement.getLength(); + else + { + if (lastElement != null) + combinedCigar.add(new CigarElement(lastElementLength, lastElement.getOperator())); + + lastElement = cigarElement; + lastElementLength = cigarElement.getLength(); + } + } + if (lastElement != null) + combinedCigar.add(new CigarElement(lastElementLength, lastElement.getOperator())); + + return combinedCigar; + } + + public static GATKSAMRecord makeRead() { + return ArtificialSAMUtils.createArtificialRead(BASES, QUALS, CIGAR); + } + + /** + * Asserts that the two reads have the same bases, qualities and cigar strings + * + * @param actual the calculated read + * @param expected the expected read + */ public static void assertEqualReads(GATKSAMRecord actual, GATKSAMRecord expected) { // If they're both not empty, test their contents if(!actual.isEmpty() && !expected.isEmpty()) { @@ -36,162 +176,4 @@ public class ClipReadsTestUtils { Assert.assertEquals(actual.isEmpty(), expected.isEmpty()); } - public static void testBaseQualCigar(GATKSAMRecord read, byte[] readBases, byte[] baseQuals, String cigar) { - // Because quals to char start at 33 for visibility - baseQuals = subtractToArray(baseQuals, 33); - - Assert.assertEquals(read.getReadBases(), readBases); - Assert.assertEquals(read.getBaseQualities(), baseQuals); - Assert.assertEquals(read.getCigarString(), cigar); - } - - public static void testCigar(GATKSAMRecord read, String cigar) { - Assert.assertEquals(read.getCigarString(), cigar); - } - - public static void testBaseQual(GATKSAMRecord read, byte[] readBases, byte[] baseQuals) { - // Because quals to chars start at 33 for visibility - baseQuals = subtractToArray(baseQuals, 33); - - if (readBases.length > 0 && baseQuals.length > 0) { - Assert.assertEquals(read.getReadBases(), readBases); - Assert.assertEquals(read.getBaseQualities(), baseQuals); - } else - Assert.assertTrue(read.isEmpty()); - } - - public static byte[] subtractToArray(byte[] array, int n) { - if (array == null) - return null; - - byte[] output = new byte[array.length]; - - for (int i = 0; i < array.length; i++) - output[i] = (byte) (array[i] - n); - - return output; - } - - // What the test read looks like - // Ref: 10 11 12 13 14 15 16 17 - // Read: 0 1 2 3 - - - - - // -------------------------------- - // Bases: A C T G - - - - - // Quals: ! + 5 ? - - - - - - public static GATKSAMRecord makeRead() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); - GATKSAMRecord output = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 10, BASES.length()); - output.setReadBases(new String(BASES).getBytes()); - output.setBaseQualityString(new String(QUALS)); - - return output; - } - - public static GATKSAMRecord makeReadFromCigar(Cigar cigar) { - - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); - GATKSAMRecord output = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 10, cigar.getReadLength()); - output.setReadBases(cycleString(BASES, cigar.getReadLength()).getBytes()); - output.setBaseQualityString(cycleString(QUALS, cigar.getReadLength())); - output.setCigar(cigar); - - return output; - } - - private static String cycleString(String string, int length) { - String output = ""; - int cycles = (length / string.length()) + 1; - - for (int i = 1; i < cycles; i++) - output += string; - - for (int j = 0; output.length() < length; j++) - output += string.charAt(j % string.length()); - - return output; - } - - public static Set generateCigars() { - - // This function generates every permutation of cigar strings we need. - - LinkedHashSet output = new LinkedHashSet(); - - List clippingOptionsStart = new LinkedList(); - clippingOptionsStart.add(new Cigar()); - clippingOptionsStart.add(TextCigarCodec.getSingleton().decode("1H1S")); - clippingOptionsStart.add(TextCigarCodec.getSingleton().decode("1S")); - clippingOptionsStart.add(TextCigarCodec.getSingleton().decode("1H")); - - LinkedList clippingOptionsEnd = new LinkedList(); - clippingOptionsEnd.add(new Cigar()); - clippingOptionsEnd.add(TextCigarCodec.getSingleton().decode("1S1H")); - clippingOptionsEnd.add(TextCigarCodec.getSingleton().decode("1S")); - clippingOptionsEnd.add(TextCigarCodec.getSingleton().decode("1H")); - - - LinkedList indelOptions1 = new LinkedList(); - indelOptions1.add(new Cigar()); - //indelOptions1.add( TextCigarCodec.getSingleton().decode("1I1D")); - //indelOptions1.add( TextCigarCodec.getSingleton().decode("1D1I") ); - indelOptions1.add(TextCigarCodec.getSingleton().decode("1I")); - indelOptions1.add(TextCigarCodec.getSingleton().decode("1D")); - - LinkedList indelOptions2 = new LinkedList(); - indelOptions2.add(new Cigar()); - indelOptions2.add(TextCigarCodec.getSingleton().decode("1I")); - indelOptions2.add(null); - - - // Start With M as base CigarElements, M, - - LinkedList base = new LinkedList(); - base.add(TextCigarCodec.getSingleton().decode("1M")); - base.add(TextCigarCodec.getSingleton().decode("5M")); - base.add(TextCigarCodec.getSingleton().decode("25M")); - // Should indel be added as a base? - - // Nested loops W00t! - for (Cigar Base : base) { - for (Cigar indelStart : indelOptions1) { - for (Cigar indelEnd : indelOptions2) { - for (Cigar clipStart : clippingOptionsStart) { - for (Cigar clipEnd : clippingOptionsEnd) { - // Create a list of Cigar Elements and construct Cigar - List CigarBuilder = new ArrayList(); - // add starting clipping (H/S) - CigarBuilder.addAll(clipStart.getCigarElements()); - // add first base (M) - CigarBuilder.addAll(Base.getCigarElements()); - // add first indel - CigarBuilder.addAll(indelStart.getCigarElements()); - // add second base (M) - CigarBuilder.addAll(Base.getCigarElements()); - // add another indel or nothing (M) - if (indelEnd != null) - CigarBuilder.addAll(indelEnd.getCigarElements()); - // add final clipping (S/H) - CigarBuilder.addAll(clipEnd.getCigarElements()); - - - output.add(new Cigar(removeConsecutiveElements(CigarBuilder))); - - } - } - } - } - } - - return output; - } - - private static List removeConsecutiveElements(List cigarBuilder) { - LinkedList output = new LinkedList(); - for (CigarElement E : cigarBuilder) { - if (output.isEmpty() || output.getLast().getOperator() != E.getOperator()) - output.add(E); - } - return output; - } } diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java index 719d04287..dd674ff1c 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java @@ -5,8 +5,6 @@ import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; -import java.util.LinkedList; -import java.util.List; /** * Created by IntelliJ IDEA. @@ -27,43 +25,43 @@ public class ClippingOpUnitTest extends BaseTest { @Test private void testHardClip() { - 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 - testList.add(new TestParameter(0, 2, 3, 4, "3H1M"));//clip 3 bases at start - testList.add(new TestParameter(1, 3, 0, 1, "1M3H"));//clip 3 bases at end - - for (TestParameter p : testList) { - init(); - clippingOp = new ClippingOp(p.inputStart, p.inputStop); - logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.substringStart + "," + p.substringStop + "," + p.cigar); - ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.HARDCLIP_BASES, read), - ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), - ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), - p.cigar); - } +// 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 +// testList.add(new TestParameter(0, 2, 3, 4, "3H1M"));//clip 3 bases at start +// testList.add(new TestParameter(1, 3, 0, 1, "1M3H"));//clip 3 bases at end +// +// for (TestParameter p : testList) { +// init(); +// clippingOp = new ClippingOp(p.inputStart, p.inputStop); +// logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.substringStart + "," + p.substringStop + "," + p.cigar); +// ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.HARDCLIP_BASES, read), +// ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), +// ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), +// p.cigar); +// } } @Test private void testSoftClip() { - List testList = new LinkedList(); - testList.add(new TestParameter(0, 0, -1, -1, "1S3M"));//clip 1 base at start - testList.add(new TestParameter(3, 3, -1, -1, "3M1S"));//clip 1 base at end - testList.add(new TestParameter(0, 1, -1, -1, "2S2M"));//clip 2 bases at start - testList.add(new TestParameter(2, 3, -1, -1, "2M2S"));//clip 2 bases at end - testList.add(new TestParameter(0, 2, -1, -1, "3S1M"));//clip 3 bases at start - testList.add(new TestParameter(1, 3, -1, -1, "1M3S"));//clip 3 bases at end - - for (TestParameter p : testList) { - init(); - clippingOp = new ClippingOp(p.inputStart, p.inputStop); - logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.cigar); - ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.SOFTCLIP_BASES, read), - ClipReadsTestUtils.BASES.getBytes(), ClipReadsTestUtils.QUALS.getBytes(), p.cigar); - } +// List testList = new LinkedList(); +// testList.add(new TestParameter(0, 0, -1, -1, "1S3M"));//clip 1 base at start +// testList.add(new TestParameter(3, 3, -1, -1, "3M1S"));//clip 1 base at end +// testList.add(new TestParameter(0, 1, -1, -1, "2S2M"));//clip 2 bases at start +// testList.add(new TestParameter(2, 3, -1, -1, "2M2S"));//clip 2 bases at end +// testList.add(new TestParameter(0, 2, -1, -1, "3S1M"));//clip 3 bases at start +// testList.add(new TestParameter(1, 3, -1, -1, "1M3S"));//clip 3 bases at end +// +// for (TestParameter p : testList) { +// init(); +// clippingOp = new ClippingOp(p.inputStart, p.inputStop); +// logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.cigar); +// ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.SOFTCLIP_BASES, read), +// ClipReadsTestUtils.BASES.getBytes(), ClipReadsTestUtils.QUALS.getBytes(), p.cigar); +// } } } 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 f998a4e78..fc1459ee0 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -28,16 +28,15 @@ package org.broadinstitute.sting.utils.clipreads; import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; -import net.sf.samtools.TextCigarCodec; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import org.broadinstitute.sting.utils.sam.ReadUtils; import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.LinkedList; import java.util.List; /** @@ -53,157 +52,36 @@ public class ReadClipperUnitTest extends BaseTest { // TODO: add indels to all test cases - ReadClipper readClipper; + List cigarList; + int maximumCigarSize = 10; - @BeforeMethod + @BeforeClass public void init() { - readClipper = new ReadClipper(ClipReadsTestUtils.makeRead()); + cigarList = ClipReadsTestUtils.generateCigarList(maximumCigarSize); } @Test(enabled = true) public void testHardClipBothEndsByReferenceCoordinates() { - logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); - //int debug = 1; - //Clip whole read - Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(10, 10), new GATKSAMRecord(readClipper.read.getHeader())); - //clip 1 base - ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipBothEndsByReferenceCoordinates(10, 13), - ClipReadsTestUtils.BASES.substring(1, 3).getBytes(), ClipReadsTestUtils.QUALS.substring(1, 3).getBytes(), - "1H2M1H"); - - List cigarStringTestPairs = new LinkedList(); - cigarStringTestPairs.add(new CigarStringTestPair("5M1D1M2I4M5I6M1D3M2I100M", "1H4M1D1M2I4M5I6M1D3M2I99M1H")); - //cigarStringTestPairs.add( new CigarStringTestPair("5M1I1M2I1M","1H4M1I1M2I1H")); - cigarStringTestPairs.add(new CigarStringTestPair("1S1M1I1M1I1M1I1M1I1M1I1M1S", "1H1M1I1M1I1M1I1M1I1M1I1M1H")); - cigarStringTestPairs.add(new CigarStringTestPair("1S1M1D1M1D1M1D1M1D1M1D1M1S", "1H1M1D1M1D1M1D1M1D1M1D1M1H")); - - for (CigarStringTestPair pair : cigarStringTestPairs) { - readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar(TextCigarCodec.getSingleton().decode(pair.toTest))); - ClipReadsTestUtils.testCigar(readClipper.hardClipBothEndsByReferenceCoordinates( - ReadUtils.getRefCoordSoftUnclippedStart(readClipper.read), - ReadUtils.getRefCoordSoftUnclippedEnd(readClipper.read)), - pair.expected); - } - /* - for ( Cigar cigar: ClipReadsTestUtils.generateCigars() ) { - // The read has to be long enough to clip one base from each side - // This also filters a lot of cigars - if ( cigar.getReadLength() > 26 ) { - readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar( cigar )); - System.out.println( "Testing Cigar: "+cigar.toString() ) ; - //cigar length reference plus soft clip - - ClipReadsTestUtils.testBaseQual( - readClipper.hardClipBothEndsByReferenceCoordinates( - ReadUtils.getRefCoordSoftUnclippedStart(readClipper.read), - ReadUtils.getRefCoordSoftUnclippedEnd(readClipper.read) ), - readClipper.read.getReadString().substring(1, (cigar.getReadLength() - 1)).getBytes(), - readClipper.read.getBaseQualityString().substring(1, (cigar.getReadLength() - 1)).getBytes()); - } - } - */ } @Test(enabled = true) public void testHardClipByReadCoordinates() { - logger.warn("Executing testHardClipByReadCoordinates"); - //Clip whole read - Assert.assertEquals(readClipper.hardClipByReadCoordinates(0, 3), new GATKSAMRecord(readClipper.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); - ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReadCoordinates(p.inputStart, p.inputStop), - ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), - ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), - p.cigar); - } - } @Test(enabled = true) public void testHardClipByReferenceCoordinates() { logger.warn("Executing testHardClipByReferenceCoordinates"); - //logger.warn(debug); - //Clip whole read - Assert.assertEquals(readClipper.hardClipByReferenceCoordinates(10, 13), new GATKSAMRecord(readClipper.read.getHeader())); - List testList = new LinkedList(); - testList.add(new TestParameter(-1, 10, 1, 4, "1H3M"));//clip 1 base at start - testList.add(new TestParameter(13, -1, 0, 3, "3M1H"));//clip 1 base at end - testList.add(new TestParameter(-1, 11, 2, 4, "2H2M"));//clip 2 bases at start - testList.add(new TestParameter(12, -1, 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); - ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReferenceCoordinates(p.inputStart, p.inputStop), - ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), - ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), - p.cigar); - } - - List cigarStringTestPairs = new LinkedList(); - cigarStringTestPairs.add(new CigarStringTestPair("5M1D1M2I4M5I6M1D3M2I100M", "1H4M1D1M2I4M5I6M1D3M2I100M")); - //cigarStringTestPairs.add( new CigarStringTestPair("5M1I1M2I1M","1H4M1I1M2I1M")); - cigarStringTestPairs.add(new CigarStringTestPair("1S1M1I1M1I1M1I1M1I1M1I1M1S", "1H1M1I1M1I1M1I1M1I1M1I1M1S")); - cigarStringTestPairs.add(new CigarStringTestPair("1S1M1D1M1D1M1D1M1D1M1D1M1S", "1H1M1D1M1D1M1D1M1D1M1D1M1S")); - - //Clips only first base - for (CigarStringTestPair pair : cigarStringTestPairs) { - readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar(TextCigarCodec.getSingleton().decode(pair.toTest))); - ClipReadsTestUtils.testCigar(readClipper.hardClipByReadCoordinates(0, 0), pair.expected); - } - /* - for ( Cigar cigar: ClipReadsTestUtils.generateCigars() ) { - // The read has to be long enough to clip one base - // This also filters a lot of cigars - if ( cigar.getReadLength() > 26 ) { - readClipper = new ReadClipper(ClipReadsTestUtils.makeReadFromCigar( cigar )); - System.out.println( "Testing Cigar: "+cigar.toString() ) ; - //cigar length reference plus soft clip - - // Clip first read - ClipReadsTestUtils.testBaseQual( - readClipper.hardClipByReadCoordinates(0,0), - readClipper.read.getReadString().substring(1, cigar.getReadLength()).getBytes(), - readClipper.read.getBaseQualityString().substring(1, cigar.getReadLength()).getBytes()); - } - } - */ } @Test(enabled = true) public void testHardClipByReferenceCoordinatesLeftTail() { - init(); logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); - //Clip whole read - Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(13), new GATKSAMRecord(readClipper.read.getHeader())); - - List testList = new LinkedList(); - testList.add(new TestParameter(10, -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); - ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesLeftTail(p.inputStart), - ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), - ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), - p.cigar); - } - } @Test(enabled = true) @@ -211,29 +89,80 @@ public class ReadClipperUnitTest extends BaseTest { init(); logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); - //Clip whole read - Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(10), new GATKSAMRecord(readClipper.read.getHeader())); - - List testList = new LinkedList(); - testList.add(new TestParameter(-1, 13, 0, 3, "3M1H"));//clip 1 base at end - testList.add(new TestParameter(-1, 12, 0, 2, "2M2H"));//clip 2 bases at end - - for (TestParameter p : testList) { - init(); - //logger.warn("Testing Parameters: " + p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); - ClipReadsTestUtils.testBaseQualCigar(readClipper.hardClipByReferenceCoordinatesRightTail(p.inputStop), - ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), - ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), - p.cigar); - } - } @Test(enabled = true) public void testHardClipLowQualEnds() { logger.warn("Executing testHardClipLowQualEnds"); - // Testing clipping that ends inside an insertion + final byte LOW_QUAL = 2; + final byte HIGH_QUAL = 30; + + // create a read for every cigar permutation + for (Cigar cigar : cigarList) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + int readLength = read.getReadLength(); + byte [] quals = new byte[readLength]; + + for (int nLowQualBases = 0; nLowQualBases < readLength; nLowQualBases++) { + + // create a read with nLowQualBases in the left tail + Utils.fillArrayWithByte(quals, HIGH_QUAL); + for (int addLeft = 0; addLeft < nLowQualBases; addLeft++) + quals[addLeft] = LOW_QUAL; + read.setBaseQualities(quals); + GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipLowQualEnds(LOW_QUAL); + + // Tests + + // Make sure the low qualities are gone + testNoLowQualBases(clipLeft, LOW_QUAL); + + // Can't run this test with the current contract of no hanging insertions + //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipLeft.getReadLength(), readLength - nLowQualBases, read.getCigarString(), clipLeft.getCigarString())); + + // create a read with nLowQualBases in the right tail + Utils.fillArrayWithByte(quals, HIGH_QUAL); + for (int addRight = 0; addRight < nLowQualBases; addRight++) + quals[readLength - addRight - 1] = LOW_QUAL; + read.setBaseQualities(quals); + GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipLowQualEnds(LOW_QUAL); + + // Tests + + // Make sure the low qualities are gone + testNoLowQualBases(clipRight, LOW_QUAL); + + // Make sure we haven't clipped any high quals -- Can't run this test with the current contract of no hanging insertions + //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipRight.getReadLength(), readLength - nLowQualBases, read.getCigarString(), clipRight.getCigarString())); + + // create a read with nLowQualBases in the both tails + if (nLowQualBases <= readLength/2) { + Utils.fillArrayWithByte(quals, HIGH_QUAL); + for (int addBoth = 0; addBoth < nLowQualBases; addBoth++) { + quals[addBoth] = LOW_QUAL; + quals[readLength - addBoth - 1] = LOW_QUAL; + } + read.setBaseQualities(quals); + GATKSAMRecord clipBoth = (new ReadClipper(read)).hardClipLowQualEnds(LOW_QUAL); + + // Tests + + // Make sure the low qualities are gone + testNoLowQualBases(clipBoth, LOW_QUAL); + + // Can't run this test with the current contract of no hanging insertions + //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipRight.getReadLength(), readLength - (2*nLowQualBases), read.getCigarString(), clipBoth.getCigarString())); + } + } +// logger.warn(String.format("Testing %s for all combinations of low/high qual... PASSED", read.getCigarString())); + } + + + + + + // ONE OFF Testing clipping that ends inside an insertion final byte[] BASES = {'A','C','G','T','A','C','G','T'}; final byte[] QUALS = {2, 2, 2, 2, 20, 20, 20, 2}; final String CIGAR = "1S1M5I1S"; @@ -248,17 +177,23 @@ public class ReadClipperUnitTest extends BaseTest { ReadClipper lowQualClipper = new ReadClipper(read); ClipReadsTestUtils.assertEqualReads(lowQualClipper.hardClipLowQualEnds((byte) 2), expected); + } - + private void testNoLowQualBases(GATKSAMRecord read, byte low_qual) { + if (!read.isEmpty()) { + byte [] quals = read.getBaseQualities(); + for (int i=0; i %s -- FAILED (number of matches mismatched by %d)", read.getCigarString(), clippedRead.getCigarString(), sumMatches)); - logger.warn(String.format("Cigar %s -> %s -- PASSED!", read.getCigarString(), clippedRead.getCigarString())); +// logger.warn(String.format("Cigar %s -> %s -- PASSED!", read.getCigarString(), clippedRead.getCigarString())); } } } \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java deleted file mode 100644 index 155fe094e..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/TestParameter.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.broadinstitute.sting.utils.clipreads; - -/** - * Created by IntelliJ IDEA. - * User: roger - * Date: 11/28/11 - * Time: 4:07 PM - * To change this template use File | Settings | File Templates. - */ -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; - } -} From fb1c9d2abcdea57ba25440e4c620001210ede257 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 16 Dec 2011 09:05:28 -0500 Subject: [PATCH 294/380] Restored serial version of reader initialization. Parallel mode is default. -- Serial version can be re-enabled with a static boolean, if we decide to return to the serial version --- .../gatk/datasources/reads/SAMDataSource.java | 137 +++++++++++------- 1 file changed, 83 insertions(+), 54 deletions(-) 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 d5d8ab2ef..e3b469b3d 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 @@ -63,6 +63,7 @@ import java.util.concurrent.*; */ public class SAMDataSource { final private static GATKSamRecordFactory factory = new GATKSamRecordFactory(); + final private static boolean USE_PARALLEL_LOADING = true; /** Backing support for reads. */ protected final ReadProperties readProperties; @@ -714,68 +715,96 @@ public class SAMDataSource { * @param validationStringency validation stringency. */ public SAMReaders(Collection readerIDs, SAMFileReader.ValidationStringency validationStringency) { - final int N_THREADS = 8; - int totalNumberOfFiles = readerIDs.size(); + final int totalNumberOfFiles = readerIDs.size(); int readerNumber = 1; + final SimpleTimer timer = new SimpleTimer().start(); - ExecutorService executor = Executors.newFixedThreadPool(N_THREADS); - final List inits = new ArrayList(totalNumberOfFiles); - Queue> futures = new LinkedList>(); - for (SAMReaderID readerID: readerIDs) { - logger.debug("Enqueuing for initialization: " + readerID.samFile); - final ReaderInitializer init = new ReaderInitializer(readerID); - inits.add(init); - futures.add(executor.submit(init)); - } - - final SimpleTimer timer = new SimpleTimer(); - try { - final int MAX_WAIT = 30 * 1000; - final int MIN_WAIT = 1 * 1000; - - timer.start(); - while ( ! futures.isEmpty() ) { - final int prevSize = futures.size(); - final double waitTime = prevSize * (0.5 / N_THREADS); // about 0.5 seconds to load each file - final int waitTimeInMS = Math.min(MAX_WAIT, Math.max((int) (waitTime * 1000), MIN_WAIT)); - Thread.sleep(waitTimeInMS); - - Queue> pending = new LinkedList>(); - for ( final Future initFuture : futures ) { - if ( initFuture.isDone() ) { - final ReaderInitializer init = initFuture.get(); - if (threadAllocation.getNumIOThreads() > 0) { - inputStreams.put(init.readerID, init.blockInputStream); // get from initializer - } - logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, init.readerID)); - readers.put(init.readerID, init.reader); - } else { - pending.add(initFuture); - } + logger.info("Initializing SAMRecords " + (USE_PARALLEL_LOADING ? "in parallel" : "in serial")); + if ( ! USE_PARALLEL_LOADING ) { + final int tickSize = 10; + int nExecutedTotal = 0; + long lastTick = timer.currentTime(); + for(final SAMReaderID readerID: readerIDs) { + final ReaderInitializer init = new ReaderInitializer(readerID).call(); + if (threadAllocation.getNumIOThreads() > 0) { + inputStreams.put(init.readerID, init.blockInputStream); // get from initializer } - final int pendingSize = pending.size(); - final int nExecutedInTick = prevSize - pendingSize; - final int nExecutedTotal = totalNumberOfFiles - pendingSize; - final double totalTimeInSeconds = timer.getElapsedTime(); - final double nTasksPerSecond = nExecutedTotal / (1.0*totalTimeInSeconds); - final int nRemaining = pendingSize; - final double estTimeToComplete = pendingSize / nTasksPerSecond; - logger.info(String.format("Init %d BAMs in last %d s, %d of %d in %.2f s / %.2f m (%.2f tasks/s). %d remaining with est. completion in %.2f s / %.2f m", - nExecutedInTick, (int)(waitTimeInMS / 1000.0), - nExecutedTotal, totalNumberOfFiles, totalTimeInSeconds, totalTimeInSeconds / 60, nTasksPerSecond, - nRemaining, estTimeToComplete, estTimeToComplete / 60)); - - futures = pending; + logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, readerID.samFile)); + readers.put(init.readerID,init.reader); + if ( nExecutedTotal++ % tickSize == 0) { + int tickInMS = (int)((timer.currentTime() - lastTick) / 1000.0); + printReaderPerformance(nExecutedTotal, tickSize, totalNumberOfFiles, timer, tickInMS); + } } - } catch ( InterruptedException e ) { - throw new ReviewedStingException("Interrupted SAMReader initialization", e); - } catch ( ExecutionException e ) { - throw new ReviewedStingException("Execution exception during SAMReader initialization", e); + } else { + final int N_THREADS = 8; + + final ExecutorService executor = Executors.newFixedThreadPool(N_THREADS); + final List inits = new ArrayList(totalNumberOfFiles); + Queue> futures = new LinkedList>(); + for (final SAMReaderID readerID: readerIDs) { + logger.debug("Enqueuing for initialization: " + readerID.samFile); + final ReaderInitializer init = new ReaderInitializer(readerID); + inits.add(init); + futures.add(executor.submit(init)); + } + + try { + final int MAX_WAIT = 30 * 1000; + final int MIN_WAIT = 1 * 1000; + + while ( ! futures.isEmpty() ) { + final int prevSize = futures.size(); + final double waitTime = prevSize * (0.5 / N_THREADS); // about 0.5 seconds to load each file + final int waitTimeInMS = Math.min(MAX_WAIT, Math.max((int) (waitTime * 1000), MIN_WAIT)); + Thread.sleep(waitTimeInMS); + + Queue> pending = new LinkedList>(); + for ( final Future initFuture : futures ) { + if ( initFuture.isDone() ) { + final ReaderInitializer init = initFuture.get(); + if (threadAllocation.getNumIOThreads() > 0) { + inputStreams.put(init.readerID, init.blockInputStream); // get from initializer + } + logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, init.readerID)); + readers.put(init.readerID, init.reader); + } else { + pending.add(initFuture); + } + } + + final int nExecutedTotal = totalNumberOfFiles - pending.size(); + final int nExecutedInTick = prevSize - pending.size(); + printReaderPerformance(nExecutedTotal, nExecutedInTick, totalNumberOfFiles, timer, (int)(waitTimeInMS / 1000)); + futures = pending; + } + } catch ( InterruptedException e ) { + throw new ReviewedStingException("Interrupted SAMReader initialization", e); + } catch ( ExecutionException e ) { + throw new ReviewedStingException("Execution exception during SAMReader initialization", e); + } + + executor.shutdown(); } logger.info(String.format("Done initializing BAM readers: total time %.2f", timer.getElapsedTime())); - executor.shutdown(); + } + + final private void printReaderPerformance(final int nExecutedTotal, + final int nExecutedInTick, + final int totalNumberOfFiles, + final SimpleTimer timer, final int tickDuration) { + final int pendingSize = totalNumberOfFiles - nExecutedTotal; + final double totalTimeInSeconds = timer.getElapsedTime(); + final double nTasksPerSecond = nExecutedTotal / (1.0*totalTimeInSeconds); + final int nRemaining = pendingSize; + final double estTimeToComplete = pendingSize / nTasksPerSecond; + logger.info(String.format("Init %d BAMs in last %d s, %d of %d in %.2f s / %.2f m (%.2f tasks/s). %d remaining with est. completion in %.2f s / %.2f m", + nExecutedInTick, tickDuration, + nExecutedTotal, totalNumberOfFiles, totalTimeInSeconds, totalTimeInSeconds / 60, nTasksPerSecond, + nRemaining, estTimeToComplete, estTimeToComplete / 60)); + } /** From 3414ecfe2e07934539fbc194fe95ee8a879ab757 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 16 Dec 2011 09:07:40 -0500 Subject: [PATCH 295/380] Restored serial version of reader initialization. Serial mode is default, as the performance gains aren't so huge. -- Serial version can be re-enabled with a static boolean, if we decide to return to the serial version -- Comparison of serial and parallel reader with cached and uncached files: Initialization time: serial with 500 fully cached BAMs: 8.20 seconds Initialization time: serial with 500 uncached BAMs : 197.02 seconds Initialization time: parallel with 500 fully cached BAMs: 30.12 seconds Initialization time: parallel with 500 uncached BAMs : 75.47 seconds --- .../gatk/datasources/reads/SAMDataSource.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 e3b469b3d..d1a1cc71b 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 @@ -63,7 +63,9 @@ import java.util.concurrent.*; */ public class SAMDataSource { final private static GATKSamRecordFactory factory = new GATKSamRecordFactory(); - final private static boolean USE_PARALLEL_LOADING = true; + + /** If true, we will load SAMReaders in parallel */ + final private static boolean USE_PARALLEL_LOADING = false; /** Backing support for reads. */ protected final ReadProperties readProperties; @@ -721,7 +723,7 @@ public class SAMDataSource { logger.info("Initializing SAMRecords " + (USE_PARALLEL_LOADING ? "in parallel" : "in serial")); if ( ! USE_PARALLEL_LOADING ) { - final int tickSize = 10; + final int tickSize = 50; int nExecutedTotal = 0; long lastTick = timer.currentTime(); for(final SAMReaderID readerID: readerIDs) { @@ -733,8 +735,9 @@ public class SAMDataSource { logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, readerID.samFile)); readers.put(init.readerID,init.reader); if ( nExecutedTotal++ % tickSize == 0) { - int tickInMS = (int)((timer.currentTime() - lastTick) / 1000.0); - printReaderPerformance(nExecutedTotal, tickSize, totalNumberOfFiles, timer, tickInMS); + double tickInSec = (timer.currentTime() - lastTick) / 1000.0; + printReaderPerformance(nExecutedTotal, tickSize, totalNumberOfFiles, timer, tickInSec); + lastTick = timer.currentTime(); } } } else { @@ -776,7 +779,7 @@ public class SAMDataSource { final int nExecutedTotal = totalNumberOfFiles - pending.size(); final int nExecutedInTick = prevSize - pending.size(); - printReaderPerformance(nExecutedTotal, nExecutedInTick, totalNumberOfFiles, timer, (int)(waitTimeInMS / 1000)); + printReaderPerformance(nExecutedTotal, nExecutedInTick, totalNumberOfFiles, timer, waitTimeInMS / 1000.0); futures = pending; } } catch ( InterruptedException e ) { @@ -794,14 +797,15 @@ public class SAMDataSource { final private void printReaderPerformance(final int nExecutedTotal, final int nExecutedInTick, final int totalNumberOfFiles, - final SimpleTimer timer, final int tickDuration) { + final SimpleTimer timer, + final double tickDurationInSec) { final int pendingSize = totalNumberOfFiles - nExecutedTotal; final double totalTimeInSeconds = timer.getElapsedTime(); final double nTasksPerSecond = nExecutedTotal / (1.0*totalTimeInSeconds); final int nRemaining = pendingSize; final double estTimeToComplete = pendingSize / nTasksPerSecond; - logger.info(String.format("Init %d BAMs in last %d s, %d of %d in %.2f s / %.2f m (%.2f tasks/s). %d remaining with est. completion in %.2f s / %.2f m", - nExecutedInTick, tickDuration, + logger.info(String.format("Init %d BAMs in last %.2f s, %d of %d in %.2f s / %.2f m (%.2f tasks/s). %d remaining with est. completion in %.2f s / %.2f m", + nExecutedInTick, tickDurationInSec, nExecutedTotal, totalNumberOfFiles, totalTimeInSeconds, totalTimeInSeconds / 60, nTasksPerSecond, nRemaining, estTimeToComplete, estTimeToComplete / 60)); From 3642a73c07d9f6da85ee59c62ea3a8c9587a364e Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Fri, 16 Dec 2011 09:37:44 -0500 Subject: [PATCH 296/380] Performance improvements for dynamically merging BAMs in read walkers. This change and my previous change have dropped runtime when dynamically merging 2k BAM files from 72.6min/1M reads to 46.8sec/1M reads. Note that many of these changes are stopgaps -- the real problem is the way ReadWalkers interface with Picard, and I'll have to work with Tim&Co to produce a more maintainable patch. --- .../picard/sam/MergingSamRecordIterator.java | 247 ++++++ .../sf/picard/sam/SamFileHeaderMerger.java | 744 ++++++++++++++++++ .../gatk/datasources/reads/SAMDataSource.java | 34 +- 3 files changed, 1011 insertions(+), 14 deletions(-) create mode 100644 public/java/src/net/sf/picard/sam/MergingSamRecordIterator.java create mode 100644 public/java/src/net/sf/picard/sam/SamFileHeaderMerger.java diff --git a/public/java/src/net/sf/picard/sam/MergingSamRecordIterator.java b/public/java/src/net/sf/picard/sam/MergingSamRecordIterator.java new file mode 100644 index 000000000..4b1c7a999 --- /dev/null +++ b/public/java/src/net/sf/picard/sam/MergingSamRecordIterator.java @@ -0,0 +1,247 @@ +/* + * 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.picard.sam; + +import net.sf.picard.PicardException; + +import java.util.*; +import java.lang.reflect.Constructor; + +import net.sf.samtools.*; +import net.sf.samtools.util.CloseableIterator; + +/** + * Provides an iterator interface for merging multiple underlying iterators into a single + * iterable stream. The underlying iterators/files must all have the same sort order unless + * the requested output format is unsorted, in which case any combination is valid. + */ +public class MergingSamRecordIterator implements CloseableIterator { + private final PriorityQueue pq; + private final SamFileHeaderMerger samHeaderMerger; + private final Collection readers; + private final SAMFileHeader.SortOrder sortOrder; + private final SAMRecordComparator comparator; + + private boolean initialized = false; + private boolean iterationStarted = false; + + /** + * Constructs a new merging iterator with the same set of readers and sort order as + * provided by the header merger parameter. + * @param headerMerger The merged header and contents of readers. + * @param forcePresorted True to ensure that the iterator checks the headers of the readers for appropriate sort order. + * @deprecated replaced by (SamFileHeaderMerger, Collection, boolean) + */ + public MergingSamRecordIterator(final SamFileHeaderMerger headerMerger, final boolean forcePresorted) { + this(headerMerger, headerMerger.getReaders(), forcePresorted); + } + + /** + * Constructs a new merging iterator with the same set of readers and sort order as + * provided by the header merger parameter. + * @param headerMerger The merged header and contents of readers. + * @param assumeSorted false ensures that the iterator checks the headers of the readers for appropriate sort order. + */ + public MergingSamRecordIterator(final SamFileHeaderMerger headerMerger, Collection readers, final boolean assumeSorted) { + this.samHeaderMerger = headerMerger; + this.sortOrder = headerMerger.getMergedHeader().getSortOrder(); + this.comparator = getComparator(); + this.readers = readers; + + this.pq = new PriorityQueue(readers.size()); + + for (final SAMFileReader reader : readers) { + if (!assumeSorted && this.sortOrder != SAMFileHeader.SortOrder.unsorted && + reader.getFileHeader().getSortOrder() != this.sortOrder){ + throw new PicardException("Files are not compatible with sort order"); + } + } + } + + /** + * Add a given SAM file iterator to the merging iterator. Use this to restrict the merged iteration to a given genomic interval, + * rather than iterating over every read in the backing file or stream. + * @param reader Reader to add to the merging iterator. + * @param iterator Iterator traversing over reader contents. + */ + public void addIterator(final SAMFileReader reader, final CloseableIterator iterator) { + if(iterationStarted) + throw new PicardException("Cannot add another iterator; iteration has already begun"); + if(!samHeaderMerger.containsHeader(reader.getFileHeader())) + throw new PicardException("All iterators to be merged must be accounted for in the SAM header merger"); + final ComparableSamRecordIterator comparableIterator = new ComparableSamRecordIterator(reader,iterator,comparator); + addIfNotEmpty(comparableIterator); + initialized = true; + } + + private void startIterationIfRequired() { + if(initialized) + return; + for(SAMFileReader reader: readers) + addIterator(reader,reader.iterator()); + iterationStarted = true; + } + + /** + * Close down all open iterators. + */ + public void close() { + // Iterators not in the priority queue have already been closed; only close down the iterators that are still in the priority queue. + for(CloseableIterator iterator: pq) + iterator.close(); + } + + /** Returns true if any of the underlying iterators has more records, otherwise false. */ + public boolean hasNext() { + startIterationIfRequired(); + return !this.pq.isEmpty(); + } + + /** Returns the next record from the top most iterator during merging. */ + public SAMRecord next() { + startIterationIfRequired(); + + final ComparableSamRecordIterator iterator = this.pq.poll(); + final SAMRecord record = iterator.next(); + addIfNotEmpty(iterator); + record.setHeader(this.samHeaderMerger.getMergedHeader()); + + // Fix the read group if needs be + if (this.samHeaderMerger.hasReadGroupCollisions()) { + final String oldGroupId = (String) record.getAttribute(ReservedTagConstants.READ_GROUP_ID); + if (oldGroupId != null ) { + final String newGroupId = this.samHeaderMerger.getReadGroupId(iterator.getReader().getFileHeader(),oldGroupId); + record.setAttribute(ReservedTagConstants.READ_GROUP_ID, newGroupId); + } + } + + // Fix the program group if needs be + if (this.samHeaderMerger.hasProgramGroupCollisions()) { + final String oldGroupId = (String) record.getAttribute(ReservedTagConstants.PROGRAM_GROUP_ID); + if (oldGroupId != null ) { + final String newGroupId = this.samHeaderMerger.getProgramGroupId(iterator.getReader().getFileHeader(),oldGroupId); + record.setAttribute(ReservedTagConstants.PROGRAM_GROUP_ID, newGroupId); + } + } + + // Fix up the sequence indexes if needs be + if (this.samHeaderMerger.hasMergedSequenceDictionary()) { + if (record.getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + record.setReferenceIndex(this.samHeaderMerger.getMergedSequenceIndex(iterator.getReader().getFileHeader(),record.getReferenceIndex())); + } + + if (record.getReadPairedFlag() && record.getMateReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + record.setMateReferenceIndex(this.samHeaderMerger.getMergedSequenceIndex(iterator.getReader().getFileHeader(),record.getMateReferenceIndex())); + } + } + + return record; + } + + /** + * Adds iterator to priority queue. If the iterator has more records it is added + * otherwise it is closed and not added. + */ + private void addIfNotEmpty(final ComparableSamRecordIterator iterator) { + if (iterator.hasNext()) { + pq.offer(iterator); + } + else { + iterator.close(); + } + } + + /** Unsupported operation. */ + public void remove() { + throw new UnsupportedOperationException("MergingSAMRecorderIterator.remove()"); + } + + /** + * Get the right comparator for a given sort order (coordinate, alphabetic). In the + * case of "unsorted" it will return a comparator that gives an arbitrary but reflexive + * ordering. + */ + private SAMRecordComparator getComparator() { + // For unsorted build a fake comparator that compares based on object ID + if (this.sortOrder == SAMFileHeader.SortOrder.unsorted) { + return new SAMRecordComparator() { + public int fileOrderCompare(final SAMRecord lhs, final SAMRecord rhs) { + return System.identityHashCode(lhs) - System.identityHashCode(rhs); + } + + public int compare(final SAMRecord lhs, final SAMRecord rhs) { + return fileOrderCompare(lhs, rhs); + } + }; + } + if (samHeaderMerger.hasMergedSequenceDictionary() && sortOrder.equals(SAMFileHeader.SortOrder.coordinate)) { + return new MergedSequenceDictionaryCoordinateOrderComparator(); + } + + // Otherwise try and figure out what kind of comparator to return and build it + return this.sortOrder.getComparatorInstance(); + } + + /** Returns the merged header that the merging iterator is working from. */ + public SAMFileHeader getMergedHeader() { + return this.samHeaderMerger.getMergedHeader(); + } + + /** + * Ugh. Basically does a regular coordinate compare, but looks up the sequence indices in the merged + * sequence dictionary. I hate the fact that this extends SAMRecordCoordinateComparator, but it avoids + * more copy & paste. + */ + private class MergedSequenceDictionaryCoordinateOrderComparator extends SAMRecordCoordinateComparator { + + public int fileOrderCompare(final SAMRecord samRecord1, final SAMRecord samRecord2) { + final int referenceIndex1 = getReferenceIndex(samRecord1); + final int referenceIndex2 = getReferenceIndex(samRecord2); + if (referenceIndex1 != referenceIndex2) { + if (referenceIndex1 == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + return 1; + } else if (referenceIndex2 == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + return -1; + } else { + return referenceIndex1 - referenceIndex2; + } + } + if (referenceIndex1 == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + // Both are unmapped. + return 0; + } + return samRecord1.getAlignmentStart() - samRecord2.getAlignmentStart(); + } + + private int getReferenceIndex(final SAMRecord samRecord) { + if (samRecord.getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + return samHeaderMerger.getMergedSequenceIndex(samRecord.getHeader(), samRecord.getReferenceIndex()); + } + if (samRecord.getMateReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + return samHeaderMerger.getMergedSequenceIndex(samRecord.getHeader(), samRecord.getMateReferenceIndex()); + } + return SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX; + } + } +} diff --git a/public/java/src/net/sf/picard/sam/SamFileHeaderMerger.java b/public/java/src/net/sf/picard/sam/SamFileHeaderMerger.java new file mode 100644 index 000000000..f78cd81da --- /dev/null +++ b/public/java/src/net/sf/picard/sam/SamFileHeaderMerger.java @@ -0,0 +1,744 @@ +/* + * 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.picard.sam; + +import java.util.*; + +import net.sf.picard.PicardException; +import net.sf.samtools.AbstractSAMHeaderRecord; +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMFileReader; +import net.sf.samtools.SAMProgramRecord; +import net.sf.samtools.SAMReadGroupRecord; +import net.sf.samtools.SAMSequenceDictionary; +import net.sf.samtools.SAMSequenceRecord; +import net.sf.samtools.util.SequenceUtil; + +/** + * Merges SAMFileHeaders that have the same sequences into a single merged header + * object while providing read group translation for cases where read groups + * clash across input headers. + */ +public class SamFileHeaderMerger { + //Super Header to construct + private final SAMFileHeader mergedHeader; + private Collection readers; + private final Collection headers; + + //Translation of old group ids to new group ids + private final Map> samReadGroupIdTranslation = + new IdentityHashMap>(); + + //the read groups from different files use the same group ids + private boolean hasReadGroupCollisions = false; + + //the program records from different files use the same program record ids + private boolean hasProgramGroupCollisions = false; + + //Translation of old program group ids to new program group ids + private Map> samProgramGroupIdTranslation = + new IdentityHashMap>(); + + private boolean hasMergedSequenceDictionary = false; + + // Translation of old sequence dictionary ids to new dictionary ids + // This is an IdentityHashMap because it can be quite expensive to compute the hashCode for + // large SAMFileHeaders. It is possible that two input files will have identical headers so that + // the regular HashMap would fold them together, but the value stored in each of the two + // Map entries will be the same, so it should not hurt anything. + private final Map> samSeqDictionaryIdTranslationViaHeader = + new IdentityHashMap>(); + + //HeaderRecordFactory that creates SAMReadGroupRecord instances. + private static final HeaderRecordFactory READ_GROUP_RECORD_FACTORY = new HeaderRecordFactory() { + public SAMReadGroupRecord createRecord(String id, SAMReadGroupRecord srcReadGroupRecord) { + return new SAMReadGroupRecord(id, srcReadGroupRecord); + } + }; + + //HeaderRecordFactory that creates SAMProgramRecord instances. + private static final HeaderRecordFactory PROGRAM_RECORD_FACTORY = new HeaderRecordFactory() { + public SAMProgramRecord createRecord(String id, SAMProgramRecord srcProgramRecord) { + return new SAMProgramRecord(id, srcProgramRecord); + } + }; + + //comparator used to sort lists of program group and read group records + private static final Comparator RECORD_ID_COMPARATOR = new Comparator() { + public int compare(AbstractSAMHeaderRecord o1, AbstractSAMHeaderRecord o2) { + return o1.getId().compareTo(o2.getId()); + } + }; + + /** + * Create SAMFileHeader with additional information. Required that sequence dictionaries agree. + * + * @param readers sam file readers to combine + * @param sortOrder sort order new header should have + * @deprecated replaced by SamFileHeaderMerger(Collection, SAMFileHeader.SortOrder, boolean) + */ + public SamFileHeaderMerger(final Collection readers, final SAMFileHeader.SortOrder sortOrder) { + this(readers, sortOrder, false); + } + + /** + * Create SAMFileHeader with additional information. + * + * @param readers sam file readers to combine + * @param sortOrder sort order new header should have + * @param mergeDictionaries If true, merge sequence dictionaries in new header. If false, require that + * all input sequence dictionaries be identical. + * @deprecated replaced by SamFileHeaderMerger(Collection, SAMFileHeader.SortOrder, boolean) + */ + public SamFileHeaderMerger(final Collection readers, final SAMFileHeader.SortOrder sortOrder, final boolean mergeDictionaries) { + this(sortOrder, getHeadersFromReaders(readers), mergeDictionaries); + this.readers = readers; + } + + /** + * Create SAMFileHeader with additional information.. This is the preferred constructor. + * + * @param sortOrder sort order new header should have + * @param headers sam file headers to combine + * @param mergeDictionaries If true, merge sequence dictionaries in new header. If false, require that + * all input sequence dictionaries be identical. + */ + public SamFileHeaderMerger(final SAMFileHeader.SortOrder sortOrder, final Collection headers, final boolean mergeDictionaries) { + this.headers = headers; + this.mergedHeader = new SAMFileHeader(); + + SAMSequenceDictionary sequenceDictionary; + try { + sequenceDictionary = getSequenceDictionary(headers); + this.hasMergedSequenceDictionary = false; + } + catch (SequenceUtil.SequenceListsDifferException pe) { + if (mergeDictionaries) { + sequenceDictionary = mergeSequenceDictionaries(headers); + this.hasMergedSequenceDictionary = true; + } + else { + throw pe; + } + } + + this.mergedHeader.setSequenceDictionary(sequenceDictionary); + + // Set program that creates input alignments + for (final SAMProgramRecord program : mergeProgramGroups(headers)) { + this.mergedHeader.addProgramRecord(program); + } + + // Set read groups for merged header + final List readGroups = mergeReadGroups(headers); + this.mergedHeader.setReadGroups(readGroups); + this.mergedHeader.setGroupOrder(SAMFileHeader.GroupOrder.none); + + this.mergedHeader.setSortOrder(sortOrder); + + for (final SAMFileHeader header : headers) { + for (final String comment : header.getComments()) { + this.mergedHeader.addComment(comment); + } + } + } + + // Utilility method to make use with old constructor + private static List getHeadersFromReaders(Collection readers) { + List headers = new ArrayList(readers.size()); + for (SAMFileReader reader : readers) { + headers.add(reader.getFileHeader()); + } + return headers; + } + + + /** + * Checks to see if there are clashes where different readers are using the same read + * group IDs. If yes, then those IDs that collided are remapped. + * + * @param headers headers to combine + * @return new list of read groups constructed from all the readers + */ + private List mergeReadGroups(final Collection headers) { + //prepare args for mergeHeaderRecords(..) call + final HashSet idsThatAreAlreadyTaken = new HashSet(); + + final List> readGroupsToProcess = new LinkedList>(); + for (final SAMFileHeader header : headers) { + for (final SAMReadGroupRecord readGroup : header.getReadGroups()) { + //verify that there are no existing id collisions in this input file + if(!idsThatAreAlreadyTaken.add(readGroup.getId())) + throw new PicardException("Input file: " + header + " contains more than one RG with the same id (" + readGroup.getId() + ")"); + + readGroupsToProcess.add(new HeaderRecordAndFileHeader(readGroup, header)); + } + idsThatAreAlreadyTaken.clear(); + } + + final List result = new LinkedList(); + + hasReadGroupCollisions = mergeHeaderRecords(readGroupsToProcess, READ_GROUP_RECORD_FACTORY, idsThatAreAlreadyTaken, samReadGroupIdTranslation, result); + + //sort the result list by record id + Collections.sort(result, RECORD_ID_COMPARATOR); + + return result; + } + + + /** + * Checks to see if there are clashes where different readers are using the same program + * group IDs. If yes, then those IDs that collided are remapped. + * + * @param headers headers to combine + * @return new list of program groups constructed from all the readers + */ + private List mergeProgramGroups(final Collection headers) { + + final List overallResult = new LinkedList(); + + //this Set will accumulate all SAMProgramRecord ids that have been encountered so far. + final HashSet idsThatAreAlreadyTaken = new HashSet(); + + //need to process all program groups + List> programGroupsLeftToProcess = new LinkedList>(); + for (final SAMFileHeader header : headers) { + for (final SAMProgramRecord programGroup : header.getProgramRecords()) { + //verify that there are no existing id collisions in this input file + if(!idsThatAreAlreadyTaken.add(programGroup.getId())) + throw new PicardException("Input file: " + header + " contains more than one PG with the same id (" + programGroup.getId() + ")"); + + programGroupsLeftToProcess.add(new HeaderRecordAndFileHeader(programGroup, header)); + } + idsThatAreAlreadyTaken.clear(); + } + + //A program group header (lets say ID=2 PN=B PP=1) may have a PP (previous program) attribute which chains it to + //another program group header (lets say ID=1 PN=A) to indicate that the given file was + //processed by program A followed by program B. These PP attributes potentially + //connect headers into one or more tree structures. Merging is done by + //first merging all headers that don't have PP attributes (eg. tree roots), + //then updating and merging all headers whose PPs point to the tree-root headers, + //and so on until all program group headers are processed. + + //currentProgramGroups is the list of records to merge next. Start by merging the programGroups that don't have a PP attribute (eg. the tree roots). + List< HeaderRecordAndFileHeader > currentProgramGroups = new LinkedList>(); + for(final Iterator> programGroupsLeftToProcessIterator = programGroupsLeftToProcess.iterator(); programGroupsLeftToProcessIterator.hasNext(); ) { + final HeaderRecordAndFileHeader pair = programGroupsLeftToProcessIterator.next(); + if(pair.getHeaderRecord().getAttribute(SAMProgramRecord.PREVIOUS_PROGRAM_GROUP_ID_TAG) == null) { + programGroupsLeftToProcessIterator.remove(); + currentProgramGroups.add(pair); + } + } + + //merge currentProgramGroups + while(!currentProgramGroups.isEmpty()) + { + final List currentResult = new LinkedList(); + + hasProgramGroupCollisions |= mergeHeaderRecords(currentProgramGroups, PROGRAM_RECORD_FACTORY, idsThatAreAlreadyTaken, samProgramGroupIdTranslation, currentResult); + + //add currentResults to overallResults + overallResult.addAll(currentResult); + + //apply the newly-computed id translations to currentProgramGroups and programGroupsLeftToProcess + currentProgramGroups = translateIds(currentProgramGroups, samProgramGroupIdTranslation, false); + programGroupsLeftToProcess = translateIds(programGroupsLeftToProcess, samProgramGroupIdTranslation, true); + + //find all records in programGroupsLeftToProcess whose ppId points to a record that was just processed (eg. a record that's in currentProgramGroups), + //and move them to the list of programGroupsToProcessNext. + LinkedList> programGroupsToProcessNext = new LinkedList>(); + for(final Iterator> programGroupsLeftToProcessIterator = programGroupsLeftToProcess.iterator(); programGroupsLeftToProcessIterator.hasNext(); ) { + final HeaderRecordAndFileHeader pairLeftToProcess = programGroupsLeftToProcessIterator.next(); + final Object ppIdOfRecordLeftToProcess = pairLeftToProcess.getHeaderRecord().getAttribute(SAMProgramRecord.PREVIOUS_PROGRAM_GROUP_ID_TAG); + //find what currentProgramGroups this ppId points to (NOTE: they have to come from the same file) + for(final HeaderRecordAndFileHeader justProcessedPair : currentProgramGroups) { + String idJustProcessed = justProcessedPair.getHeaderRecord().getId(); + if(pairLeftToProcess.getFileHeader() == justProcessedPair.getFileHeader() && ppIdOfRecordLeftToProcess.equals(idJustProcessed)) { + programGroupsLeftToProcessIterator.remove(); + programGroupsToProcessNext.add(pairLeftToProcess); + break; + } + } + } + + currentProgramGroups = programGroupsToProcessNext; + } + + //verify that all records were processed + if(!programGroupsLeftToProcess.isEmpty()) { + StringBuffer errorMsg = new StringBuffer(programGroupsLeftToProcess.size() + " program groups weren't processed. Do their PP ids point to existing PGs? \n"); + for( final HeaderRecordAndFileHeader pair : programGroupsLeftToProcess ) { + SAMProgramRecord record = pair.getHeaderRecord(); + errorMsg.append("@PG ID:"+record.getProgramGroupId()+" PN:"+record.getProgramName()+" PP:"+record.getPreviousProgramGroupId() +"\n"); + } + throw new PicardException(errorMsg.toString()); + } + + //sort the result list by record id + Collections.sort(overallResult, RECORD_ID_COMPARATOR); + + return overallResult; + } + + + /** + * Utility method that takes a list of program groups and remaps all their + * ids (including ppIds if requested) using the given idTranslationTable. + * + * NOTE: when remapping, this method creates new SAMProgramRecords and + * doesn't mutate any records in the programGroups list. + * + * @param programGroups The program groups to translate. + * @param idTranslationTable The translation table. + * @param translatePpIds Whether ppIds should be translated as well. + * + * @return The list of translated records. + */ + private List> translateIds( + List> programGroups, + Map> idTranslationTable, + boolean translatePpIds) { + + //go through programGroups and translate any IDs and PPs based on the idTranslationTable. + List> result = new LinkedList>(); + for(final HeaderRecordAndFileHeader pair : programGroups ) { + final SAMProgramRecord record = pair.getHeaderRecord(); + final String id = record.getProgramGroupId(); + final String ppId = (String) record.getAttribute(SAMProgramRecord.PREVIOUS_PROGRAM_GROUP_ID_TAG); + + final SAMFileHeader header = pair.getFileHeader(); + final Map translations = idTranslationTable.get(header); + + //see if one or both ids need to be translated + SAMProgramRecord translatedRecord = null; + if(translations != null) + { + String translatedId = translations.get( id ); + String translatedPpId = translatePpIds ? translations.get( ppId ) : null; + + boolean needToTranslateId = translatedId != null && !translatedId.equals(id); + boolean needToTranslatePpId = translatedPpId != null && !translatedPpId.equals(ppId); + + if(needToTranslateId && needToTranslatePpId) { + translatedRecord = new SAMProgramRecord(translatedId, record); + translatedRecord.setAttribute(SAMProgramRecord.PREVIOUS_PROGRAM_GROUP_ID_TAG, translatedPpId); + } else if(needToTranslateId) { + translatedRecord = new SAMProgramRecord(translatedId, record); + } else if(needToTranslatePpId) { + translatedRecord = new SAMProgramRecord(id, record); + translatedRecord.setAttribute(SAMProgramRecord.PREVIOUS_PROGRAM_GROUP_ID_TAG, translatedPpId); + } + } + + if(translatedRecord != null) { + result.add(new HeaderRecordAndFileHeader(translatedRecord, header)); + } else { + result.add(pair); //keep the original record + } + } + + return result; + } + + + /** + * Utility method for merging a List of AbstractSAMHeaderRecords. If it finds + * records that have identical ids and attributes, it will collapse them + * into one record. If it finds records that have identical ids but + * non-identical attributes, this is treated as a collision. When collision happens, + * the records' ids are remapped, and an old-id to new-id mapping is added to the idTranslationTable. + * + * NOTE: Non-collided records also get recorded in the idTranslationTable as + * old-id to old-id. This way, an idTranslationTable lookup should never return null. + * + * @param headerRecords The header records to merge. + * @param headerRecordFactory Constructs a specific subclass of AbstractSAMHeaderRecord. + * @param idsThatAreAlreadyTaken If the id of a headerRecord matches an id in this set, it will be treated as a collision, and the headRecord's id will be remapped. + * @param idTranslationTable When records collide, their ids are remapped, and an old-id to new-id + * mapping is added to the idTranslationTable. Non-collided records also get recorded in the idTranslationTable as + * old-id to old-id. This way, an idTranslationTable lookup should never return null. + * + * @param result The list of merged header records. + * + * @return True if there were collisions. + */ + private boolean mergeHeaderRecords(final List> headerRecords, HeaderRecordFactory headerRecordFactory, + final HashSet idsThatAreAlreadyTaken, Map> idTranslationTable, List result) { + + //The outer Map bins the header records by their ids. The nested Map further collapses + //header records which, in addition to having the same id, also have identical attributes. + //In other words, each key in the nested map represents one or more + //header records which have both identical ids and identical attributes. The List of + //SAMFileHeaders keeps track of which readers these header record(s) came from. + final Map>> idToRecord = + new HashMap>>(); + + //Populate the idToRecord and seenIds data structures + for (final HeaderRecordAndFileHeader pair : headerRecords) { + final RecordType record = pair.getHeaderRecord(); + final SAMFileHeader header = pair.getFileHeader(); + final String recordId = record.getId(); + Map> recordsWithSameId = idToRecord.get(recordId); + if(recordsWithSameId == null) { + recordsWithSameId = new LinkedHashMap>(); + idToRecord.put(recordId, recordsWithSameId); + } + + List fileHeaders = recordsWithSameId.get(record); + if(fileHeaders == null) { + fileHeaders = new LinkedList(); + recordsWithSameId.put(record, fileHeaders); + } + + fileHeaders.add(header); + } + + //Resolve any collisions between header records by remapping their ids. + boolean hasCollisions = false; + for (final Map.Entry>> entry : idToRecord.entrySet() ) + { + final String recordId = entry.getKey(); + final Map> recordsWithSameId = entry.getValue(); + + + for( Map.Entry> recordWithUniqueAttr : recordsWithSameId.entrySet()) { + final RecordType record = recordWithUniqueAttr.getKey(); + final List fileHeaders = recordWithUniqueAttr.getValue(); + + String newId; + if(!idsThatAreAlreadyTaken.contains(recordId)) { + //don't remap 1st record. If there are more records + //with this id, they will be remapped in the 'else'. + newId = recordId; + idsThatAreAlreadyTaken.add(recordId); + } else { + //there is more than one record with this id. + hasCollisions = true; + + //find a unique newId for this record + int idx=1; + while(idsThatAreAlreadyTaken.contains(newId = recordId + "." + Integer.toString(idx++))) + ; + + idsThatAreAlreadyTaken.add( newId ); + } + + for(SAMFileHeader fileHeader : fileHeaders) { + Map readerTranslationTable = idTranslationTable.get(fileHeader); + if(readerTranslationTable == null) { + readerTranslationTable = new HashMap(); + idTranslationTable.put(fileHeader, readerTranslationTable); + } + readerTranslationTable.put(recordId, newId); + } + + result.add( headerRecordFactory.createRecord(newId, record) ); + } + } + + return hasCollisions; + } + + + /** + * Get the sequences off the SAMFileHeader. Throws runtime exception if the sequence + * are different from one another. + * + * @param headers headers to pull sequences from + * @return sequences from files. Each file should have the same sequence + */ + private SAMSequenceDictionary getSequenceDictionary(final Collection headers) { + SAMSequenceDictionary sequences = null; + for (final SAMFileHeader header : headers) { + + if (sequences == null) { + sequences = header.getSequenceDictionary(); + } + else { + final SAMSequenceDictionary currentSequences = header.getSequenceDictionary(); + SequenceUtil.assertSequenceDictionariesEqual(sequences, currentSequences); + } + } + + return sequences; + } + + /** + * Get the sequences from the SAMFileHeader, and merge the resulting sequence dictionaries. + * + * @param headers headers to pull sequences from + * @return sequences from files. Each file should have the same sequence + */ + private SAMSequenceDictionary mergeSequenceDictionaries(final Collection headers) { + SAMSequenceDictionary sequences = new SAMSequenceDictionary(); + for (final SAMFileHeader header : headers) { + final SAMSequenceDictionary currentSequences = header.getSequenceDictionary(); + sequences = mergeSequences(sequences, currentSequences); + } + // second pass, make a map of the original seqeunce id -> new sequence id + createSequenceMapping(headers, sequences); + return sequences; + } + + /** + * They've asked to merge the sequence headers. What we support right now is finding the sequence name superset. + * + * @param mergeIntoDict the result of merging so far. All SAMSequenceRecords in here have been cloned from the originals. + * @param mergeFromDict A new sequence dictionary to merge into mergeIntoDict. + * @return A new sequence dictionary that resulting from merging the two inputs. + */ + private SAMSequenceDictionary mergeSequences(SAMSequenceDictionary mergeIntoDict, SAMSequenceDictionary mergeFromDict) { + + // a place to hold the sequences that we haven't found a home for, in the order the appear in mergeFromDict. + LinkedList holder = new LinkedList(); + + // Return value will be created from this. + LinkedList resultingDict = new LinkedList(); + for (final SAMSequenceRecord sequenceRecord : mergeIntoDict.getSequences()) { + resultingDict.add(sequenceRecord); + } + + // Index into resultingDict of previous SAMSequenceRecord from mergeFromDict that already existed in mergeIntoDict. + int prevloc = -1; + // Previous SAMSequenceRecord from mergeFromDict that already existed in mergeIntoDict. + SAMSequenceRecord previouslyMerged = null; + + for (SAMSequenceRecord sequenceRecord : mergeFromDict.getSequences()) { + // Does it already exist in resultingDict? + int loc = getIndexOfSequenceName(resultingDict, sequenceRecord.getSequenceName()); + if (loc == -1) { + // If doesn't already exist in resultingDict, save it an decide where to insert it later. + holder.add(sequenceRecord.clone()); + } else if (prevloc > loc) { + // If sequenceRecord already exists in resultingDict, but prior to the previous one + // from mergeIntoDict that already existed, cannot merge. + throw new PicardException("Cannot merge sequence dictionaries because sequence " + + sequenceRecord.getSequenceName() + " and " + previouslyMerged.getSequenceName() + + " are in different orders in two input sequence dictionaries."); + } else { + // Since sequenceRecord already exists in resultingDict, don't need to add it. + // Add in all the sequences prior to it that have been held in holder. + resultingDict.addAll(loc, holder); + // Remember the index of sequenceRecord so can check for merge imcompatibility. + prevloc = loc + holder.size(); + previouslyMerged = sequenceRecord; + holder.clear(); + } + } + // Append anything left in holder. + if (holder.size() != 0) { + resultingDict.addAll(holder); + } + return new SAMSequenceDictionary(resultingDict); + } + + /** + * Find sequence in list. + * @param list List to search for the sequence name. + * @param sequenceName Name to search for. + * @return Index of SAMSequenceRecord with the given name in list, or -1 if not found. + */ + private static int getIndexOfSequenceName(final List list, final String sequenceName) { + for (int i = 0; i < list.size(); ++i) { + if (list.get(i).getSequenceName().equals(sequenceName)) { + return i; + } + } + return -1; + } + + /** + * create the sequence mapping. This map is used to convert the unmerged header sequence ID's to the merged + * list of sequence id's. + * @param headers the collections of headers. + * @param masterDictionary the superset dictionary we've created. + */ + private void createSequenceMapping(final Collection headers, SAMSequenceDictionary masterDictionary) { + LinkedList resultingDictStr = new LinkedList(); + for (SAMSequenceRecord r : masterDictionary.getSequences()) { + resultingDictStr.add(r.getSequenceName()); + } + for (final SAMFileHeader header : headers) { + Map seqMap = new HashMap(); + SAMSequenceDictionary dict = header.getSequenceDictionary(); + for (SAMSequenceRecord rec : dict.getSequences()) { + seqMap.put(rec.getSequenceIndex(), resultingDictStr.indexOf(rec.getSequenceName())); + } + this.samSeqDictionaryIdTranslationViaHeader.put(header, seqMap); + } + } + + + + /** + * Returns the read group id that should be used for the input read and RG id. + * + * @deprecated replaced by getReadGroupId(SAMFileHeader, String) + * */ + public String getReadGroupId(final SAMFileReader reader, final String originalReadGroupId) { + return getReadGroupId(reader.getFileHeader(), originalReadGroupId); + } + + /** Returns the read group id that should be used for the input read and RG id. */ + public String getReadGroupId(final SAMFileHeader header, final String originalReadGroupId) { + return this.samReadGroupIdTranslation.get(header).get(originalReadGroupId); + } + + /** + * @param reader one of the input files + * @param originalProgramGroupId a program group ID from the above input file + * @return new ID from the merged list of program groups in the output file + * @deprecated replaced by getProgramGroupId(SAMFileHeader, String) + */ + public String getProgramGroupId(final SAMFileReader reader, final String originalProgramGroupId) { + return getProgramGroupId(reader.getFileHeader(), originalProgramGroupId); + } + + /** + * @param header one of the input headers + * @param originalProgramGroupId a program group ID from the above input file + * @return new ID from the merged list of program groups in the output file + */ + public String getProgramGroupId(final SAMFileHeader header, final String originalProgramGroupId) { + return this.samProgramGroupIdTranslation.get(header).get(originalProgramGroupId); + } + + /** Returns true if there are read group duplicates within the merged headers. */ + public boolean hasReadGroupCollisions() { + return this.hasReadGroupCollisions; + } + + /** Returns true if there are program group duplicates within the merged headers. */ + public boolean hasProgramGroupCollisions() { + return hasProgramGroupCollisions; + } + + /** @return if we've merged the sequence dictionaries, return true */ + public boolean hasMergedSequenceDictionary() { + return hasMergedSequenceDictionary; + } + + /** Returns the merged header that should be written to any output merged file. */ + public SAMFileHeader getMergedHeader() { + return this.mergedHeader; + } + + /** Returns the collection of readers that this header merger is working with. May return null. + * @deprecated replaced by getHeaders() + */ + public Collection getReaders() { + return this.readers; + } + + /** Returns the collection of readers that this header merger is working with. + */ + public Collection getHeaders() { + return this.headers; + } + + /** + * Tells whether this header merger contains a given SAM file header. Note that header presence + * is confirmed / blocked by == equality, rather than actually testing SAMFileHeader.equals(), for + * reasons of performance. + * @param header header to check for. + * @return True if the header exists in this HeaderMerger. False otherwise. + */ + boolean containsHeader(SAMFileHeader header) { + for(SAMFileHeader headerMergerHeader: headers) { + if(headerMergerHeader == header) + return true; + } + return false; + } + + /** + * returns the new mapping for a specified reader, given it's old sequence index + * @param reader the reader + * @param oldReferenceSequenceIndex the old sequence (also called reference) index + * @return the new index value + * @deprecated replaced by getMergedSequenceIndex(SAMFileHeader, Integer) + */ + public Integer getMergedSequenceIndex(SAMFileReader reader, Integer oldReferenceSequenceIndex) { + return this.getMergedSequenceIndex(reader.getFileHeader(), oldReferenceSequenceIndex); + } + + /** + * Another mechanism for getting the new sequence index, for situations in which the reader is not available. + * Note that if the SAMRecord has already had its header replaced with the merged header, this won't work. + * @param header The original header for the input record in question. + * @param oldReferenceSequenceIndex The original sequence index. + * @return the new index value that is compatible with the merged sequence index. + */ + public Integer getMergedSequenceIndex(final SAMFileHeader header, Integer oldReferenceSequenceIndex) { + final Map mapping = this.samSeqDictionaryIdTranslationViaHeader.get(header); + if (mapping == null) { + throw new PicardException("No sequence dictionary mapping available for header: " + header); + } + + final Integer newIndex = mapping.get(oldReferenceSequenceIndex); + if (newIndex == null) { + throw new PicardException("No mapping for reference index " + oldReferenceSequenceIndex + " from header: " + header); + } + + return newIndex; + } + + + /** + * Implementations of this interface are used by mergeHeaderRecords(..) to instantiate + * specific subclasses of AbstractSAMHeaderRecord. + */ + private static interface HeaderRecordFactory { + + /** + * Constructs a new instance of RecordType. + * @param id The id of the new record. + * @param srcRecord Except for the id, the new record will be a copy of this source record. + */ + public RecordType createRecord(final String id, RecordType srcRecord); + } + + /** + * Struct that groups together a subclass of AbstractSAMHeaderRecord with the + * SAMFileHeader that it came from. + */ + private static class HeaderRecordAndFileHeader { + private RecordType headerRecord; + private SAMFileHeader samFileHeader; + + public HeaderRecordAndFileHeader(RecordType headerRecord, SAMFileHeader samFileHeader) { + this.headerRecord = headerRecord; + this.samFileHeader = samFileHeader; + } + + public RecordType getHeaderRecord() { + return headerRecord; + } + public SAMFileHeader getFileHeader() { + return samFileHeader; + } + } +} 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 d5d8ab2ef..2729941bc 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 @@ -259,6 +259,11 @@ public class SAMDataSource { validationStringency = strictness; if(readBufferSize != null) ReadShard.setReadBufferSize(readBufferSize); + else { + // Choose a sensible default for the read buffer size. For the moment, we're picking 1000 reads per BAM per shard (which effectively + // will mean per-thread once ReadWalkers are parallelized) with a max cap of 250K reads in memory at once. + ReadShard.setReadBufferSize(Math.min(1000*samFiles.size(),250000)); + } resourcePool = new SAMResourcePool(Integer.MAX_VALUE); SAMReaders readers = resourcePool.getAvailableReaders(); @@ -479,10 +484,13 @@ public class SAMDataSource { // Cache the most recently viewed read so that we can check whether we've reached the end of a pair. SAMRecord read = null; + Map positionUpdates = new IdentityHashMap(); + CloseableIterator iterator = getIterator(readers,shard,sortOrder == SAMFileHeader.SortOrder.coordinate); while(!shard.isBufferFull() && iterator.hasNext()) { read = iterator.next(); - addReadToBufferingShard(shard,getReaderID(readers,read),read); + shard.addRead(read); + noteFilePositionUpdate(positionUpdates,read); } // If the reads are sorted in queryname order, ensure that all reads @@ -492,11 +500,21 @@ public class SAMDataSource { SAMRecord nextRead = iterator.next(); if(read == null || !read.getReadName().equals(nextRead.getReadName())) break; - addReadToBufferingShard(shard,getReaderID(readers,nextRead),nextRead); + shard.addRead(nextRead); + noteFilePositionUpdate(positionUpdates,nextRead); } } iterator.close(); + + // Make the updates specified by the reader. + for(Map.Entry positionUpdate: positionUpdates.entrySet()) + readerPositions.put(readers.getReaderID(positionUpdate.getKey()),positionUpdate.getValue()); + } + + private void noteFilePositionUpdate(Map positionMapping, SAMRecord read) { + GATKBAMFileSpan endChunk = new GATKBAMFileSpan(read.getFileSource().getFilePointer().getContentsFollowing()); + positionMapping.put(read.getFileSource().getReader(),endChunk); } public StingSAMIterator seek(Shard shard) { @@ -574,18 +592,6 @@ public class SAMDataSource { readProperties.defaultBaseQualities()); } - /** - * Adds this read to the given shard. - * @param shard The shard to which to add the read. - * @param id The id of the given reader. - * @param read The read to add to the shard. - */ - private void addReadToBufferingShard(Shard shard,SAMReaderID id,SAMRecord read) { - GATKBAMFileSpan endChunk = new GATKBAMFileSpan(read.getFileSource().getFilePointer().getContentsFollowing()); - shard.addRead(read); - readerPositions.put(id,endChunk); - } - /** * Filter reads based on user-specified criteria. * From 5aa79dacfc63895ab7ab96d1b17ea43b35230f37 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Fri, 16 Dec 2011 10:29:20 -0500 Subject: [PATCH 297/380] Changing hidden optimization argument to advanced. --- .../gatk/walkers/variantrecalibration/VariantRecalibrator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java index 339b8e0e6..7cc5b1625 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VariantRecalibrator.java @@ -175,7 +175,7 @@ public class VariantRecalibrator extends RodWalker Date: Fri, 16 Dec 2011 11:45:40 -0500 Subject: [PATCH 298/380] Minor bug fix for printing in SAMDataSource --- .../sting/gatk/datasources/reads/SAMDataSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a021aedd0..8d0df407e 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 @@ -740,7 +740,7 @@ public class SAMDataSource { logger.debug(String.format("Processing file (%d of %d) %s...", readerNumber++, totalNumberOfFiles, readerID.samFile)); readers.put(init.readerID,init.reader); - if ( nExecutedTotal++ % tickSize == 0) { + if ( ++nExecutedTotal % tickSize == 0) { double tickInSec = (timer.currentTime() - lastTick) / 1000.0; printReaderPerformance(nExecutedTotal, tickSize, totalNumberOfFiles, timer, tickInSec); lastTick = timer.currentTime(); From d6d2f49c889d1b8323907e1d0aeaa0a6f430e48c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 16 Dec 2011 16:09:37 -0500 Subject: [PATCH 300/380] Don't print log if there are no BAMs --- .../sting/gatk/datasources/reads/SAMDataSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8d0df407e..75a851d65 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 @@ -727,7 +727,7 @@ public class SAMDataSource { int readerNumber = 1; final SimpleTimer timer = new SimpleTimer().start(); - logger.info("Initializing SAMRecords " + (USE_PARALLEL_LOADING ? "in parallel" : "in serial")); + if ( totalNumberOfFiles > 0 ) logger.info("Initializing SAMRecords " + (USE_PARALLEL_LOADING ? "in parallel" : "in serial")); if ( ! USE_PARALLEL_LOADING ) { final int tickSize = 50; int nExecutedTotal = 0; @@ -797,7 +797,7 @@ public class SAMDataSource { executor.shutdown(); } - logger.info(String.format("Done initializing BAM readers: total time %.2f", timer.getElapsedTime())); + if ( totalNumberOfFiles > 0 ) logger.info(String.format("Done initializing BAM readers: total time %.2f", timer.getElapsedTime())); } final private void printReaderPerformance(final int nExecutedTotal, From b6067be9526db68c36d497f1f4647916ce04bb2f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 16 Dec 2011 16:10:18 -0500 Subject: [PATCH 301/380] Support for selecting only variants with specific IDs from a file in SelectVariants -- Cleaned up unused variables as well --- .../walkers/variantutils/SelectVariants.java | 87 ++++++++++--------- 1 file changed, 47 insertions(+), 40 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 d20fb54aa..0af351750 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 @@ -32,6 +32,7 @@ import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.utils.MendelianViolation; +import org.broadinstitute.sting.utils.text.XReadLines; import org.broadinstitute.sting.utils.variantcontext.*; import org.broadinstitute.sting.commandline.Argument; import org.broadinstitute.sting.commandline.Output; @@ -42,6 +43,7 @@ import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.SampleUtils; import java.io.File; +import java.io.FileNotFoundException; import java.io.PrintStream; import java.util.*; @@ -140,7 +142,7 @@ import java.util.*; * -R ref.fasta \ * -T SelectVariants \ * --variant input.vcf \ - * -family NA12891+NA12892=NA12878 \ + * -bed family.ped \ * -mvq 50 \ * -o violations.vcf * @@ -250,16 +252,6 @@ public class SelectVariants extends RodWalker implements TreeR @Argument(fullName="keepOriginalAC", shortName="keepOriginalAC", doc="Don't update the AC, AF, or AN values in the INFO field after selecting", required=false) private boolean KEEP_ORIGINAL_CHR_COUNTS = false; - @Hidden - @Argument(fullName="family_structure_file", shortName="familyFile", doc="use -family unless you know what you're doing", required=false) - private File FAMILY_STRUCTURE_FILE = null; - - /** - * String formatted as dad+mom=child where these parameters determine which sample names are examined. - */ - @Argument(fullName="family_structure", shortName="family", doc="string formatted as dad+mom=child where these parameters determine which sample names are examined", required=false) - private String FAMILY_STRUCTURE = ""; - /** * This activates the mendelian violation module that will select all variants that correspond to a mendelian violation following the rules given by the family structure. */ @@ -286,13 +278,21 @@ public class SelectVariants extends RodWalker implements TreeR private double fractionGenotypes = 0; /** - * This argument select particular kinds of variants out of a list. If left empty, there is no type selection and all variant types are considered for other selection criteria. + * This argument select particular kinds of variants out of a list. If left empty, there is no type selection and all variant types are considered for other selection criteria. * When specified one or more times, a particular type of variant is selected. * - */ + */ @Argument(fullName="selectTypeToInclude", shortName="selectType", doc="Select only a certain type of variants from the input file. Valid types are INDEL, SNP, MIXED, MNP, SYMBOLIC, NO_VARIATION. Can be specified multiple times", required=false) private List TYPES_TO_INCLUDE = new ArrayList(); + /** + * If provided, we will only include variants whose ID field is present in this list of ids. The matching + * is exact string matching. The file format is just one ID per line + * + */ + @Argument(fullName="keepIDs", shortName="IDs", doc="Only emit sites whose ID is found in this file (one ID per line)", required=false) + private File rsIDFile = null; + @Hidden @Argument(fullName="outMVFile", shortName="outMVFile", doc="", required=false) @@ -313,9 +313,9 @@ public class SelectVariants extends RodWalker implements TreeR } public enum NumberAlleleRestriction { - ALL, - BIALLELIC, - MULTIALLELIC + ALL, + BIALLELIC, + MULTIALLELIC } private ArrayList selectedTypes = new ArrayList(); @@ -339,17 +339,13 @@ public class SelectVariants extends RodWalker implements TreeR private int positionToAdd = 0; private RandomVariantStructure [] variantArray; - - /* Variables used for random selection with AF boosting */ - private ArrayList afBreakpoints = null; - private ArrayList afBoosts = null; - double bkDelta = 0.0; - private PrintStream outMVFileStream = null; - //Random number generator for the genotypes to remove + //Random number generator for the genotypes to remove private Random randomGenotypes = new Random(); + private Set IDsToKeep = null; + /** * Set up the VCF writer, the sample expressions and regexs, and the JEXL matcher */ @@ -437,7 +433,18 @@ public class SelectVariants extends RodWalker implements TreeR if (SELECT_RANDOM_FRACTION) logger.info("Selecting approximately " + 100.0*fractionRandom + "% of the variants at random from the variant track"); - + /** load in the IDs file to a hashset for matching */ + if ( rsIDFile != null ) { + IDsToKeep = new HashSet(); + try { + for ( final String line : new XReadLines(rsIDFile).readLines() ) { + IDsToKeep.add(line.trim()); + } + logger.info("Selecting only variants with one of " + IDsToKeep.size() + " IDs from " + rsIDFile); + } catch ( FileNotFoundException e ) { + throw new UserException.CouldNotReadInputFile(rsIDFile, e); + } + } } /** @@ -460,20 +467,23 @@ public class SelectVariants extends RodWalker implements TreeR } for (VariantContext vc : vcs) { + if ( IDsToKeep != null && ! IDsToKeep.contains(vc.getID()) ) + continue; + if (MENDELIAN_VIOLATIONS && mv.countViolations(this.getSampleDB().getFamilies(samples),vc) < 1) - break; + break; if (outMVFile != null){ for( String familyId : mv.getViolationFamilies()){ for(Sample sample : this.getSampleDB().getFamily(familyId)){ if(sample.getParents().size() > 0){ - 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.getCalledChrCount(vc.getAlternateAllele(0)), - sample.getMaternalID(), sample.getPaternalID(), sample.getID(), - vc.getGenotype(sample.getMaternalID()).toBriefString(), vc.getGenotype(sample.getMaternalID()).getLikelihoods().getAsString(), - vc.getGenotype(sample.getPaternalID()).toBriefString(), vc.getGenotype(sample.getPaternalID()).getLikelihoods().getAsString(), - vc.getGenotype(sample.getID()).toBriefString(),vc.getGenotype(sample.getID()).getLikelihoods().getAsString() ); + 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.getCalledChrCount(vc.getAlternateAllele(0)), + sample.getMaternalID(), sample.getPaternalID(), sample.getID(), + vc.getGenotype(sample.getMaternalID()).toBriefString(), vc.getGenotype(sample.getMaternalID()).getLikelihoods().getAsString(), + vc.getGenotype(sample.getPaternalID()).toBriefString(), vc.getGenotype(sample.getPaternalID()).getLikelihoods().getAsString(), + vc.getGenotype(sample.getID()).toBriefString(),vc.getGenotype(sample.getID()).getLikelihoods().getAsString() ); } } @@ -513,10 +523,7 @@ public class SelectVariants extends RodWalker implements TreeR else if (!SELECT_RANDOM_FRACTION || ( GenomeAnalysisEngine.getRandomGenerator().nextDouble() < fractionRandom)) { vcfWriter.add(sub); } - - } - } return 1; @@ -647,7 +654,7 @@ public class SelectVariants extends RodWalker implements TreeR // 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() ) - newGC = VariantContextUtils.stripPLs(sub.getGenotypes()); + newGC = VariantContextUtils.stripPLs(sub.getGenotypes()); //Remove a fraction of the genotypes if needed if(fractionGenotypes>0){ @@ -655,10 +662,10 @@ public class SelectVariants extends RodWalker implements TreeR for ( Genotype genotype : newGC ) { //Set genotype to no call if it falls in the fraction. if(fractionGenotypes>0 && randomGenotypes.nextDouble() alleles = new ArrayList(2); - alleles.add(Allele.create((byte)'.')); - alleles.add(Allele.create((byte)'.')); - genotypes.add(new Genotype(genotype.getSampleName(),alleles, Genotype.NO_LOG10_PERROR,genotype.getFilters(),new HashMap(),false)); + ArrayList alleles = new ArrayList(2); + alleles.add(Allele.create((byte)'.')); + alleles.add(Allele.create((byte)'.')); + genotypes.add(new Genotype(genotype.getSampleName(),alleles, Genotype.NO_LOG10_PERROR,genotype.getFilters(),new HashMap(),false)); } else{ genotypes.add(genotype); From 1994c3e3bca05593d05693dd41de7bf30b21cbec Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 16 Dec 2011 16:33:13 -0500 Subject: [PATCH 303/380] Only print warning about allele incompatibility when running there are genotypes in the file in CombineVariants --- .../sting/utils/variantcontext/VariantContextUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 91a018c4e..c9a4965c1 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -619,8 +619,9 @@ public class VariantContextUtils { 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)); + if ( ! genotypes.isEmpty() ) + 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); From c26295919e6cfb4903c11ea0646d2c93039de4d9 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 16 Dec 2011 14:36:27 -0500 Subject: [PATCH 304/380] Added hardClipBothEndsByReferenceCoordinates UnitTest for the ReadClipper --- .../utils/clipreads/ReadClipperUnitTest.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 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 fc1459ee0..2e2b5f373 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -34,7 +34,6 @@ 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.BeforeMethod; import org.testng.annotations.Test; import java.util.List; @@ -48,10 +47,6 @@ import java.util.List; */ public class ReadClipperUnitTest extends BaseTest { - // TODO: exception testing, make cases that should fail will fail - - // TODO: add indels to all test cases - List cigarList; int maximumCigarSize = 10; @@ -64,6 +59,18 @@ public class ReadClipperUnitTest extends BaseTest { public void testHardClipBothEndsByReferenceCoordinates() { logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); + for (Cigar cigar : cigarList) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + int alnStart = read.getAlignmentStart(); + int alnEnd = read.getAlignmentEnd(); + int readLength = alnStart - alnEnd; + for (int i=0; i= alnStart + i, String.format("Clipped alignment start is less than original read (minus %d): %s -> %s", i, read.getCigarString(), clippedRead.getCigarString())); + Assert.assertTrue(clippedRead.getAlignmentEnd() <= alnEnd + i, String.format("Clipped alignment end is greater than original read (minus %d): %s -> %s", i, read.getCigarString(), clippedRead.getCigarString())); + } + } + } @Test(enabled = true) @@ -116,7 +123,7 @@ public class ReadClipperUnitTest extends BaseTest { // Tests // Make sure the low qualities are gone - testNoLowQualBases(clipLeft, LOW_QUAL); + assertNoLowQualBases(clipLeft, LOW_QUAL); // Can't run this test with the current contract of no hanging insertions //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipLeft.getReadLength(), readLength - nLowQualBases, read.getCigarString(), clipLeft.getCigarString())); @@ -131,7 +138,7 @@ public class ReadClipperUnitTest extends BaseTest { // Tests // Make sure the low qualities are gone - testNoLowQualBases(clipRight, LOW_QUAL); + assertNoLowQualBases(clipRight, LOW_QUAL); // Make sure we haven't clipped any high quals -- Can't run this test with the current contract of no hanging insertions //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipRight.getReadLength(), readLength - nLowQualBases, read.getCigarString(), clipRight.getCigarString())); @@ -149,7 +156,7 @@ public class ReadClipperUnitTest extends BaseTest { // Tests // Make sure the low qualities are gone - testNoLowQualBases(clipBoth, LOW_QUAL); + assertNoLowQualBases(clipBoth, LOW_QUAL); // Can't run this test with the current contract of no hanging insertions //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipRight.getReadLength(), readLength - (2*nLowQualBases), read.getCigarString(), clipBoth.getCigarString())); @@ -158,11 +165,7 @@ public class ReadClipperUnitTest extends BaseTest { // logger.warn(String.format("Testing %s for all combinations of low/high qual... PASSED", read.getCigarString())); } - - - - - // ONE OFF Testing clipping that ends inside an insertion + // ONE OFF Testing clipping that ends inside an insertion ( Ryan's bug ) final byte[] BASES = {'A','C','G','T','A','C','G','T'}; final byte[] QUALS = {2, 2, 2, 2, 20, 20, 20, 2}; final String CIGAR = "1S1M5I1S"; @@ -179,14 +182,6 @@ public class ReadClipperUnitTest extends BaseTest { ClipReadsTestUtils.assertEqualReads(lowQualClipper.hardClipLowQualEnds((byte) 2), expected); } - private void testNoLowQualBases(GATKSAMRecord read, byte low_qual) { - if (!read.isEmpty()) { - byte [] quals = read.getBaseQualities(); - for (int i=0; i %s -- PASSED!", read.getCigarString(), clippedRead.getCigarString())); } } + + private void assertNoLowQualBases(GATKSAMRecord read, byte low_qual) { + if (!read.isEmpty()) { + byte [] quals = read.getBaseQualities(); + for (int i=0; i Date: Fri, 16 Dec 2011 15:10:22 -0500 Subject: [PATCH 305/380] Added hardClipByReadCoordinates UnitTest for the ReadClipper --- .../utils/clipreads/ReadClipperUnitTest.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 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 2e2b5f373..d93e0ac08 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -39,11 +39,8 @@ import org.testng.annotations.Test; import java.util.List; /** - * Created by IntelliJ IDEA. * User: roger * Date: 9/28/11 - * Time: 9:54 PM - * To change this template use File | Settings | File Templates. */ public class ReadClipperUnitTest extends BaseTest { @@ -57,8 +54,6 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipBothEndsByReferenceCoordinates() { - logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); - for (Cigar cigar : cigarList) { GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); int alnStart = read.getAlignmentStart(); @@ -70,18 +65,28 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertTrue(clippedRead.getAlignmentEnd() <= alnEnd + i, String.format("Clipped alignment end is greater than original read (minus %d): %s -> %s", i, read.getCigarString(), clippedRead.getCigarString())); } } - + logger.warn("PASSED"); } @Test(enabled = true) public void testHardClipByReadCoordinates() { - logger.warn("Executing testHardClipByReadCoordinates"); + for (Cigar cigar : cigarList) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + int readLength = read.getReadLength(); + for (int i=0; i %s", i, read.getCigarString(), clipLeft.getCigarString())); + GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReadCoordinates(i, readLength-1); + Assert.assertTrue(clipRight.getReadLength() <= i, String.format("Clipped read length is greater than original read length (minus %d): %s -> %s", i, read.getCigarString(), clipRight.getCigarString())); + } + } + logger.warn("PASSED"); } @Test(enabled = true) public void testHardClipByReferenceCoordinates() { - logger.warn("Executing testHardClipByReferenceCoordinates"); + logger.warn("PASSED"); } @@ -93,15 +98,12 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipByReferenceCoordinatesRightTail() { - init(); - logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); + logger.warn("PASSED"); } @Test(enabled = true) public void testHardClipLowQualEnds() { - logger.warn("Executing testHardClipLowQualEnds"); - final byte LOW_QUAL = 2; final byte HIGH_QUAL = 30; @@ -180,6 +182,8 @@ public class ReadClipperUnitTest extends BaseTest { ReadClipper lowQualClipper = new ReadClipper(read); ClipReadsTestUtils.assertEqualReads(lowQualClipper.hardClipLowQualEnds((byte) 2), expected); + + logger.warn("PASSED"); } @Test(enabled = true) @@ -233,6 +237,8 @@ public class ReadClipperUnitTest extends BaseTest { // logger.warn(String.format("Cigar %s -> %s -- PASSED!", read.getCigarString(), clippedRead.getCigarString())); } + + logger.warn("PASSED"); } private void assertNoLowQualBases(GATKSAMRecord read, byte low_qual) { From 5bba44d6934b06f944728cd3aad5ef191373a369 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 16 Dec 2011 16:36:31 -0500 Subject: [PATCH 306/380] Added hardClipByReferenceCoordinates UnitTest for the ReadClipper * fixed edge case when requested to hard clip beginning of a read that had hanging soft clipped bases on the left tail. * fixed edge case when requested to hard clip end of a read that had hanging soft clipped bases on the right tail. * fixed AlignmentStart of a clipped read that results in only hard clips and soft clips note: added tests to all these beautiful cases... --- .../sting/utils/clipreads/ClippingOp.java | 7 ++++-- .../sting/utils/clipreads/ReadClipper.java | 9 ++++++++ .../utils/clipreads/ReadClipperUnitTest.java | 23 ++++++++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java index 06985041e..0bb8c3e6f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -460,17 +460,20 @@ public class ClippingOp { int newShift = 0; int oldShift = 0; + boolean readHasStarted = false; // if the new cigar is composed of S and H only, we have to traverse the entire old cigar to calculate the shift for (CigarElement cigarElement : newCigar.getCigarElements()) { if (cigarElement.getOperator() == CigarOperator.HARD_CLIP || cigarElement.getOperator() == CigarOperator.SOFT_CLIP) newShift += cigarElement.getLength(); - else + else { + readHasStarted = true; break; + } } for (CigarElement cigarElement : oldCigar.getCigarElements()) { if (cigarElement.getOperator() == CigarOperator.HARD_CLIP || cigarElement.getOperator() == CigarOperator.SOFT_CLIP ) oldShift += Math.min(cigarElement.getLength(), newShift - oldShift); - else + else if (readHasStarted) break; } return newShift - oldShift; 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 4bb309a48..461c9aef3 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -63,12 +63,18 @@ public class ReadClipper { int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart, ReadUtils.ClippingTail.RIGHT_TAIL); int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop, ReadUtils.ClippingTail.LEFT_TAIL); + if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) + return new GATKSAMRecord(read.getHeader()); + if (start < 0 || stop > read.getReadLength() - 1) throw new ReviewedStingException("Trying to clip before the start or after the end of a read"); if ( start > stop ) throw new ReviewedStingException("START > STOP -- this should never happen -- call Mauricio!"); + if ( start > 0 && stop < read.getReadLength() - 1) + throw new ReviewedStingException(String.format("Trying to clip the middle of the read: start %d, stop %d, cigar: %s", start, stop, read.getCigarString())); + this.addOp(new ClippingOp(start, stop)); GATKSAMRecord clippedRead = clipRead(ClippingRepresentation.HARDCLIP_BASES); this.ops = null; @@ -76,6 +82,9 @@ public class ReadClipper { } public GATKSAMRecord hardClipByReadCoordinates(int start, int stop) { + if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) + return new GATKSAMRecord(read.getHeader()); + this.addOp(new ClippingOp(start, stop)); return clipRead(ClippingRepresentation.HARDCLIP_BASES); } 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 d93e0ac08..193eec661 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -32,6 +32,7 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.ReadUtils; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -86,14 +87,30 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipByReferenceCoordinates() { - logger.warn("PASSED"); + for (Cigar cigar : cigarList) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + int alnStart = read.getAlignmentStart(); + int alnEnd = read.getAlignmentEnd(); + for (int i=alnStart; i<=alnEnd; i++) { + if (ReadUtils.getRefCoordSoftUnclippedStart(read) == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side + GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipByReferenceCoordinates(alnStart, i); + if (!clipLeft.isEmpty()) + Assert.assertTrue(clipLeft.getAlignmentStart() >= i + 1, String.format("Clipped alignment start (%d) is less the expected (%d): %s -> %s", clipLeft.getAlignmentStart(), i + 1, read.getCigarString(), clipLeft.getCigarString())); + } + if (ReadUtils.getRefCoordSoftUnclippedEnd(read) == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side + GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReferenceCoordinates(i, alnEnd); + if (!clipRight.isEmpty() && clipRight.getAlignmentStart() <= clipRight.getAlignmentEnd()) // alnStart > alnEnd if the entire read is a soft clip now. We can't test those. + Assert.assertTrue(clipRight.getAlignmentEnd() <= i - 1, String.format("Clipped alignment end (%d) is greater than expected (%d): %s -> %s", clipRight.getAlignmentEnd(), i - 1, read.getCigarString(), clipRight.getCigarString())); + } + } + } + logger.warn("PASSED"); } @Test(enabled = true) public void testHardClipByReferenceCoordinatesLeftTail() { - logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); - + logger.warn("PASSED"); } @Test(enabled = true) From 075be52adc2f544812270905bceb912da3b4961c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 16 Dec 2011 17:05:46 -0500 Subject: [PATCH 307/380] Added hardClipByReferenceCoordinates (left and right tails) UnitTest for the ReadClipper --- .../utils/clipreads/ReadClipperUnitTest.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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 193eec661..655b1e709 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -110,13 +110,36 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipByReferenceCoordinatesLeftTail() { + for (Cigar cigar : cigarList) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + int alnStart = read.getAlignmentStart(); + int alnEnd = read.getAlignmentEnd(); + if (ReadUtils.getRefCoordSoftUnclippedStart(read) == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side + for (int i=alnStart; i<=alnEnd; i++) { + GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipByReferenceCoordinates(alnStart, i); + if (!clipLeft.isEmpty()) + Assert.assertTrue(clipLeft.getAlignmentStart() >= i + 1, String.format("Clipped alignment start (%d) is less the expected (%d): %s -> %s", clipLeft.getAlignmentStart(), i + 1, read.getCigarString(), clipLeft.getCigarString())); + } + } + } logger.warn("PASSED"); } @Test(enabled = true) public void testHardClipByReferenceCoordinatesRightTail() { + for (Cigar cigar : cigarList) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + int alnStart = read.getAlignmentStart(); + int alnEnd = read.getAlignmentEnd(); + if (ReadUtils.getRefCoordSoftUnclippedEnd(read) == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side + for (int i=alnStart; i<=alnEnd; i++) { + GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReferenceCoordinates(i, alnEnd); + if (!clipRight.isEmpty() && clipRight.getAlignmentStart() <= clipRight.getAlignmentEnd()) // alnStart > alnEnd if the entire read is a soft clip now. We can't test those. + Assert.assertTrue(clipRight.getAlignmentEnd() <= i - 1, String.format("Clipped alignment end (%d) is greater than expected (%d): %s -> %s", clipRight.getAlignmentEnd(), i - 1, read.getCigarString(), clipRight.getCigarString())); + } + } + } logger.warn("PASSED"); - } @Test(enabled = true) From fcc21180e8b3b9c7f54b011d2e8e2f1521a05805 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 16 Dec 2011 17:22:40 -0500 Subject: [PATCH 308/380] Added hardClipLeadingInsertions UnitTest for the ReadClipper fixed issue where a read starting with an insertion followed by a deletion would break, clipper can now safely clip the insertion and the deletion if that's the case. note: test is turned off until contract changes to allow hanging insertions (left/right). --- .../sting/utils/clipreads/ReadClipper.java | 8 +--- .../utils/clipreads/ClipReadsTestUtils.java | 12 +++++ .../utils/clipreads/ReadClipperUnitTest.java | 46 +++++++++++++++++-- 3 files changed, 57 insertions(+), 9 deletions(-) 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 461c9aef3..e6b3ce929 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -204,16 +204,12 @@ public class ReadClipper { for(CigarElement cigarElement : read.getCigar().getCigarElements()) { if (cigarElement.getOperator() != CigarOperator.HARD_CLIP && cigarElement.getOperator() != CigarOperator.SOFT_CLIP && - cigarElement.getOperator() != CigarOperator.INSERTION && cigarElement.getOperator() != CigarOperator.DELETION) + cigarElement.getOperator() != CigarOperator.INSERTION) break; - else if (cigarElement.getOperator() == CigarOperator.INSERTION) { + else if (cigarElement.getOperator() == CigarOperator.INSERTION) this.addOp(new ClippingOp(0, cigarElement.getLength() - 1)); - } - else if (cigarElement.getOperator() == CigarOperator.DELETION) { - throw new ReviewedStingException("No read should start with a deletion. Aligner bug?"); - } } return clipRead(ClippingRepresentation.HARDCLIP_BASES); } diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java index c6dc3a833..048d9d8e0 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java @@ -176,4 +176,16 @@ public class ClipReadsTestUtils { Assert.assertEquals(actual.isEmpty(), expected.isEmpty()); } + public static Cigar invertCigar (Cigar cigar) { + Stack cigarStack = new Stack(); + for (CigarElement cigarElement : cigar.getCigarElements()) + cigarStack.push(cigarElement); + + Cigar invertedCigar = new Cigar(); + while (!cigarStack.isEmpty()) + invertedCigar.add(cigarStack.pop()); + + return invertedCigar; + } + } 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 655b1e709..249951139 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -46,7 +46,7 @@ import java.util.List; public class ReadClipperUnitTest extends BaseTest { List cigarList; - int maximumCigarSize = 10; + int maximumCigarSize = 6; // 6 is the minimum necessary number to try all combinations of cigar types with guarantee of clipping an element with length = 2 @BeforeClass public void init() { @@ -277,10 +277,31 @@ public class ReadClipperUnitTest extends BaseTest { // logger.warn(String.format("Cigar %s -> %s -- PASSED!", read.getCigarString(), clippedRead.getCigarString())); } - - logger.warn("PASSED"); } + @Test(enabled = false) + public void testHardClipLeadingInsertions() { + for (Cigar cigar : cigarList) { + if (startsWithInsertion(cigar)) { + GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + GATKSAMRecord clippedRead = (new ReadClipper(read)).hardClipLeadingInsertions(); + + int expectedLength = read.getReadLength() - leadingInsertionLength(read.getCigar()); + if (cigarHasElementsDifferentThanInsertionsAndHardClips(read.getCigar())) + expectedLength -= leadingInsertionLength(ClipReadsTestUtils.invertCigar(read.getCigar())); + + if (! clippedRead.isEmpty()) { + Assert.assertEquals(expectedLength, clippedRead.getReadLength(), String.format("%s -> %s", read.getCigarString(), clippedRead.getCigarString())); // check that everything else is still there + Assert.assertFalse(startsWithInsertion(clippedRead.getCigar())); // check that the insertions are gone + } + else + Assert.assertTrue(expectedLength == 0, String.format("expected length: %d", expectedLength)); // check that the read was expected to be fully clipped + } + } + } + + + private void assertNoLowQualBases(GATKSAMRecord read, byte low_qual) { if (!read.isEmpty()) { byte [] quals = read.getBaseQualities(); @@ -289,5 +310,24 @@ public class ReadClipperUnitTest extends BaseTest { } } + private boolean startsWithInsertion(Cigar cigar) { + return leadingInsertionLength(cigar) > 0; + } + private int leadingInsertionLength(Cigar cigar) { + for (CigarElement cigarElement : cigar.getCigarElements()) { + if (cigarElement.getOperator() == CigarOperator.INSERTION) + return cigarElement.getLength(); + if (cigarElement.getOperator() != CigarOperator.HARD_CLIP) + break; + } + return 0; + } + + private boolean cigarHasElementsDifferentThanInsertionsAndHardClips (Cigar cigar) { + for (CigarElement cigarElement : cigar.getCigarElements()) + if (cigarElement.getOperator() != CigarOperator.INSERTION && cigarElement.getOperator() != CigarOperator.HARD_CLIP) + return true; + return false; + } } \ No newline at end of file From e5df9e06845bfddac3d1f5daddea7c89bc6a4efa Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 16 Dec 2011 17:08:18 -0500 Subject: [PATCH 309/380] cleaner test output cleaned up the debug "pass" messages in the unit tests --- .../sting/utils/clipreads/ReadClipperUnitTest.java | 7 ------- 1 file changed, 7 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 249951139..fcde5f360 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -66,7 +66,6 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertTrue(clippedRead.getAlignmentEnd() <= alnEnd + i, String.format("Clipped alignment end is greater than original read (minus %d): %s -> %s", i, read.getCigarString(), clippedRead.getCigarString())); } } - logger.warn("PASSED"); } @Test(enabled = true) @@ -82,7 +81,6 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertTrue(clipRight.getReadLength() <= i, String.format("Clipped read length is greater than original read length (minus %d): %s -> %s", i, read.getCigarString(), clipRight.getCigarString())); } } - logger.warn("PASSED"); } @Test(enabled = true) @@ -105,7 +103,6 @@ public class ReadClipperUnitTest extends BaseTest { } } } - logger.warn("PASSED"); } @Test(enabled = true) @@ -122,7 +119,6 @@ public class ReadClipperUnitTest extends BaseTest { } } } - logger.warn("PASSED"); } @Test(enabled = true) @@ -139,7 +135,6 @@ public class ReadClipperUnitTest extends BaseTest { } } } - logger.warn("PASSED"); } @Test(enabled = true) @@ -222,8 +217,6 @@ public class ReadClipperUnitTest extends BaseTest { ReadClipper lowQualClipper = new ReadClipper(read); ClipReadsTestUtils.assertEqualReads(lowQualClipper.hardClipLowQualEnds((byte) 2), expected); - - logger.warn("PASSED"); } @Test(enabled = true) From 7486696c076f73c02a61baaf2262f6f1cdc91de7 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Fri, 16 Dec 2011 18:07:26 -0500 Subject: [PATCH 310/380] When using bam list mode in HSP deriving VCF name from bam list instead of requiring an additional parameter. Creating a single temporary directory per ant test run instead of a putting temp files across all runs in the same directory. Updated various tests for above items and other small fixes. --- .../sting/utils/R/RScriptExecutor.java | 2 +- .../sting/utils/io/IOUtils.java | 2 +- .../org/broadinstitute/sting/BaseTest.java | 28 +++--- .../drmaa/v1_0/JnaSessionIntegrationTest.java | 2 +- .../drmaa/v1_0/LibDrmaaIntegrationTest.java | 4 +- .../jna/lsf/v7_0_6/LibBatIntegrationTest.java | 2 +- .../qscripts/examples/ExampleCountLoci.scala | 2 +- .../qscripts/examples/ExampleCountReads.scala | 6 +- .../examples/ExampleCustomWalker.scala | 2 +- .../examples/ExampleUnifiedGenotyper.scala | 1 - .../sting/queue/QCommandLine.scala | 2 +- .../ExampleCountLociPipelineTest.scala | 2 +- .../ExampleCountReadsPipelineTest.scala | 66 +++++++++++++ .../ExampleUnifiedGenotyperPipelineTest.scala | 92 +++++++++++++++++++ 14 files changed, 187 insertions(+), 26 deletions(-) create mode 100644 public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala create mode 100644 public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala diff --git a/public/java/src/org/broadinstitute/sting/utils/R/RScriptExecutor.java b/public/java/src/org/broadinstitute/sting/utils/R/RScriptExecutor.java index d8176ff4e..d753da1c8 100644 --- a/public/java/src/org/broadinstitute/sting/utils/R/RScriptExecutor.java +++ b/public/java/src/org/broadinstitute/sting/utils/R/RScriptExecutor.java @@ -109,7 +109,7 @@ public class RScriptExecutor { List tempFiles = new ArrayList(); try { - File tempLibDir = IOUtils.tempDir("R.", ".lib"); + File tempLibDir = IOUtils.tempDir("Rlib.", ""); tempFiles.add(tempLibDir); StringBuilder expression = new StringBuilder("tempLibDir = '").append(tempLibDir).append("';"); diff --git a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java index 6f0573b02..b3fdb93d3 100644 --- a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java @@ -79,7 +79,7 @@ public class IOUtils { tempDirParent = FileUtils.getTempDirectory(); if (!tempDirParent.exists() && !tempDirParent.mkdirs()) throw new UserException.BadTmpDir("Could not create temp directory: " + tempDirParent); - File temp = File.createTempFile(prefix + "-", suffix, tempDirParent); + File temp = File.createTempFile(prefix, suffix, tempDirParent); if (!temp.delete()) throw new UserException.BadTmpDir("Could not delete sub file: " + temp.getAbsolutePath()); if (!temp.mkdir()) diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java index 54d34fcfa..0f59131f5 100755 --- a/public/java/test/org/broadinstitute/sting/BaseTest.java +++ b/public/java/test/org/broadinstitute/sting/BaseTest.java @@ -3,9 +3,11 @@ package org.broadinstitute.sting; import org.apache.commons.io.FileUtils; import org.apache.log4j.*; import org.apache.log4j.spi.LoggingEvent; +import org.apache.xmlbeans.impl.common.IOUtil; import org.broadinstitute.sting.commandline.CommandLineUtils; import org.broadinstitute.sting.gatk.walkers.diffengine.DiffEngine; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.io.IOUtils; import org.testng.Assert; import javax.swing.*; @@ -78,8 +80,8 @@ public abstract class BaseTest { public static final String hg19Intervals = intervalsLocation + "whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.interval_list"; public static final String hg19Chr20Intervals = intervalsLocation + "whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.chr20.interval_list"; - public static final String networkTempDir = "/broad/shptmp/" + System.getProperty("user.name") + "/"; - public static final File networkTempDirFile = new File(networkTempDir); + public static final String networkTempDir; + public static final File networkTempDirFile; public static final File testDirFile = new File("public/testdata/"); public static final String testDir = testDirFile.getAbsolutePath() + "/"; @@ -99,6 +101,10 @@ public abstract class BaseTest { // Set the Root logger to only output warnings. logger.setLevel(Level.WARN); + networkTempDirFile = IOUtils.tempDir("temp.", ".dir", new File("/broad/shptmp/" + System.getProperty("user.name"))); + networkTempDirFile.deleteOnExit(); + networkTempDir = networkTempDirFile.getAbsolutePath() + "/"; + // find our file sources // if (!fileExist(hg18Reference) || !fileExist(hg19Reference) || !fileExist(b36KGReference)) { // logger.fatal("We can't locate the reference directories. Aborting!"); @@ -233,18 +239,12 @@ public abstract class BaseTest { /** * Creates a temp file that will be deleted on exit after tests are complete. - * @param name Prefix of the file. - * @param extension Extension to concat to the end of the file. - * @return A file in the network temporary directory starting with name, ending with extension, which will be deleted after the program exits. + * @param name Name of the file. + * @return A file in the network temporary directory with name, which will be deleted after the program exits. */ - public static File createNetworkTempFile(String name, String extension) { - try { - FileUtils.forceMkdir(networkTempDirFile); - File file = File.createTempFile(name, extension, networkTempDirFile); - file.deleteOnExit(); - return file; - } catch (IOException ex) { - throw new ReviewedStingException("Cannot create temp file: " + ex.getMessage(), ex); - } + public static File createNetworkTempFile(String name) { + File file = new File(networkTempDirFile, name); + file.deleteOnExit(); + return file; } } diff --git a/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/JnaSessionIntegrationTest.java b/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/JnaSessionIntegrationTest.java index 48f4c3777..f68a96d26 100644 --- a/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/JnaSessionIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/JnaSessionIntegrationTest.java @@ -62,7 +62,7 @@ public class JnaSessionIntegrationTest extends BaseTest { return; } - File outFile = createNetworkTempFile("JnaSessionIntegrationTest-", ".out"); + File outFile = createNetworkTempFile("JnaSessionIntegrationTest.out"); Session session = factory.getSession(); session.init(null); try { diff --git a/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/LibDrmaaIntegrationTest.java b/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/LibDrmaaIntegrationTest.java index d98281ad3..4c7d4ce06 100644 --- a/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/LibDrmaaIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/jna/drmaa/v1_0/LibDrmaaIntegrationTest.java @@ -86,7 +86,7 @@ public class LibDrmaaIntegrationTest extends BaseTest { @Test(dependsOnMethods = { "testDrmaa" }) public void testSubmitEcho() throws Exception { - if (implementation.indexOf("LSF") >= 0) { + if (implementation.contains("LSF")) { System.err.println(" *********************************************************"); System.err.println(" ***********************************************************"); System.err.println(" **** ****"); @@ -101,7 +101,7 @@ public class LibDrmaaIntegrationTest extends BaseTest { Memory error = new Memory(LibDrmaa.DRMAA_ERROR_STRING_BUFFER); int errnum; - File outFile = createNetworkTempFile("LibDrmaaIntegrationTest-", ".out"); + File outFile = createNetworkTempFile("LibDrmaaIntegrationTest.out"); errnum = LibDrmaa.drmaa_init(null, error, LibDrmaa.DRMAA_ERROR_STRING_BUFFER_LEN); diff --git a/public/java/test/org/broadinstitute/sting/jna/lsf/v7_0_6/LibBatIntegrationTest.java b/public/java/test/org/broadinstitute/sting/jna/lsf/v7_0_6/LibBatIntegrationTest.java index b4fb5cfa3..21339eb46 100644 --- a/public/java/test/org/broadinstitute/sting/jna/lsf/v7_0_6/LibBatIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/jna/lsf/v7_0_6/LibBatIntegrationTest.java @@ -93,7 +93,7 @@ public class LibBatIntegrationTest extends BaseTest { @Test public void testSubmitEcho() throws Exception { String queue = "hour"; - File outFile = createNetworkTempFile("LibBatIntegrationTest-", ".out"); + File outFile = createNetworkTempFile("LibBatIntegrationTest.out"); submit req = new submit(); diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountLoci.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountLoci.scala index 4ca3cbb89..149376018 100644 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountLoci.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountLoci.scala @@ -19,7 +19,7 @@ class ExampleCountLoci extends QScript { @Output var out: File = _ - def script = { + def script() { val countLoci = new CountLoci countLoci.reference_sequence = referenceFile countLoci.input_file = bamFiles diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountReads.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountReads.scala index 9fdd1ba4c..7f9d3f87a 100644 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountReads.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountReads.scala @@ -24,7 +24,7 @@ class ExampleCountReads extends QScript { /** * In script, you create and then add() functions to the pipeline. */ - def script = { + def script() { // Run CountReads for all bams jointly. @@ -41,6 +41,9 @@ class ExampleCountReads extends QScript { // matches the full form of the argument, but will actually be a scala List[] jointCountReads.input_file = bamFiles + // Set the memory limit. Also acts as a memory request on LSF and GridEngine. + jointCountReads.memoryLimit = 1 + // Add the newly created function to the pipeline. add(jointCountReads) @@ -51,6 +54,7 @@ class ExampleCountReads extends QScript { singleCountReads.reference_sequence = referenceFile // ':+' is the scala List append operator singleCountReads.input_file :+= bamFile + singleCountReads.memoryLimit = 1 add(singleCountReads) } } diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCustomWalker.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCustomWalker.scala index d3796d350..d30668c19 100644 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCustomWalker.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCustomWalker.scala @@ -24,7 +24,7 @@ class ExampleCustomWalker extends QScript { /** * In script, you create and then add() functions to the pipeline. */ - def script = { + def script() { val customWalker = new CommandLineGATK { // Set the name of your walker, for example this will be passed as -T MyCustomWalker this.analysis_type = "MyCustomWalker" diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala index 2f4aea755..8cb86db0b 100644 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala @@ -33,7 +33,6 @@ class ExampleUnifiedGenotyper extends QScript { @Argument(doc="An optional list of filter expressions.", shortName="filterExpression", required=false) var filterExpressions: List[String] = Nil - // This trait allows us set the variables below in one place, // and then reuse this trait on each CommandLineGATK function below. trait UnifiedGenotyperArguments extends CommandLineGATK { diff --git a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala index 913bd243c..32913deb4 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala @@ -89,7 +89,7 @@ class QCommandLine extends CommandLineProgram with Logging { private var shuttingDown = false private lazy val pluginManager = { - qScriptClasses = IOUtils.tempDir("Q-Classes", "", settings.qSettings.tempDirectory) + qScriptClasses = IOUtils.tempDir("Q-Classes-", "", settings.qSettings.tempDirectory) qScriptManager.loadScripts(scripts, qScriptClasses) new PluginManager[QScript](classOf[QScript], List(qScriptClasses.toURI.toURL)) } diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala index 5901cab46..f657e4be1 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala @@ -30,7 +30,7 @@ import org.broadinstitute.sting.BaseTest class ExampleCountLociPipelineTest { @Test - def testCountLoci { + def testCountLoci() { val testOut = "count.out" val spec = new PipelineTestSpec spec.name = "countloci" diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala new file mode 100644 index 000000000..d9147a0ed --- /dev/null +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala @@ -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.queue.pipeline.examples + +/* + * 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. + */ + +import org.testng.annotations.Test +import org.broadinstitute.sting.queue.pipeline.{PipelineTest, PipelineTestSpec} +import org.broadinstitute.sting.BaseTest + +class ExampleCountReadsPipelineTest { + @Test + def testCountReads() { + val spec = new PipelineTestSpec + spec.name = "countreads" + spec.args = Array( + " -S public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountReads.scala", + " -R " + BaseTest.testDir + "exampleFASTA.fasta", + " -I " + BaseTest.testDir + "exampleBAM.bam").mkString + PipelineTest.executeTest(spec) + } +} diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala new file mode 100644 index 000000000..858e1cce6 --- /dev/null +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala @@ -0,0 +1,92 @@ +/* + * 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.pipeline.examples + +/* + * 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. + */ + +/* + * 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. + */ + +import org.testng.annotations.Test +import org.broadinstitute.sting.queue.pipeline.{PipelineTest, PipelineTestSpec} +import org.broadinstitute.sting.BaseTest + +class ExampleUnifiedGenotyperPipelineTest { + @Test + def testUnifiedGenotyper() { + val spec = new PipelineTestSpec + spec.name = "unifiedgenotyper" + spec.args = Array( + " -S public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleUnifiedGenotyper.scala", + " -R " + BaseTest.testDir + "exampleFASTA.fasta", + " -I " + BaseTest.testDir + "exampleBAM.bam", + " -filter QD", + " -filterExpression 'QD < 2.0'").mkString + PipelineTest.executeTest(spec) + } +} From 6059ca76e8939f77e3e7ebe940912f9c47181db8 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Fri, 16 Dec 2011 23:00:16 -0500 Subject: [PATCH 311/380] Removing cruft that snuck in last commit. --- .../org/broadinstitute/sting/BaseTest.java | 8 ---- .../ExampleCountReadsPipelineTest.scala | 24 ---------- .../ExampleUnifiedGenotyperPipelineTest.scala | 48 ------------------- 3 files changed, 80 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java index 0f59131f5..8e218f950 100755 --- a/public/java/test/org/broadinstitute/sting/BaseTest.java +++ b/public/java/test/org/broadinstitute/sting/BaseTest.java @@ -1,20 +1,12 @@ package org.broadinstitute.sting; -import org.apache.commons.io.FileUtils; import org.apache.log4j.*; import org.apache.log4j.spi.LoggingEvent; -import org.apache.xmlbeans.impl.common.IOUtil; import org.broadinstitute.sting.commandline.CommandLineUtils; -import org.broadinstitute.sting.gatk.walkers.diffengine.DiffEngine; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.io.IOUtils; -import org.testng.Assert; -import javax.swing.*; import java.io.*; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.*; /** diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala index d9147a0ed..8b286f090 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountReadsPipelineTest.scala @@ -24,30 +24,6 @@ package org.broadinstitute.sting.queue.pipeline.examples -/* - * 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. - */ - import org.testng.annotations.Test import org.broadinstitute.sting.queue.pipeline.{PipelineTest, PipelineTestSpec} import org.broadinstitute.sting.BaseTest diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala index 858e1cce6..d50673a1a 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleUnifiedGenotyperPipelineTest.scala @@ -24,54 +24,6 @@ package org.broadinstitute.sting.queue.pipeline.examples -/* - * 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. - */ - -/* - * 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. - */ - import org.testng.annotations.Test import org.broadinstitute.sting.queue.pipeline.{PipelineTest, PipelineTestSpec} import org.broadinstitute.sting.BaseTest From 6dc52d42bfbc8777dcc9307cb68463a461d811f7 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sun, 18 Dec 2011 00:01:42 -0500 Subject: [PATCH 312/380] Implemented the proper QUAL calculation for multi-allelic calls. Integration tests pass except for the ones making multi-allelic calls (duh) and one of the SLOD tests (which used to print 0 when one of the LODs was NaN but now we just don't print the SB annotation for that record). --- .../AlleleFrequencyCalculationResult.java | 4 + .../genotyper/ExactAFCalculationModel.java | 19 ++-- .../genotyper/UnifiedGenotyperEngine.java | 87 ++++++++++--------- .../UnifiedGenotyperIntegrationTest.java | 6 +- 4 files changed, 64 insertions(+), 52 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java index 66106f658..cc72fde11 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java @@ -34,9 +34,13 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; */ public class AlleleFrequencyCalculationResult { + // note that the cell at position zero in the likelihoods/posteriors array is actually probability of non-ref (since it's marginalized over all alleles) final double[][] log10AlleleFrequencyLikelihoods; final double[][] log10AlleleFrequencyPosteriors; + double log10LikelihoodOfAFzero = 0.0; + double log10PosteriorOfAFzero = 0.0; + AlleleFrequencyCalculationResult(int maxAltAlleles, int numChr) { log10AlleleFrequencyLikelihoods = new double[maxAltAlleles][numChr+1]; log10AlleleFrequencyPosteriors = new double[maxAltAlleles][numChr+1]; 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 bc27916dd..aa743f86f 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 @@ -407,14 +407,19 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { nonRefAlleles++; } - // update the likelihoods/posteriors vectors which are collapsed views of each of the various ACs - for ( int i = 0; i < set.ACcounts.getCounts().length; i++ ) { - int AC = set.ACcounts.getCounts()[i]; - result.log10AlleleFrequencyLikelihoods[i][AC] = approximateLog10SumLog10(result.log10AlleleFrequencyLikelihoods[i][AC], log10LofK); + // for k=0, we don't want to put that value into the likelihoods/posteriors matrix, but instead want to set the value in the results object + if ( nonRefAlleles == 0 ) { + result.log10LikelihoodOfAFzero = log10LofK; + result.log10PosteriorOfAFzero = log10LofK + log10AlleleFrequencyPriors[0][0]; + } else { + // update the likelihoods/posteriors vectors which are collapsed views of each of the various ACs + for ( int i = 0; i < set.ACcounts.getCounts().length; i++ ) { + int AC = set.ACcounts.getCounts()[i]; + result.log10AlleleFrequencyLikelihoods[i][AC] = approximateLog10SumLog10(result.log10AlleleFrequencyLikelihoods[i][AC], log10LofK); - // for k=0 we still want to use theta - final double prior = (nonRefAlleles == 0) ? log10AlleleFrequencyPriors[0][0] : log10AlleleFrequencyPriors[nonRefAlleles-1][AC]; - result.log10AlleleFrequencyPosteriors[i][AC] = approximateLog10SumLog10(result.log10AlleleFrequencyPosteriors[i][AC], log10LofK + prior); + final double prior = log10AlleleFrequencyPriors[nonRefAlleles-1][AC]; + result.log10AlleleFrequencyPosteriors[i][AC] = approximateLog10SumLog10(result.log10AlleleFrequencyPosteriors[i][AC], log10LofK + prior); + } } } 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 7e5f388b2..533bf0c68 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 @@ -75,8 +75,9 @@ public class UnifiedGenotyperEngine { // the model used for calculating p(non-ref) private ThreadLocal afcm = new ThreadLocal(); - // the allele frequency likelihoods (allocated once as an optimization) + // the allele frequency likelihoods and posteriors (allocated once as an optimization) private ThreadLocal alleleFrequencyCalculationResult = new ThreadLocal(); + private ThreadLocal posteriorsArray = new ThreadLocal(); // because the allele frequency priors are constant for a given i, we cache the results to avoid having to recompute everything private final double[][] log10AlleleFrequencyPriorsSNPs; @@ -289,7 +290,9 @@ public class UnifiedGenotyperEngine { if ( afcm.get() == null ) { afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); alleleFrequencyCalculationResult.set(new AlleleFrequencyCalculationResult(UAC.MAX_ALTERNATE_ALLELES, N)); + posteriorsArray.set(new double[N + 2]); } + AlleleFrequencyCalculationResult AFresult = alleleFrequencyCalculationResult.get(); // don't try to genotype too many alternate alleles if ( vc.getAlternateAlleles().size() > UAC.MAX_ALTERNATE_ALLELES ) { @@ -307,24 +310,20 @@ public class UnifiedGenotyperEngine { } // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); - afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); + clearAFarray(AFresult.log10AlleleFrequencyLikelihoods); + clearAFarray(AFresult.log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), AFresult); // is the most likely frequency conformation AC=0 for all alternate alleles? boolean bestGuessIsRef = true; - // which alternate allele has the highest MLE AC? - int indexOfHighestAlt = -1; - int alleleCountOfHighestAlt = -1; - // determine which alternate alleles have AF>0 boolean[] altAllelesToUse = new boolean[vc.getAlternateAlleles().size()]; for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { - int indexOfBestAC = MathUtils.maxElementIndex(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[i]); + int indexOfBestAC = MathUtils.maxElementIndex(AFresult.log10AlleleFrequencyPosteriors[i]); // if the most likely AC is not 0, then this is a good alternate allele to use - if ( indexOfBestAC != 0 ) { + if ( indexOfBestAC != 0 && (AFresult.log10AlleleFrequencyPosteriors[i][0] != AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED || AFresult.log10AlleleFrequencyPosteriors[i][indexOfBestAC] > AFresult.log10PosteriorOfAFzero) ) { altAllelesToUse[i] = true; bestGuessIsRef = false; } @@ -332,19 +331,14 @@ public class UnifiedGenotyperEngine { else if ( UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { altAllelesToUse[i] = true; } - - // keep track of the "best" alternate allele to use - if ( indexOfBestAC > alleleCountOfHighestAlt) { - alleleCountOfHighestAlt = indexOfBestAC; - indexOfHighestAlt = i; - } } - // calculate p(f>0) - // TODO -- right now we just calculate it for the alt allele with highest AF, but the likelihoods need to be combined correctly over all AFs - double[] normalizedPosteriors = MathUtils.normalizeFromLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[indexOfHighestAlt]); + // calculate p(f>0): + // because the likelihoods are marginalized for each alternate allele, we only need to compare log10PosteriorOfAFzero against any one of them + double[] normalizedPosteriors = generateNormalizedPosteriors(AFresult, posteriorsArray.get()); + // TODO -- why not just 1.0 - normalizedPosteriors[0]? double sum = 0.0; - for (int i = 1; i <= N; i++) + for (int i = 1; i <= N+1; i++) // N+1 because the cell at position zero in the original posteriors array is actually probability of non-ref (since it's marginalized over all alleles) sum += normalizedPosteriors[i]; double PofF = Math.min(sum, 1.0); // deal with precision errors @@ -352,15 +346,17 @@ public class UnifiedGenotyperEngine { if ( !bestGuessIsRef || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { phredScaledConfidence = QualityUtils.phredScaleErrorRate(normalizedPosteriors[0]); if ( Double.isInfinite(phredScaledConfidence) ) - phredScaledConfidence = -10.0 * alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][0]; + phredScaledConfidence = -10.0 * AFresult.log10PosteriorOfAFzero; } else { phredScaledConfidence = QualityUtils.phredScaleErrorRate(PofF); if ( Double.isInfinite(phredScaledConfidence) ) { - sum = 0.0; + sum = AFresult.log10AlleleFrequencyPosteriors[0][0]; + if ( sum == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) + sum = 0.0; for (int i = 1; i <= N; i++) { - if ( alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) + if ( AFresult.log10AlleleFrequencyPosteriors[0][i] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) break; - sum += alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][i]; + sum += AFresult.log10AlleleFrequencyPosteriors[0][i]; } phredScaledConfidence = (MathUtils.compareDoubles(sum, 0.0) == 0 ? 0 : -10.0 * sum); } @@ -418,31 +414,31 @@ public class UnifiedGenotyperEngine { // the overall lod VariantContext vcOverall = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, vc.getAlternateAllele(0), false, model); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); - afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); - //double overallLog10PofNull = log10AlleleFrequencyPosteriors.get()[0]; - double overallLog10PofF = MathUtils.log10sumLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0], 1); + clearAFarray(AFresult.log10AlleleFrequencyLikelihoods); + clearAFarray(AFresult.log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), AFresult); + //double overallLog10PofNull = AFresult.log10AlleleFrequencyPosteriors[0]; + double overallLog10PofF = MathUtils.log10sumLog10(AFresult.log10AlleleFrequencyPosteriors[0], 0); //if ( DEBUG_SLOD ) System.out.println("overallLog10PofF=" + overallLog10PofF); // the forward lod VariantContext vcForward = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.FORWARD, vc.getAlternateAllele(0), false, model); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); - afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); - //double[] normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); - double forwardLog10PofNull = alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][0]; - double forwardLog10PofF = MathUtils.log10sumLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0], 1); + clearAFarray(AFresult.log10AlleleFrequencyLikelihoods); + clearAFarray(AFresult.log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), AFresult); + //double[] normalizedLog10Posteriors = MathUtils.normalizeFromLog10(AFresult.log10AlleleFrequencyPosteriors, true); + double forwardLog10PofNull = AFresult.log10PosteriorOfAFzero; + double forwardLog10PofF = MathUtils.log10sumLog10(AFresult.log10AlleleFrequencyPosteriors[0], 0); //if ( DEBUG_SLOD ) System.out.println("forwardLog10PofNull=" + forwardLog10PofNull + ", forwardLog10PofF=" + forwardLog10PofF); // the reverse lod VariantContext vcReverse = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.REVERSE, vc.getAlternateAllele(0), false, model); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyLikelihoods); - clearAFarray(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors); - afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), alleleFrequencyCalculationResult.get()); - //normalizedLog10Posteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get(), true); - double reverseLog10PofNull = alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0][0]; - double reverseLog10PofF = MathUtils.log10sumLog10(alleleFrequencyCalculationResult.get().log10AlleleFrequencyPosteriors[0], 1); + clearAFarray(AFresult.log10AlleleFrequencyLikelihoods); + clearAFarray(AFresult.log10AlleleFrequencyPosteriors); + afcm.get().getLog10PNonRef(vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), AFresult); + //normalizedLog10Posteriors = MathUtils.normalizeFromLog10(AFresult.log10AlleleFrequencyPosteriors, true); + double reverseLog10PofNull = AFresult.log10PosteriorOfAFzero; + double reverseLog10PofF = MathUtils.log10sumLog10(AFresult.log10AlleleFrequencyPosteriors[0], 0); //if ( DEBUG_SLOD ) System.out.println("reverseLog10PofNull=" + reverseLog10PofNull + ", reverseLog10PofF=" + reverseLog10PofF); double forwardLod = forwardLog10PofF + reverseLog10PofNull - overallLog10PofF; @@ -455,7 +451,8 @@ public class UnifiedGenotyperEngine { strandScore *= 10.0; //logger.debug(String.format("SLOD=%f", strandScore)); - attributes.put("SB", strandScore); + if ( !Double.isNaN(strandScore) ) + attributes.put("SB", strandScore); } // finish constructing the resulting VC @@ -478,6 +475,12 @@ public class UnifiedGenotyperEngine { return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } + private double[] generateNormalizedPosteriors(AlleleFrequencyCalculationResult AFresult, double[] normalizedPosteriors) { + normalizedPosteriors[0] = AFresult.log10PosteriorOfAFzero; + System.arraycopy(AFresult.log10AlleleFrequencyPosteriors[0], 0, normalizedPosteriors, 1, normalizedPosteriors.length-1); + return MathUtils.normalizeFromLog10(normalizedPosteriors); + } + private Map getFilteredAndStratifiedContexts(UnifiedArgumentCollection UAC, ReferenceContext refContext, AlignmentContext rawContext, final GenotypeLikelihoodsCalculationModel.Model model) { Map stratifiedContexts = null; 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 6041f80b8..7fde73f0a 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 @@ -115,7 +115,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testCallingParameters() { HashMap e = new HashMap(); e.put( "--min_base_quality_score 26", "7acb1a5aee5fdadb0cc0ea07a212efc6" ); - e.put( "--computeSLOD", "e9d23a08472e4e27b4f25e844f5bad57" ); + e.put( "--computeSLOD", "6172d2f3d370132f4c57a26aa94c256e" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -285,7 +285,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("98a4d1e1e0a363ba37518563ac6cbead")); + Arrays.asList("8bd5028cf294850b8a95b3c0af23d728")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -294,7 +294,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("915e7a3e7cbfd995dbc41fdd382d0d51")); + Arrays.asList("814dcd66950635a870602d90a1618cce")); executeTest("test MultiSample Phase1 indels with complicated records", spec4); } From c5ffe0ab0438a14d7e4ff90a2c443ba6dac4e989 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sun, 18 Dec 2011 00:31:47 -0500 Subject: [PATCH 313/380] No reason to sum the normalized posteriors array to get Pr(AF>0) given that we can just compute 1.0 - array[0]. Integration tests change only because of trivial precision artifacts for reference calls using EMIT_ALL_SITES. --- .../genotyper/UnifiedGenotyperEngine.java | 20 ++++++++----------- .../UnifiedGenotyperIntegrationTest.java | 4 ++-- 2 files changed, 10 insertions(+), 14 deletions(-) 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 533bf0c68..aa9be97a6 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 @@ -335,12 +335,8 @@ public class UnifiedGenotyperEngine { // calculate p(f>0): // because the likelihoods are marginalized for each alternate allele, we only need to compare log10PosteriorOfAFzero against any one of them - double[] normalizedPosteriors = generateNormalizedPosteriors(AFresult, posteriorsArray.get()); - // TODO -- why not just 1.0 - normalizedPosteriors[0]? - double sum = 0.0; - for (int i = 1; i <= N+1; i++) // N+1 because the cell at position zero in the original posteriors array is actually probability of non-ref (since it's marginalized over all alleles) - sum += normalizedPosteriors[i]; - double PofF = Math.min(sum, 1.0); // deal with precision errors + final double[] normalizedPosteriors = generateNormalizedPosteriors(AFresult, posteriorsArray.get()); + final double PofF = 1.0 - normalizedPosteriors[0]; double phredScaledConfidence; if ( !bestGuessIsRef || UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { @@ -350,7 +346,7 @@ public class UnifiedGenotyperEngine { } else { phredScaledConfidence = QualityUtils.phredScaleErrorRate(PofF); if ( Double.isInfinite(phredScaledConfidence) ) { - sum = AFresult.log10AlleleFrequencyPosteriors[0][0]; + double sum = AFresult.log10AlleleFrequencyPosteriors[0][0]; if ( sum == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) sum = 0.0; for (int i = 1; i <= N; i++) { @@ -370,7 +366,7 @@ public class UnifiedGenotyperEngine { } // strip out any alleles that aren't going to be used in the VariantContext - List myAlleles; + final List myAlleles; if ( UAC.GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.DISCOVERY ) { myAlleles = new ArrayList(vc.getAlleles().size()); myAlleles.add(vc.getReference()); @@ -384,8 +380,8 @@ public class UnifiedGenotyperEngine { } // start constructing the resulting VC - GenomeLoc loc = genomeLocParser.createGenomeLoc(vc); - VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), loc.getStop(), myAlleles); + final GenomeLoc loc = genomeLocParser.createGenomeLoc(vc); + final VariantContextBuilder builder = new VariantContextBuilder("UG_call", loc.getContig(), loc.getStart(), loc.getStop(), myAlleles); builder.log10PError(phredScaledConfidence/-10.0); if ( ! passesCallThreshold(phredScaledConfidence) ) builder.filters(filter); @@ -396,14 +392,14 @@ public class UnifiedGenotyperEngine { } // create the genotypes - GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse, myAlleles); + final GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse, myAlleles); // print out stats if we have a writer if ( verboseWriter != null && !limitedContext ) printVerboseData(refContext.getLocus().toString(), vc, PofF, phredScaledConfidence, normalizedPosteriors, model); // *** note that calculating strand bias involves overwriting data structures, so we do that last - HashMap attributes = new HashMap(); + final HashMap attributes = new HashMap(); // if the site was downsampled, record that fact if ( !limitedContext && rawContext.hasPileupBeenDownsampled() ) 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 7fde73f0a..e049af064 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 @@ -129,8 +129,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testOutputParameter() { HashMap e = new HashMap(); e.put( "-sites_only", "44f3b5b40e6ad44486cddfdb7e0bfcd8" ); - e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "274bd9d1b9c7857690fa5f0228ffc6d7" ); - e.put( "--output_mode EMIT_ALL_SITES", "594c6d3c48bbc73289de7727d768644d" ); + e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "d971d392956aea69c3707da64d24485b" ); + e.put( "--output_mode EMIT_ALL_SITES", "21993e71ca1a06a0d1f11d58e3cc26c3" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( From 76bd13a1ed5fe25f028cae8add881492ac7dd484 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Sun, 18 Dec 2011 01:13:49 -0500 Subject: [PATCH 315/380] Forgot to update the unit test --- .../walkers/genotyper/ExactAFCalculationModelUnitTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java index dec6ecb79..6f259f699 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/ExactAFCalculationModelUnitTest.java @@ -97,7 +97,12 @@ public class ExactAFCalculationModelUnitTest extends BaseTest { for ( int allele = 0; allele < cfg.numAltAlleles; allele++, nameIndex+=2 ) { int expectedAlleleCount = Integer.valueOf(cfg.name.substring(nameIndex, nameIndex+1)); int calculatedAlleleCount = MathUtils.maxElementIndex(result.log10AlleleFrequencyPosteriors[allele]); - Assert.assertEquals(calculatedAlleleCount, expectedAlleleCount); + + if ( result.log10AlleleFrequencyPosteriors[0][0] == AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED ) { + Assert.assertTrue(calculatedAlleleCount == expectedAlleleCount || result.log10AlleleFrequencyPosteriors[0][calculatedAlleleCount] < result.log10PosteriorOfAFzero); + } else { + Assert.assertEquals(calculatedAlleleCount, expectedAlleleCount); + } } } } From 953998dcd0d31280ea876cdcdcf46b0d66c6b16e Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Sun, 18 Dec 2011 14:38:59 -0500 Subject: [PATCH 316/380] Now that getSampleDB is public in the walker base class this override in VariantAnnotator isn't necessary. --- .../sting/gatk/walkers/annotator/VariantAnnotator.java | 5 ----- .../sting/gatk/walkers/variantutils/VariantsToTable.java | 6 +++--- 2 files changed, 3 insertions(+), 8 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 94902e828..4fa6d0bbf 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 @@ -194,11 +194,6 @@ public class VariantAnnotator extends RodWalker implements Ann System.exit(0); } - @Override - public SampleDB getSampleDB() { - return super.getSampleDB(); - } - /** * Prepare the output file and the list of available features. */ 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 f1f61d071..4b3aa4864 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 @@ -127,12 +127,12 @@ public class VariantsToTable extends RodWalker { * multi-allelic INFO field values can be lists of values. */ @Advanced - @Argument(fullName="keepMultiAllelic", shortName="KMA", doc="If provided, we will not require the site to be biallelic", required=false) - public boolean keepMultiAllelic = false; + @Argument(fullName="keepMultiAllelic", shortName="KMA", doc="If provided, we will not require the site to be biallelic", required=false) + public boolean keepMultiAllelic = false; @Hidden @Argument(fullName="logACSum", shortName="logACSum", doc="Log sum of AC instead of max value in case of multiallelic variants", required=false) - public boolean logACSum = false; + public boolean logACSum = false; /** * By default, this tool throws a UserException when it encounters a field without a value in some record. This From bc842ab3a5d60095aadff6bae4d271615d0ea294 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Sun, 18 Dec 2011 15:27:23 -0500 Subject: [PATCH 317/380] Adding option to VariantAnnotator to do strict allele matching when annotating with comp track concordance. --- .../gatk/walkers/annotator/VariantAnnotator.java | 4 ++++ .../walkers/annotator/VariantAnnotatorEngine.java | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 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 4fa6d0bbf..69560c7cb 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 @@ -170,6 +170,9 @@ public class VariantAnnotator extends RodWalker implements Ann @Argument(fullName="MendelViolationGenotypeQualityThreshold",shortName="mvq",required=false,doc="The genotype quality treshold in order to annotate mendelian violation ratio") public double minGenotypeQualityP = 0.0; + @Argument(fullName="requireStrictAlleleMatch", shortName="strict", doc="If provided only comp tracks that exactly match both reference and alternate alleles will be counted as concordant", required=false) + private boolean requireStrictAlleleMatch = false; + private VariantAnnotatorEngine engine; private Collection indelBufferContext; @@ -211,6 +214,7 @@ public class VariantAnnotator extends RodWalker implements Ann else engine = new VariantAnnotatorEngine(annotationGroupsToUse, annotationsToUse, annotationsToExclude, this, getToolkit()); engine.initializeExpressions(expressionsToUse); + engine.setRequireStrictAlleleMatch(requireStrictAlleleMatch); // setup the header fields // note that if any of the definitions conflict with our new ones, then we want to overwrite the old ones 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 d4442dc5d..98d2fe17b 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 @@ -47,9 +47,11 @@ public class VariantAnnotatorEngine { private List requestedGenotypeAnnotations; private List requestedExpressions = new ArrayList(); - private HashMap, String> dbAnnotations = new HashMap, String>(); - private AnnotatorCompatibleWalker walker; - private GenomeAnalysisEngine toolkit; + private final HashMap, String> dbAnnotations = new HashMap, String>(); + private final AnnotatorCompatibleWalker walker; + private final GenomeAnalysisEngine toolkit; + + private boolean requireStrictAlleleMatch = false; protected static class VAExpression { @@ -163,6 +165,10 @@ public class VariantAnnotatorEngine { return descriptions; } + public void setRequireStrictAlleleMatch( final boolean requireStrictAlleleMatch ) { + this.requireStrictAlleleMatch = requireStrictAlleleMatch; + } + public VariantContext annotateContext(RefMetaDataTracker tracker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { Map infoAnnotations = new LinkedHashMap(vc.getAttributes()); @@ -197,7 +203,7 @@ public class VariantAnnotatorEngine { } else { boolean overlapsComp = false; for ( VariantContext comp : tracker.getValues(dbSet.getKey(), ref.getLocus()) ) { - if ( !comp.isFiltered() ) { + if ( !comp.isFiltered() && ( !requireStrictAlleleMatch || comp.getAlleles().equals(vc.getAlleles()) ) ) { overlapsComp = true; break; } From 1ead00cac5d027b45633b58bb8701f07f57c7166 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Sun, 18 Dec 2011 19:04:26 -0500 Subject: [PATCH 318/380] New fork of SamFileHeaderMerger should be cached at the thread level to enable fast (and valid) thread lookups. --- .../gatk/datasources/reads/SAMDataSource.java | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) 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 75a851d65..2e243b847 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 @@ -100,11 +100,6 @@ public class SAMDataSource { */ private final Map readerPositions = new HashMap(); - /** - * Cached representation of the merged header used to generate a merging iterator. - */ - private final SamFileHeaderMerger headerMerger; - /** * The merged header. */ @@ -300,9 +295,8 @@ public class SAMDataSource { initializeReaderPositions(readers); - headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,readers.headers(),true); - mergedHeader = headerMerger.getMergedHeader(); - hasReadGroupCollisions = headerMerger.hasReadGroupCollisions(); + mergedHeader = readers.getMergedHeader(); + hasReadGroupCollisions = readers.hasReadGroupCollisions(); readProperties = new ReadProperties( samFiles, @@ -327,9 +321,9 @@ public class SAMDataSource { List readGroups = reader.getFileHeader().getReadGroups(); for(SAMReadGroupRecord readGroup: readGroups) { - if(headerMerger.hasReadGroupCollisions()) { - mappingToMerged.put(readGroup.getReadGroupId(),headerMerger.getReadGroupId(reader,readGroup.getReadGroupId())); - mergedToOriginalReadGroupMappings.put(headerMerger.getReadGroupId(reader,readGroup.getReadGroupId()),readGroup.getReadGroupId()); + if(hasReadGroupCollisions) { + mappingToMerged.put(readGroup.getReadGroupId(),readers.getReadGroupId(id,readGroup.getReadGroupId())); + mergedToOriginalReadGroupMappings.put(readers.getReadGroupId(id,readGroup.getReadGroupId()),readGroup.getReadGroupId()); } else { mappingToMerged.put(readGroup.getReadGroupId(),readGroup.getReadGroupId()); mergedToOriginalReadGroupMappings.put(readGroup.getReadGroupId(),readGroup.getReadGroupId()); @@ -562,7 +556,7 @@ public class SAMDataSource { */ private StingSAMIterator getIterator(SAMReaders readers, Shard shard, boolean enableVerification) { // Set up merging to dynamically merge together multiple BAMs. - MergingSamRecordIterator mergingIterator = new MergingSamRecordIterator(headerMerger,readers.values(),true); + MergingSamRecordIterator mergingIterator = readers.createMergingIterator(); for(SAMReaderID id: getReaderIDs()) { CloseableIterator iterator = null; @@ -707,6 +701,11 @@ public class SAMDataSource { * A collection of readers derived from a reads metadata structure. */ private class SAMReaders implements Iterable { + /** + * Cached representation of the merged header used to generate a merging iterator. + */ + private final SamFileHeaderMerger headerMerger; + /** * Internal storage for a map of id -> reader. */ @@ -798,6 +797,11 @@ public class SAMDataSource { } if ( totalNumberOfFiles > 0 ) logger.info(String.format("Done initializing BAM readers: total time %.2f", timer.getElapsedTime())); + + Collection headers = new LinkedList(); + for(SAMFileReader reader: readers.values()) + headers.add(reader.getFileHeader()); + headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,headers,true); } final private void printReaderPerformance(final int nExecutedTotal, @@ -814,7 +818,37 @@ public class SAMDataSource { nExecutedInTick, tickDurationInSec, nExecutedTotal, totalNumberOfFiles, totalTimeInSeconds, totalTimeInSeconds / 60, nTasksPerSecond, nRemaining, estTimeToComplete, estTimeToComplete / 60)); + } + /** + * Return the header derived from the merging of these BAM files. + * @return the merged header. + */ + public SAMFileHeader getMergedHeader() { + return headerMerger.getMergedHeader(); + } + + /** + * Do multiple read groups collide in this dataset? + * @return True if multiple read groups collide; false otherwis. + */ + public boolean hasReadGroupCollisions() { + return headerMerger.hasReadGroupCollisions(); + } + + /** + * Get the newly mapped read group ID for the given read group. + * @param readerID Reader for which to discern the transformed ID. + * @param originalReadGroupID Original read group. + * @return Remapped read group. + */ + public String getReadGroupId(final SAMReaderID readerID, final String originalReadGroupID) { + SAMFileHeader header = readers.get(readerID).getFileHeader(); + return headerMerger.getReadGroupId(header,originalReadGroupID); + } + + public MergingSamRecordIterator createMergingIterator() { + return new MergingSamRecordIterator(headerMerger,readers.values(),true); } /** @@ -866,25 +900,6 @@ public class SAMDataSource { public boolean isEmpty() { return readers.isEmpty(); } - - /** - * Gets all the actual readers out of this data structure. - * @return A collection of the readers. - */ - public Collection values() { - return readers.values(); - } - - /** - * Gets all the actual readers out of this data structure. - * @return A collection of the readers. - */ - public Collection headers() { - ArrayList headers = new ArrayList(readers.size()); - for (SAMFileReader reader : values()) - headers.add(reader.getFileHeader()); - return headers; - } } class ReaderInitializer implements Callable { From 5b678e3b94a086c40a8cd7baf18dba713d07fe50 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Sun, 18 Dec 2011 12:26:06 -0500 Subject: [PATCH 319/380] Remove ClippingOp UnitTests * all testing functionality is in the ReadClipperUnitTest, no need to double test. * class and package naming cleanup --- .../sting/gatk/walkers/ClipReadsWalker.java | 6 +- .../{clipreads => clipping}/ClippingOp.java | 2 +- .../ClippingRepresentation.java | 2 +- .../{clipreads => clipping}/ReadClipper.java | 2 +- .../ReadClipperTestUtils.java} | 8 ++- .../ReadClipperUnitTest.java | 24 +++---- .../utils/clipreads/ClippingOpUnitTest.java | 67 ------------------- 7 files changed, 23 insertions(+), 88 deletions(-) rename public/java/src/org/broadinstitute/sting/utils/{clipreads => clipping}/ClippingOp.java (99%) rename public/java/src/org/broadinstitute/sting/utils/{clipreads => clipping}/ClippingRepresentation.java (95%) rename public/java/src/org/broadinstitute/sting/utils/{clipreads => clipping}/ReadClipper.java (99%) rename public/java/test/org/broadinstitute/sting/utils/{clipreads/ClipReadsTestUtils.java => clipping/ReadClipperTestUtils.java} (98%) rename public/java/test/org/broadinstitute/sting/utils/{clipreads => clipping}/ReadClipperUnitTest.java (94%) delete mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java index d1148cbd5..ff3867120 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java @@ -38,9 +38,9 @@ import org.broadinstitute.sting.gatk.io.StingSAMFileWriter; import org.broadinstitute.sting.gatk.refdata.ReadMetaDataTracker; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.Utils; -import org.broadinstitute.sting.utils.clipreads.ClippingOp; -import org.broadinstitute.sting.utils.clipreads.ClippingRepresentation; -import org.broadinstitute.sting.utils.clipreads.ReadClipper; +import org.broadinstitute.sting.utils.clipping.ClippingOp; +import org.broadinstitute.sting.utils.clipping.ClippingRepresentation; +import org.broadinstitute.sting.utils.clipping.ReadClipper; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.sam.ReadUtils; diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java similarity index 99% rename from public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java rename to public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java index 0bb8c3e6f..f440eda5d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.utils.clipreads; +package org.broadinstitute.sting.utils.clipping; import com.google.java.contract.Requires; import net.sf.samtools.Cigar; diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingRepresentation.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java similarity index 95% rename from public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingRepresentation.java rename to public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java index d574ba2f0..c9d8730d1 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingRepresentation.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.utils.clipreads; +package org.broadinstitute.sting.utils.clipping; /** * How should we represent a clipped bases in a read? diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java similarity index 99% rename from public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java rename to public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java index e6b3ce929..6e3f37980 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.utils.clipreads; +package org.broadinstitute.sting.utils.clipping; import com.google.java.contract.Requires; import net.sf.samtools.CigarElement; diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperTestUtils.java similarity index 98% rename from public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java rename to public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperTestUtils.java index 048d9d8e0..18108e0a1 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClipReadsTestUtils.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperTestUtils.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.utils.clipreads; +package org.broadinstitute.sting.utils.clipping; import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; @@ -8,7 +8,9 @@ import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; -import java.util.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; /** * Created by IntelliJ IDEA. @@ -17,7 +19,7 @@ import java.util.*; * Time: 6:45 AM * To change this template use File | Settings | File Templates. */ -public class ClipReadsTestUtils { +public class ReadClipperTestUtils { //Should contain all the utils needed for tests to mass produce //reads, cigars, and other needed classes diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java similarity index 94% rename from public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java rename to public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java index fcde5f360..3b4b0abce 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java @@ -23,7 +23,7 @@ * THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.clipreads; +package org.broadinstitute.sting.utils.clipping; import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; @@ -50,13 +50,13 @@ public class ReadClipperUnitTest extends BaseTest { @BeforeClass public void init() { - cigarList = ClipReadsTestUtils.generateCigarList(maximumCigarSize); + cigarList = ReadClipperTestUtils.generateCigarList(maximumCigarSize); } @Test(enabled = true) public void testHardClipBothEndsByReferenceCoordinates() { for (Cigar cigar : cigarList) { - GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); int alnStart = read.getAlignmentStart(); int alnEnd = read.getAlignmentEnd(); int readLength = alnStart - alnEnd; @@ -71,7 +71,7 @@ public class ReadClipperUnitTest extends BaseTest { @Test(enabled = true) public void testHardClipByReadCoordinates() { for (Cigar cigar : cigarList) { - GATKSAMRecord read = ClipReadsTestUtils.makeReadFromCigar(cigar); + GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); int readLength = read.getReadLength(); for (int i=0; i %s", read.getCigarString(), clippedRead.getCigarString())); // check that everything else is still there diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java deleted file mode 100644 index dd674ff1c..000000000 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ClippingOpUnitTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.broadinstitute.sting.utils.clipreads; - -import org.broadinstitute.sting.BaseTest; -import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; - - -/** - * Created by IntelliJ IDEA. - * User: roger - * Date: 11/27/11 - * Time: 5:17 AM - * To change this template use File | Settings | File Templates. - */ -public class ClippingOpUnitTest extends BaseTest { - - ClippingOp clippingOp; - GATKSAMRecord read; - - @BeforeTest - public void init() { - read = ClipReadsTestUtils.makeRead(); - } - - @Test - private void testHardClip() { -// 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 -// testList.add(new TestParameter(0, 2, 3, 4, "3H1M"));//clip 3 bases at start -// testList.add(new TestParameter(1, 3, 0, 1, "1M3H"));//clip 3 bases at end -// -// for (TestParameter p : testList) { -// init(); -// clippingOp = new ClippingOp(p.inputStart, p.inputStop); -// logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.substringStart + "," + p.substringStop + "," + p.cigar); -// ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.HARDCLIP_BASES, read), -// ClipReadsTestUtils.BASES.substring(p.substringStart, p.substringStop).getBytes(), -// ClipReadsTestUtils.QUALS.substring(p.substringStart, p.substringStop).getBytes(), -// p.cigar); -// } - - } - - @Test - private void testSoftClip() { -// List testList = new LinkedList(); -// testList.add(new TestParameter(0, 0, -1, -1, "1S3M"));//clip 1 base at start -// testList.add(new TestParameter(3, 3, -1, -1, "3M1S"));//clip 1 base at end -// testList.add(new TestParameter(0, 1, -1, -1, "2S2M"));//clip 2 bases at start -// testList.add(new TestParameter(2, 3, -1, -1, "2M2S"));//clip 2 bases at end -// testList.add(new TestParameter(0, 2, -1, -1, "3S1M"));//clip 3 bases at start -// testList.add(new TestParameter(1, 3, -1, -1, "1M3S"));//clip 3 bases at end -// -// for (TestParameter p : testList) { -// init(); -// clippingOp = new ClippingOp(p.inputStart, p.inputStop); -// logger.warn("Testing Parameters: " + p.inputStart + "," + p.inputStop + "," + p.cigar); -// ClipReadsTestUtils.testBaseQualCigar(clippingOp.apply(ClippingRepresentation.SOFTCLIP_BASES, read), -// ClipReadsTestUtils.BASES.getBytes(), ClipReadsTestUtils.QUALS.getBytes(), p.cigar); -// } - - } -} From 3069a689fe7dcca14200a6ed1ba1fdff3a31dbf8 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 19 Dec 2011 10:04:33 -0500 Subject: [PATCH 321/380] Bug fix: if there are multiple records at a given position, it turns out that SelectVariants would drop all variants that follow after one that fails filters (instead of dropping just the failing one). Added an integration test to cover this case. --- .../walkers/variantutils/SelectVariants.java | 20 +++++++++++-------- .../SelectVariantsIntegrationTest.java | 13 ++++++++++++ 2 files changed, 25 insertions(+), 8 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 0af351750..6d94ffe6d 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 @@ -493,12 +493,12 @@ public class SelectVariants extends RodWalker implements TreeR if (DISCORDANCE_ONLY) { Collection compVCs = tracker.getValues(discordanceTrack, context.getLocation()); if (!isDiscordant(vc, compVCs)) - return 0; + continue; } if (CONCORDANCE_ONLY) { Collection compVCs = tracker.getValues(concordanceTrack, context.getLocation()); if (!isConcordant(vc, compVCs)) - return 0; + continue; } if (alleleRestriction.equals(NumberAlleleRestriction.BIALLELIC) && !vc.isBiallelic()) @@ -512,16 +512,20 @@ public class SelectVariants extends RodWalker implements TreeR VariantContext sub = subsetRecord(vc, samples); if ( (sub.isPolymorphicInSamples() || !EXCLUDE_NON_VARIANTS) && (!sub.isFiltered() || !EXCLUDE_FILTERED) ) { + boolean failedJexlMatch = false; for ( VariantContextUtils.JexlVCMatchExp jexl : jexls ) { if ( !VariantContextUtils.match(sub, jexl) ) { - return 0; + failedJexlMatch = true; + break; } } - if (SELECT_RANDOM_NUMBER) { - randomlyAddVariant(++variantNumber, sub); - } - else if (!SELECT_RANDOM_FRACTION || ( GenomeAnalysisEngine.getRandomGenerator().nextDouble() < fractionRandom)) { - vcfWriter.add(sub); + if ( !failedJexlMatch ) { + if (SELECT_RANDOM_NUMBER) { + randomlyAddVariant(++variantNumber, sub); + } + else if (!SELECT_RANDOM_FRACTION || ( GenomeAnalysisEngine.getRandomGenerator().nextDouble() < fractionRandom)) { + vcfWriter.add(sub); + } } } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java index 72a07bd0e..042de2a27 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariantsIntegrationTest.java @@ -116,6 +116,19 @@ public class SelectVariantsIntegrationTest extends WalkerTest { executeTest("testUsingDbsnpName--" + testFile, spec); } + @Test + public void testMultipleRecordsAtOnePosition() { + String testFile = validationDataLocation + "selectVariants.onePosition.vcf"; + + WalkerTestSpec spec = new WalkerTestSpec( + "-T SelectVariants -R " + b36KGReference + " -select 'KG_FREQ < 0.5' --variant " + testFile + " -o %s -NO_HEADER", + 1, + Arrays.asList("20b52c96f5c48258494d072752b53693") + ); + + executeTest("testMultipleRecordsAtOnePositionFirstIsFiltered--" + testFile, spec); + } + @Test public void testParallelization() { String testfile = validationDataLocation + "test.filtered.maf_annotated.vcf"; From 5383c506540ca58827d5d798d3390780d5e57512 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 19 Dec 2011 10:08:38 -0500 Subject: [PATCH 322/380] Protect ourselves when iteration is present but there's only a single iteration in queueJobReport.R --- .../broadinstitute/sting/queue/util/queueJobReport.R | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R index aff783b8e..d5ee3626f 100644 --- a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R +++ b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R @@ -12,7 +12,7 @@ if ( onCMDLine ) { inputFileName = args[1] outputPDF = args[2] } else { - inputFileName = "Q-8271@gsa2.jobreport.txt" + inputFileName = "Q-26618@gsa4.jobreport.txt" #inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/Q-25718@node1149.jobreport.txt" #inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/rodPerformanceGoals/history/report.082711.txt" outputPDF = NA @@ -129,9 +129,11 @@ plotGroup <- function(groupTable) { # as above, but averaging over all iterations groupAnnotationsNoIteration = setdiff(groupAnnotations, "iteration") if ( dim(sub)[1] > 1 ) { - sum = cast(melt(sub, id.vars=groupAnnotationsNoIteration, measure.vars=c("runtime")), ... ~ ., fun.aggregate=c(mean, sd)) - textplot(as.data.frame(sum), show.rownames=F) - title(paste("Job summary for", name, "averaging over all iterations"), cex=3) + try({ # need a try here because we will fail to reduce when there's just a single iteration + sum = cast(melt(sub, id.vars=groupAnnotationsNoIteration, measure.vars=c("runtime")), ... ~ ., fun.aggregate=c(mean, sd)) + textplot(as.data.frame(sum), show.rownames=F) + title(paste("Job summary for", name, "averaging over all iterations"), cex=3) + }, silent=T) } } @@ -193,6 +195,7 @@ plotJobsGantt(gatkReportData, F, F) plotProgressByTime(gatkReportData) plotTimeByHost(gatkReportData) for ( group in gatkReportData ) { + print(group) plotGroup(group) } From 5fd19ae734b8cc94ee276c8909b02b2e47c787a1 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 19 Dec 2011 10:19:00 -0500 Subject: [PATCH 323/380] Commented exactly how the results are represented from the exact model so developers can know how to use them. --- .../genotyper/AlleleFrequencyCalculationResult.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java index cc72fde11..ed80dce0d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java @@ -34,10 +34,17 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; */ public class AlleleFrequencyCalculationResult { - // note that the cell at position zero in the likelihoods/posteriors array is actually probability of non-ref (since it's marginalized over all alleles) + // IMPORTANT NOTE: + // These 2 arrays are intended to contain the likelihoods/posterior probabilities for each alternate allele over each possible frequency (from 0 to 2N). + // For any given alternate allele and frequency, the likelihoods are marginalized over values for all other alternate alleles. What this means is that + // the likelihoods at cell index zero (AF=0) in the array is actually that of the site's being polymorphic (because although this alternate allele may + // be at AF=0, it is marginalized over all other alternate alleles which are not necessarily at AF=0). + // In the bi-allelic case (where there are no other alternate alleles over which to marginalize), + // the value at cell index zero will be equal to AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED. final double[][] log10AlleleFrequencyLikelihoods; final double[][] log10AlleleFrequencyPosteriors; + // These 2 variables are intended to contain the likelihood/posterior probability for the site's being monomorphic (i.e. AF=0 for all alternate alleles) double log10LikelihoodOfAFzero = 0.0; double log10PosteriorOfAFzero = 0.0; From 16cc2b864e3098e96eaff5727a5583775e392442 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 19 Dec 2011 13:41:47 +0100 Subject: [PATCH 324/380] - Corrected bug causing cases where both parents are HET to be accounted twice in the TDT calculation - Adapted TDT Integration test to corrected version of TDT Signed-off-by: Ryan Poplin --- .../annotator/TransmissionDisequilibriumTest.java | 15 +++++++-------- .../VariantAnnotatorIntegrationTest.java | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java index 6cc8923e8..ecdde1e4f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/TransmissionDisequilibriumTest.java @@ -63,27 +63,26 @@ public class TransmissionDisequilibriumTest extends InfoFieldAnnotation implemen // Following derivation in http://en.wikipedia.org/wiki/Transmission_disequilibrium_test#A_modified_version_of_the_TDT private double calculateTDT( final VariantContext vc, final Set triosToTest ) { - final double nABGivenABandBB = calculateNChildren(vc, triosToTest, HET, HET, HOM); - final double nBBGivenABandBB = calculateNChildren(vc, triosToTest, HOM, HET, HOM); + final double nABGivenABandBB = calculateNChildren(vc, triosToTest, HET, HET, HOM) + calculateNChildren(vc, triosToTest, HET, HOM, HET); + final double nBBGivenABandBB = calculateNChildren(vc, triosToTest, HOM, HET, HOM) + calculateNChildren(vc, triosToTest, HOM, HOM, HET); final double nAAGivenABandAB = calculateNChildren(vc, triosToTest, REF, HET, HET); final double nBBGivenABandAB = calculateNChildren(vc, triosToTest, HOM, HET, HET); - final double nAAGivenAAandAB = calculateNChildren(vc, triosToTest, REF, REF, HET); - final double nABGivenAAandAB = calculateNChildren(vc, triosToTest, HET, REF, HET); + final double nAAGivenAAandAB = calculateNChildren(vc, triosToTest, REF, REF, HET) + calculateNChildren(vc, triosToTest, REF, HET, REF); + final double nABGivenAAandAB = calculateNChildren(vc, triosToTest, HET, REF, HET) + calculateNChildren(vc, triosToTest, HET, HET, REF); final double numer = (nABGivenABandBB - nBBGivenABandBB) + 2.0 * (nAAGivenABandAB - nBBGivenABandAB) + (nAAGivenAAandAB - nABGivenAAandAB); final double denom = (nABGivenABandBB + nBBGivenABandBB) + 4.0 * (nAAGivenABandAB + nBBGivenABandAB) + (nAAGivenAAandAB + nABGivenAAandAB); return (numer * numer) / denom; } - private double calculateNChildren( final VariantContext vc, final Set triosToTest, final int childIdx, final int momIdx, final int dadIdx ) { - final double likelihoodVector[] = new double[triosToTest.size() * 2]; + private double calculateNChildren( final VariantContext vc, final Set triosToTest, final int childIdx, final int parent1Idx, final int parent2Idx ) { + final double likelihoodVector[] = new double[triosToTest.size()]; int iii = 0; for( final Sample child : triosToTest ) { final double[] momGL = vc.getGenotype(child.getMaternalID()).getLikelihoods().getAsVector(); final double[] dadGL = vc.getGenotype(child.getPaternalID()).getLikelihoods().getAsVector(); final double[] childGL = vc.getGenotype(child.getID()).getLikelihoods().getAsVector(); - likelihoodVector[iii++] = momGL[momIdx] + dadGL[dadIdx] + childGL[childIdx]; - likelihoodVector[iii++] = momGL[dadIdx] + dadGL[momIdx] + childGL[childIdx]; + likelihoodVector[iii++] = momGL[parent1Idx] + dadGL[parent2Idx] + childGL[childIdx]; } return MathUtils.sumLog10(likelihoodVector); 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 ffb9aedcc..245fda3c7 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 @@ -171,7 +171,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { @Test public void testTDTAnnotation() { - final String MD5 = "9fe37b61aab695ad47ce3c587148e91f"; + final String MD5 = "204e67536a17af7eaa6bf0a910818997"; WalkerTestSpec spec = new WalkerTestSpec( "-T VariantAnnotator -R " + b37KGReference + " -A TransmissionDisequilibriumTest --variant:vcf " + validationDataLocation + "ug.random50000.subset300bp.chr1.family.vcf" + " -L " + validationDataLocation + "ug.random50000.subset300bp.chr1.family.vcf -NO_HEADER -ped " + validationDataLocation + "ug.random50000.family.ped -o %s", 1, From 69661da37d0688c79ac718d4600fadd7eaed19d5 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Mon, 19 Dec 2011 10:57:28 -0500 Subject: [PATCH 327/380] Moving ValidationSiteSelector to validation package in public under my ownership. JunctionGenotyper added and modified several times, this commit is due to merging conflix fixes. --- .../genotyper/UnifiedGenotyperEngine.java | 6 +- .../FrequencyModeSelector.java | 47 +++ .../GLBasedSampleSelector.java | 43 +++ .../GTBasedSampleSelector.java | 51 +++ .../validationsiteselector/GenomeEvent.java | 74 +++++ .../KeepAFSpectrumFrequencySelector.java | 185 +++++++++++ .../NullSampleSelector.java | 40 +++ .../SampleSelector.java | 40 +++ .../UniformSamplingFrequencySelector.java | 86 +++++ .../ValidationSiteSelectorWalker.java | 296 ++++++++++++++++++ .../utils/codecs/refseq/RefSeqFeature.java | 31 +- .../sting/utils/codecs/table/TableCodec.java | 1 - 12 files changed, 894 insertions(+), 6 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GenomeEvent.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java 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 7e5f388b2..dc85e95e4 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 @@ -776,9 +776,9 @@ public class UnifiedGenotyperEngine { * * @return genotypes */ - public GenotypesContext assignGenotypes(final VariantContext vc, - final boolean[] allelesToUse, - final List newAlleles) { + public static GenotypesContext assignGenotypes(final VariantContext vc, + final boolean[] allelesToUse, + final List newAlleles) { // the no-called genotypes final GenotypesContext GLs = vc.getGenotypes(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java new file mode 100644 index 000000000..5c0d6cb87 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java @@ -0,0 +1,47 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; + +import java.util.ArrayList; +import java.util.HashMap; + +public abstract class FrequencyModeSelector implements Cloneable{ + + protected GenomeLocParser parser; + + protected FrequencyModeSelector(GenomeLocParser parser) { + this.parser = parser; + } + protected void logCurrentSiteData(VariantContext vc, VariantContext subVC) { + logCurrentSiteData(vc, subVC, false, false); + } + protected abstract void logCurrentSiteData(VariantContext vc, VariantContext subVC, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC); + protected abstract ArrayList selectValidationSites(int numValidationSites); + +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java new file mode 100644 index 000000000..d229e718e --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java @@ -0,0 +1,43 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.util.TreeSet; + + +public class GLBasedSampleSelector extends SampleSelector { + public GLBasedSampleSelector(TreeSet sm) { + super(sm); + } + + public VariantContext subsetSiteToSamples(VariantContext vc) { + /* todo - Look at sample array, and create a new vc with samples for which GL's indicate they should be included. + For example, include all samples (and corresponding genotypes) whose GL's are such that argmax(GL) = HET or HOMVAR. */ + throw new ReviewedStingException("GLBasedSampleSelector not implemented yet!"); + //return true; + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java new file mode 100644 index 000000000..44fca71fb --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java @@ -0,0 +1,51 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.utils.variantcontext.Genotype; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + + +public class GTBasedSampleSelector extends SampleSelector{ + public GTBasedSampleSelector(TreeSet sm) { + super(sm); + } + + public VariantContext subsetSiteToSamples(VariantContext vc) { + // Super class already defined initialization which filled data structure "samples" with desired samples. + // We only need to check if current vc if polymorphic in that set of samples + + if ( samples == null || samples.isEmpty() ) + return vc; + + return vc.subContextFromSamples(samples, vc.getAlleles()); + + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GenomeEvent.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GenomeEvent.java new file mode 100644 index 000000000..af6a52002 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GenomeEvent.java @@ -0,0 +1,74 @@ +/* + * 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.validation.validationsiteselector; + +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.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.variantcontext.Allele; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextBuilder; + +import java.util.HashMap; +import java.util.List; + + +public class GenomeEvent implements Comparable { + final protected GenomeLoc loc; + /** A set of the alleles segregating in this context */ + final protected List alleles; + final protected Byte refBase; +// final protected HashMap attributes; + + public GenomeEvent(GenomeLocParser parser, final String contig, final int start, final int stop, final List alleles, HashMap attributes, + byte base) { + this.loc = parser.createGenomeLoc(contig, start, stop); + this.alleles = alleles; + this.refBase = base; +// this.attributes = attributes; + } + + // Routine to compare two variant contexts (useful to sort collections of vc's). + // By default, we want to sort first by contig, then by start location + + public GenomeLoc getGenomeLoc() { + return loc; + } + public int compareTo(final Object o) { + if (!(o instanceof GenomeEvent)) + throw new ReviewedStingException("BUG: comparing variant context with non-VC object"); + + GenomeEvent otherEvent = (GenomeEvent)o; + + return loc.compareTo(otherEvent.getGenomeLoc()); + } + + public VariantContext createVariantContextFromEvent() { + return new VariantContextBuilder("event", loc.getContig(), loc.getStart(), loc.getStop(), alleles) + .log10PError(0.0).referenceBaseForIndel(refBase).make(); + + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java new file mode 100644 index 000000000..739cfcab4 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java @@ -0,0 +1,185 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + + +public class KeepAFSpectrumFrequencySelector extends FrequencyModeSelector { + + private static final boolean DEBUG = true; + + private int NUM_BINS = 20; + + private int[] preSampleSelectionHistogram; + private int numTotalSites = 0; + private int[] postSampleSelectionHistogram; + private int numSampleSelectedSites = 0; + private ArrayList[] binnedEventArray; + + public KeepAFSpectrumFrequencySelector(int numBins, GenomeLocParser parser) { + super(parser); + NUM_BINS = numBins; + // initialize arrays dependent on NUM_BINS + binnedEventArray = new ArrayList[NUM_BINS]; + + for (int k=0; k < NUM_BINS; k++) + binnedEventArray[k] = new ArrayList(); + + preSampleSelectionHistogram = new int[NUM_BINS]; + postSampleSelectionHistogram = new int[NUM_BINS]; + } + + public void logCurrentSiteData(VariantContext vc, VariantContext subVC, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC) { + + // this method is called for every variant of a selected type, regardless of whether it will be selectable or not + // get AC,AF,AN attributes from vc + HashMap attributes = new HashMap(); + double[] afArray = null; + + if (vc.hasGenotypes() && !IGNORE_GENOTYPES) { + // recompute AF,AC,AN based on genotypes: + // todo - - maybe too inefficient?? + VariantContextUtils.calculateChromosomeCounts(vc, attributes, false); + afArray = new double[] {Double.valueOf((String)attributes.get(VCFConstants.ALLELE_FREQUENCY_KEY))}; + } else { + // sites-only vc or we explicitly tell to ignore genotypes; we trust the AF field if present + if ( vc.hasAttribute(VCFConstants.ALLELE_FREQUENCY_KEY) ) { + String afo = vc.getAttributeAsString(VCFConstants.ALLELE_FREQUENCY_KEY, null); + + if (afo.contains(",")) { + String[] afs = afo.split(","); + afs[0] = afs[0].substring(1,afs[0].length()); + afs[afs.length-1] = afs[afs.length-1].substring(0,afs[afs.length-1].length()-1); + + afArray = new double[afs.length]; + + for (int k=0; k < afArray.length; k++) + afArray[k] = Double.valueOf(afs[k]); + } + else + afArray = new double[] {Double.valueOf(afo)}; + } + } + + + if (afArray == null ) + return; + + double af0 = MathUtils.arrayMax(afArray); + + int binIndex = (NUM_BINS-1) - (int) Math.floor(((1.0-af0)*NUM_BINS)); + // deal with round-off issue: low-AC sites with large samples can have AF rounded down to 0.000 + if (binIndex < 0) + binIndex = 0; +// System.out.format("Pre:%4.4f %d\n",af0, binIndex); + preSampleSelectionHistogram[binIndex]++; + numTotalSites++; + + // now process VC subsetted to samples of interest + if (!subVC.isPolymorphicInSamples() && !IGNORE_POLYMORPHIC) + return; + + //System.out.format("Post:%4.4f %d\n",af0, binIndex); + postSampleSelectionHistogram[binIndex]++; + numSampleSelectedSites++; + + // create bare-bones event and log in corresponding bin + // attributes contains AC,AF,AN pulled from original vc, and we keep them here and log in output file for bookkeeping purposes + GenomeEvent event = new GenomeEvent(parser, vc.getChr(), vc.getStart(), vc.getEnd(),vc.getAlleles(), attributes, vc.getReferenceBaseForIndel()); + + binnedEventArray[binIndex].add(event); + + } + + public ArrayList selectValidationSites(int numValidationSites) { + // number of sites to choose at random for each frequency bin = #desired validation sites/# total sites * #sites in original bin + int[] sitesToChoosePerBin = new int[NUM_BINS]; + int totalSites = 0; + for (int k=0; k < NUM_BINS; k++) { + int sites = (int)Math.round((double)numValidationSites * preSampleSelectionHistogram[k]/ (double)numTotalSites); + sitesToChoosePerBin[k] = sites; + totalSites += sites; + } + + // deal with rounding artifacts + while (totalSites > numValidationSites) { + // take off one from randomly selected bin + int k= GenomeAnalysisEngine.getRandomGenerator().nextInt(NUM_BINS); + sitesToChoosePerBin[k]--; + totalSites--; + } + while (totalSites < numValidationSites) { + // take off one from randomly selected bin + int k= GenomeAnalysisEngine.getRandomGenerator().nextInt( NUM_BINS); + sitesToChoosePerBin[k]++; + totalSites++; + } + + if (DEBUG) { + System.out.println("sitesToChoosePerBin:"); + for (int k=0; k < NUM_BINS; k++) + System.out.format("%d ", sitesToChoosePerBin[k]); + System.out.println(); + + System.out.println("preSampleSelectionHistogram:"); + for (int k=0; k < NUM_BINS; k++) + System.out.format("%d ", preSampleSelectionHistogram[k]); + System.out.println(); + + System.out.println("postSampleSelectionHistogram:"); + for (int k=0; k < NUM_BINS; k++) + System.out.format("%d ", postSampleSelectionHistogram[k]); + System.out.println(); + + } + + // take randomly sitesToChoosePerBin[k] elements from each bin + ArrayList selectedEvents = new ArrayList(); + + for (int k=0; k < NUM_BINS; k++) { + selectedEvents.addAll(MathUtils.randomSubset(binnedEventArray[k], sitesToChoosePerBin[k])); + } + + Collections.sort(selectedEvents); + + // now convert to VC + ArrayList selectedSites = new ArrayList(); + for (GenomeEvent event : selectedEvents) + selectedSites.add(event.createVariantContextFromEvent()); + + return selectedSites; + + } + +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java new file mode 100644 index 000000000..35b8893c2 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java @@ -0,0 +1,40 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import java.util.TreeSet; + + +public class NullSampleSelector extends SampleSelector{ + public NullSampleSelector(TreeSet sm) { + super(sm); + } + + public VariantContext subsetSiteToSamples(VariantContext vc) { + return vc; + + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java new file mode 100644 index 000000000..e75fcef5d --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java @@ -0,0 +1,40 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import java.util.TreeSet; + + +public abstract class SampleSelector implements Cloneable { + + TreeSet samples; + protected SampleSelector(TreeSet sm) { + samples = new TreeSet(sm); + } + + protected abstract VariantContext subsetSiteToSamples(VariantContext vc); + + +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java new file mode 100644 index 000000000..506a6b2e4 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java @@ -0,0 +1,86 @@ +/* + * 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.validation.validationsiteselector; + +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +public class UniformSamplingFrequencySelector extends FrequencyModeSelector { + private ArrayList binnedEventArray; + + public UniformSamplingFrequencySelector(GenomeLocParser parser) { + super(parser); + binnedEventArray = new ArrayList(); + + } + + public void logCurrentSiteData(VariantContext vc, VariantContext subVC, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC) { + HashMap attributes = new HashMap(); + + + if (vc.hasGenotypes() && !IGNORE_GENOTYPES) { + // recompute AF,AC,AN based on genotypes: + VariantContextUtils.calculateChromosomeCounts(vc, attributes, false); + if (!subVC.isPolymorphicInSamples() && !IGNORE_POLYMORPHIC) + return; + } else { + if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) { + int ac = vc.getAttributeAsInt(VCFConstants.ALLELE_COUNT_KEY, 0); + if (ac == 0) return; // site not polymorphic + } + else + return; + + } + // create bare-bones event and log in corresponding bin + // attributes contains AC,AF,AN pulled from original vc, and we keep them here and log in output file for bookkeeping purposes + GenomeEvent event = new GenomeEvent(parser, vc.getChr(), vc.getStart(), vc.getEnd(),vc.getAlleles(), attributes, vc.getReferenceBaseForIndel()); + binnedEventArray.add(event); + + } + + public ArrayList selectValidationSites(int numValidationSites) { + + // take randomly sitesToChoosePerBin[k] elements from each bin + ArrayList selectedEvents = new ArrayList(); + + selectedEvents.addAll(MathUtils.randomSubset(binnedEventArray, numValidationSites)); + + Collections.sort(selectedEvents); + + // now convert to VC + ArrayList selectedSites = new ArrayList(); + for (GenomeEvent event : selectedEvents) + selectedSites.add(event.createVariantContextFromEvent()); + + return selectedSites; + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java new file mode 100644 index 000000000..10d2f639c --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java @@ -0,0 +1,296 @@ +/* + * 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.validation.validationsiteselector; + +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.RodWalker; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.SampleUtils; +import org.broadinstitute.sting.utils.codecs.vcf.*; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; + +import java.io.File; +import java.util.*; + + +/** + * Randomly selects VCF records according to specified options. + * + *

+ * ValidationSiteSelectorWalker is intended for use in experiments where we sample data randomly from a set of variants, for example + * in order to choose sites for a follow-up validation study. + * + * Sites are selected randomly but within certain restrictions. There are two main sources of restrictions + * a) Sample restrictions. A user can specify a set of samples, and we will only consider sites which are polymorphic within such given sample subset. + * These sample restrictions can be given as a set of individual samples, a text file (each line containing a sample name), or a regular expression. + * A user can additionally specify whether samples will be considered based on their genotypes (a non-reference genotype means that such sample is polymorphic in that variant, + * and hence that variant will be considered for inclusion in set), or based on their PLs. + * b) A user can additionally specify a sampling method based on allele frequency. Two sampling methods are currently supported. + * 1. Uniform sampling will just sample uniformly from variants polymorphic in selected samples. + * 2. Sampling based on Allele Frequency spectrum will ensure that output sites have the same AF distribution as the input set. + * + * User can additionally restrict output to a particular type of variant (SNP, Indel, etc.) + * + *

Input

+ *

+ * One or more variant sets to choose from. + *

+ * + *

Output

+ *

+ * A sites-only VCF with the desired number of randomly selected sites. + *

+ * + *

Examples

+ *
+ * java -Xmx2g -jar GenomeAnalysisTK.jar \
+ *   -R ref.fasta \
+ *   -T ValidationSiteSelectorWalker \
+ *   --variant input1.vcf \
+ *   --variant input2.vcf \
+ *   -sn NA12878 \
+ *   -o output.vcf \
+ *   --numValidationSites 200   \
+ *   -sampleMode  POLY_BASED_ON_GT \
+ *   -freqMode KEEP_AF_SPECTRUM
+ *
+ * java -Xmx2g -jar GenomeAnalysisTK.jar \
+ *   -R ref.fasta \
+ *   -T ValidationSiteSelectorWalker \
+ *   --variant:foo input1.vcf \
+ *   --variant:bar input2.vcf \
+ *   --numValidationSites 200 \
+ *   -sf samples.txt \
+ *   -o output.vcf \
+ *   -sampleMode  POLY_BASED_ON_GT \
+  *   -freqMode UNIFORM
+ *   -selectType INDEL
+ * 
+ * + */ +public class ValidationSiteSelectorWalker extends RodWalker { + + public enum AF_COMPUTATION_MODE { + KEEP_AF_SPECTRUM, + UNIFORM + } + + public enum SAMPLE_SELECTION_MODE { + NONE, + POLY_BASED_ON_GT, + POLY_BASED_ON_GL + } + + @Input(fullName="variant", shortName = "V", doc="Input VCF file, can be specified multiple times", required=true) + public List> variants; + + @Output(doc="File to which variants should be written",required=true) + protected VCFWriter vcfWriter = null; + + @Argument(fullName="sample_name", shortName="sn", doc="Include genotypes from this sample. Can be specified multiple times", required=false) + public Set sampleNames = new HashSet(0); + + @Argument(fullName="sample_expressions", shortName="se", doc="Regular expression to select many samples from the ROD tracks provided. Can be specified multiple times", required=false) + public Set sampleExpressions ; + + @Input(fullName="sample_file", shortName="sf", doc="File containing a list of samples (one per line) to include. Can be specified multiple times", required=false) + public Set sampleFiles; + + @Argument(fullName="sampleMode", shortName="sampleMode", doc="Sample selection mode", required=false) + private SAMPLE_SELECTION_MODE sampleMode = SAMPLE_SELECTION_MODE.NONE; + + @Argument(fullName="numValidationSites", shortName="numSites", doc="Number of output validation sites", required=true) + private int numValidationSites; + + @Argument(fullName="includeFilteredSites", shortName="ifs", doc="If true, will include filtered sites in set to choose variants from", required=false) + private boolean INCLUDE_FILTERED_SITES = false; + + @Argument(fullName="ignoreGenotypes", shortName="ignoreGenotypes", doc="If true, will ignore genotypes in VCF, will take AC,AF from annotations and will make no sample selection", required=false) + private boolean IGNORE_GENOTYPES = false; + + @Argument(fullName="ignorePolymorphicStatus", shortName="ignorePolymorphicStatus", doc="If true, will ignore polymorphic status in VCF, and will take VCF record directly without pre-selection", required=false) + private boolean IGNORE_POLYMORPHIC = false; + + @Hidden + @Argument(fullName="numFrequencyBins", shortName="numBins", doc="Number of frequency bins if we're to match AF distribution", required=false) + private int numFrequencyBins = 20; + + /** + * This argument selects allele frequency selection mode: + * KEEP_AF_SPECTRUM will choose variants so that the resulting allele frequency spectrum matches as closely as possible the input set + * UNIFORM will choose variants uniformly without regard to their allele frequency. + * + */ + @Argument(fullName="frequencySelectionMode", shortName="freqMode", doc="Allele Frequency selection mode", required=false) + private AF_COMPUTATION_MODE freqMode = AF_COMPUTATION_MODE.KEEP_AF_SPECTRUM; + + /** + * This argument selects particular kinds of variants out of a list. If left empty, there is no type selection and all variant types are considered for other selection criteria. + * When specified one or more times, a particular type of variant is selected. + * + */ + @Argument(fullName="selectTypeToInclude", shortName="selectType", doc="Select only a certain type of variants from the input file. Valid types are INDEL, SNP, MIXED, MNP, SYMBOLIC, NO_VARIATION. Can be specified multiple times", required=false) + private List TYPES_TO_INCLUDE = new ArrayList(); + + + private TreeSet samples = new TreeSet(); + SampleSelector sampleSelector = null; + FrequencyModeSelector frequencyModeSelector = null; + private ArrayList selectedTypes = new ArrayList(); + + public void initialize() { + // Get list of samples to include in the output + Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit()); + TreeSet vcfSamples = new TreeSet(SampleUtils.getSampleList(vcfRods, VariantContextUtils.GenotypeMergeType.REQUIRE_UNIQUE)); + + Collection samplesFromFile = SampleUtils.getSamplesFromFiles(sampleFiles); + Collection samplesFromExpressions = SampleUtils.matchSamplesExpressions(vcfSamples, sampleExpressions); + + // first, add any requested samples + samples.addAll(samplesFromFile); + samples.addAll(samplesFromExpressions); + samples.addAll(sampleNames); + + // if none were requested, we want all of them + if ( samples.isEmpty() ) { + samples.addAll(vcfSamples); + + } + + sampleSelector = getSampleSelectorObject(sampleMode, samples); + + // initialize frequency mode selector + frequencyModeSelector = getFrequencyModeSelectorObject(freqMode, getToolkit().getGenomeLocParser()); + + // if user specified types to include, add these, otherwise, add all possible variant context types to list of vc types to include + if (TYPES_TO_INCLUDE.isEmpty()) { + + for (VariantContext.Type t : VariantContext.Type.values()) + selectedTypes.add(t); + + } + else { + for (VariantContext.Type t : TYPES_TO_INCLUDE) + selectedTypes.add(t); + + } + + Set headerLines = new HashSet(); + headerLines.add(new VCFHeaderLine("source", "ValidationSiteSelector")); + vcfWriter.writeHeader(new VCFHeader(headerLines)); + + } + + + @Override + public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + if ( tracker == null ) + return 0; + + Collection vcs = tracker.getValues(variants, context.getLocation()); + + if ( vcs == null || vcs.size() == 0) { + return 0; + } + + + for (VariantContext vc : vcs) { + if (!selectedTypes.contains(vc.getType())) + continue; + + // skip if site isn't polymorphic and if user didn't request to ignore polymorphic status + if (!vc.isPolymorphicInSamples() && !IGNORE_POLYMORPHIC) + continue; + + if (!INCLUDE_FILTERED_SITES && vc.filtersWereApplied() && vc.isFiltered()) + continue; + + + // do anything required by frequency selector before we select for samples + VariantContext subVC; + if (samples.isEmpty()) + subVC = vc; + else + subVC = sampleSelector.subsetSiteToSamples(vc); + frequencyModeSelector.logCurrentSiteData(vc, subVC, IGNORE_GENOTYPES, IGNORE_POLYMORPHIC); + } + return 1; + } + + @Override + public Integer reduceInit() { return 0; } + + @Override + public Integer reduce(Integer value, Integer sum) { return value + sum; } + + public void onTraversalDone(Integer result) { + logger.info("Outputting validation sites..."); + ArrayList selectedSites = frequencyModeSelector.selectValidationSites(numValidationSites); + + for (VariantContext vc : selectedSites) { + vcfWriter.add(vc); + } + logger.info(result + " records processed."); + + } + + private SampleSelector getSampleSelectorObject(SAMPLE_SELECTION_MODE sampleMode, TreeSet samples) { + SampleSelector sm; + switch ( sampleMode ) { + case POLY_BASED_ON_GL: + sm = new GLBasedSampleSelector(samples); + break; + case POLY_BASED_ON_GT: + sm = new GTBasedSampleSelector(samples); + break; + case NONE: + sm = new NullSampleSelector(samples); + break; + default: + throw new IllegalArgumentException("Unsupported Sample Selection Mode: " + sampleMode); + } + + return sm; + } + + private FrequencyModeSelector getFrequencyModeSelectorObject (AF_COMPUTATION_MODE freqMode, GenomeLocParser parser) { + FrequencyModeSelector fm; + + switch (freqMode) { + case KEEP_AF_SPECTRUM: + fm = new KeepAFSpectrumFrequencySelector(numFrequencyBins, parser); + break; + case UNIFORM: + fm = new UniformSamplingFrequencySelector(parser); + break; + default: throw new IllegalArgumentException("Unexpected Frequency Selection Mode: "+ freqMode); + + } + return fm; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqFeature.java b/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqFeature.java index c04ca8592..a86d4781f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqFeature.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqFeature.java @@ -6,8 +6,7 @@ import org.broadinstitute.sting.gatk.refdata.utils.RODRecordList; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * the ref seq feature @@ -111,6 +110,34 @@ public class RefSeqFeature implements Transcript, Feature { return overlapString.toString(); } + ArrayList exonInRefOrderCache = null; + + public Integer getSortedOverlapInteger(GenomeLoc position) { + int exonNo = -1; + ArrayList exonsInReferenceOrder = exonInRefOrderCache != null ? exonInRefOrderCache : new ArrayList(exons); + if ( exonInRefOrderCache == null ) { + Collections.sort(exonsInReferenceOrder); + } + exonInRefOrderCache = exonsInReferenceOrder; + for ( GenomeLoc exon : exonsInReferenceOrder ) { + if ( exon.overlapsP(position) ) { + return ++exonNo; + } + ++exonNo; + } + + return -1; + } + + public GenomeLoc getSortedExonLoc(int offset) { + ArrayList exonsInReferenceOrder = exonInRefOrderCache != null ? exonInRefOrderCache : new ArrayList(exons); + if ( exonInRefOrderCache == null ) { + Collections.sort(exonsInReferenceOrder); + } + exonInRefOrderCache = exonsInReferenceOrder; + return exonsInReferenceOrder.get(offset); + } + /** Returns true if the specified interval 'that' overlaps with the full genomic interval of this transcript */ public boolean overlapsP (GenomeLoc that) { return getLocation().overlapsP(that); diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java index 2557d70bb..aa6d7d345 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/table/TableCodec.java @@ -88,7 +88,6 @@ public class TableCodec implements ReferenceDependentFeatureCodec { try { boolean isFirst = true; while ((line = reader.readLine()) != null) { - System.out.println(line); if ( isFirst && ! line.startsWith(headerDelimiter) && ! line.startsWith(commentDelimiter)) { throw new UserException.MalformedFile("TableCodec file does not have a header"); } From 339ef92eacba83c1d3a39f0d1a15c4b0169ea695 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Mon, 19 Dec 2011 12:18:18 -0500 Subject: [PATCH 329/380] Goodbye SW by default. Now aligned reads that overlap intron-exon junctions are scored where they are by default, but warns the user (and flags the record in the VCF) if there's evidence to suggest that there is an indel throwing off the scoring (e.g. if the best score of a realigned unmapped read is >5 log orders better than the best score of a scored mapped read). Unmapped reads are still SW-aligned to the junction-junction sequence. This should result in a rather massive speedup, so far untested. UGBoundAF has to go in at some point. In the process of rewriting the math for bounding the allele frequency (it was assuming uniform tails, which is silly since i derived the posterior distribution in closed form sometime back, just need to find it) --- .../gatk/walkers/genotyper/UGBoundAF.java | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100755 public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGBoundAF.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGBoundAF.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGBoundAF.java new file mode 100755 index 000000000..e40054c9f --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UGBoundAF.java @@ -0,0 +1,209 @@ +package org.broadinstitute.sting.gatk.walkers.genotyper; + +import org.apache.commons.lang.NotImplementedException; +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.RodWalker; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.SampleUtils; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.collections.Pair; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.variantcontext.*; + +import java.security.cert.CertificateNotYetValidException; +import java.util.*; + +import org.broadinstitute.sting.utils.codecs.vcf.*; + +/** + * Created by IntelliJ IDEA. + * User: chartl + * Date: 8/30/11 + * Time: 10:08 AM + * To change this template use File | Settings | File Templates. + */ +public class UGBoundAF extends RodWalker { + + @Output(shortName="vcf",fullName="VCF",doc="file to write to",required=true) + VCFWriter writer; + + @Input(shortName="V",fullName="Variants",doc="variant tracks to use in calculation",required=true) + List> variants; + + private static double EPS_LOWER_LIMIT = Math.pow(10,-6.0); + + private HashMap> epsilonPosteriorCache = new HashMap>(8192); + private HashMap logAC0Cache = new HashMap(8192); + private int QUANTIZATION_FACTOR = 1000; + + + public void initialize() { + Set allHeaderLines = new HashSet(1024); + for ( RodBinding v : variants ) { + String trackName = v.getName(); + Map vcfHeaders = VCFUtils.getVCFHeadersFromRods(getToolkit(), Arrays.asList(trackName)); + Set headerLines = new HashSet(vcfHeaders.get(trackName).getMetaData()); + } + allHeaderLines.add(new VCFInfoHeaderLine("AFB",2,VCFHeaderLineType.Float,"The 95% bounds on the allele "+ + "frequency. First value=95% probability AF>x. Second value=95% probability AF allVariants = tracker.getValues(variants); + if ( allVariants.size() == 0 ) { + return null; + } + + List alternateAlleles = getAllAlternateAlleles(allVariants); + VariantContextBuilder builder = new VariantContextBuilder(allVariants.get(0).subContextFromSamples(new TreeSet())); + if ( alternateAlleles.size() > 1 ) { + logger.warn("Multiple Segregating Variants at position "+ref.getLocus().toString()); + alternateAlleles.add(allVariants.get(0).getReference()); + builder.alleles(alternateAlleles); + builder.filters(String.format("MULTIPLE_SEGREGATING[%s]", Utils.join(",",alternateAlleles))); + } else { + // get all the genotype likelihoods + GenotypesContext context = GenotypesContext.create(); + int numNoCall = 0; + for ( VariantContext v : allVariants ) { + numNoCall += v.getNoCallCount(); + context.addAll(v.getGenotypes()); + } + builder.attribute("AFB",boundAlleleFrequency(getACPosteriors(context))); + } + + return builder.make(); + } + + private List getAllAlternateAlleles(List variants) { + List alleles = new ArrayList(3); // some overhead + for ( VariantContext v : variants ) { + alleles.addAll(v.getAlternateAlleles()); + } + return alleles; + } + + @Override + public Integer reduce(VariantContext value, Integer sum) { + if ( value == null ) + return sum; + writer.add(value); + return ++sum; + } + + private int N_ITERATIONS = 1; + private double[] getACPosteriors(GenotypesContext gc) { + // note this uses uniform priors (!) + + double[][] zeroPriors = new double[1][1+2*gc.size()]; + AlleleFrequencyCalculationResult result = new AlleleFrequencyCalculationResult(2,2*gc.size()); + // todo -- allow multiple alleles here + for ( int i = 0; i < N_ITERATIONS; i ++ ) { + ExactAFCalculationModel.linearExactMultiAllelic(gc, 2, zeroPriors, result, false); + } + + return result.log10AlleleFrequencyPosteriors[0]; + } + + private String boundAlleleFrequency(double[] ACLikelihoods) { + // note that no-calls are unnecessary: the ML likelihoods take nocalls into account as 0,0,0 GLs + // thus, for sites with K 100,40,0 likelihoods and M no-calls, the likelihoods will be + // agnostic between 2*K alleles through 2*(K+M) alleles - exactly what we want to marginalize over + + // want to pick a lower limit x and upper limit y such that + // int_{f = x to y} sum_{c = 0 to 2*AN} P(AF=f | c, AN) df = 0.95 + // int_{f=x to y} calculateAFPosterior(f) df = 0.95 + // and that (y-x) is minimized + + // this is done by quantizing [0,1] into small bins and, since the distribution is + // unimodal, greedily adding them until the probability is >= 0.95 + + throw new ReviewedStingException("This walker is unsupported, and is not fully implemented", new NotImplementedException("bound allele frequency not implemented")); + } + + private double calculateAFPosterior(double[] likelihoods, double af) { + double[] probLiks = new double[likelihoods.length]; + for ( int c = 0; c < likelihoods.length; c++) { + probLiks[c] = calculateAFPosterior(c,likelihoods.length,af); + } + + return MathUtils.log10sumLog10(probLiks); + } + + private double calculateAFPosterior(int ac, int n, double af) { + // evaluate the allele frequency posterior distribution at AF given AC observations of N chromosomes + switch ( ac ) { + case 0: + return logAC0Coef(n) + n*Math.log10(1 - af) - Math.log10(af); + case 1: + return Math.log10(n) + (n-1)*Math.log10(1-af) - n*Math.log10(1-EPS_LOWER_LIMIT); + case 2: + return Math.log10(n) + Math.log10(n-1) + Math.log10(af) + (n-2)*Math.log10(1-af) - Math.log10(1-(n-1)*EPS_LOWER_LIMIT) - (n-1)*Math.log10(EPS_LOWER_LIMIT); + default: + return (ac-1)*Math.log10(af)+ac*Math.log10( (double) n-ac)-(n-ac)*af*Math.log10(Math.E) - MathUtils.log10Gamma(ac); + } + } + + private double logAC0Coef(int an) { + if ( ! logAC0Cache.containsKey(an) ) { + double coef = -Math.log10(EPS_LOWER_LIMIT); + for ( int k = 1; k <= an; k++ ) { + // note this should typically just be + // term = ( 1 - Math.pow(EPS_LOWER_LIMIT,k) ) * MathUtils.binomialCoefficient(an,k) / k + // but the 1-E term will just be 1, so we do the following to mitigate this problem + double binom = MathUtils.binomialCoefficient(an,k); + double eps_correction = EPS_LOWER_LIMIT*Math.pow(binom,1/k); + double term = binom/k - Math.pow(eps_correction,k); + if ( k % 2 == 0 ) { + coef += term; + } else { + coef -= term; + } + } + + logAC0Cache.put(an,coef); + } + + return logAC0Cache.get(an); + } + + private double adaptiveSimpson(double[] likelihoods, double start, double stop, double err, int cap) { + double mid = (start + stop)/2; + double size = stop-start; + double fa = calculateAFPosterior(likelihoods,start); + double fb = calculateAFPosterior(likelihoods,mid); + double fc = calculateAFPosterior(likelihoods,stop); + double s = (size/6)*(fa + 4*fc + fb); + double h = simpAux(likelihoods,start,stop,err,s,fa,fb,fc,cap); + return h; + } + + private double simpAux(double[] likelihoods, double a,double b,double eps,double s,double fa,double fb,double fc,double cap){ + if ( s == 0 ) + return -300.0; + double c = ( a + b )/2; + double h = b-a; + double d = (a + c)/2; + double e = (c + b)/2; + double fd = calculateAFPosterior(likelihoods, d); + double fe = calculateAFPosterior(likelihoods, e); + double s_l = (h/12)*(fa + 4*fd + fc); + double s_r = (h/12)*(fc + 4*fe + fb); + double s_2 = s_l + s_r; + if ( cap <= 0 || Math.abs(s_2 - s) <= 15*eps ){ + return Math.log10(s_2 + (s_2 - s)/15.0); + } + + return ExactAFCalculationModel.approximateLog10SumLog10(simpAux(likelihoods,a,c,eps/2,s_l,fa,fc,fd,cap-1),simpAux(likelihoods, c, b, eps / 2, s_r, fc, fb, fe, cap - 1)); + } +} From 06d385e619751b3ea30b83079272cef66cd7883a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 19 Dec 2011 15:29:46 -0500 Subject: [PATCH 331/380] Simplifying the interface a bit --- .../genotyper/UnifiedGenotyperEngine.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 c58966999..aa33d39e3 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 @@ -322,8 +322,9 @@ public class UnifiedGenotyperEngine { for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { int indexOfBestAC = MathUtils.maxElementIndex(AFresult.log10AlleleFrequencyPosteriors[i]); - // if the most likely AC is not 0, then this is a good alternate allele to use - if ( indexOfBestAC != 0 && (AFresult.log10AlleleFrequencyPosteriors[i][0] != AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED || AFresult.log10AlleleFrequencyPosteriors[i][indexOfBestAC] > AFresult.log10PosteriorOfAFzero) ) { + // if the most likely AC is not 0, then this is a good alternate allele to use; + // make sure to test against log10PosteriorOfAFzero since that no longer is an entry in the array + if ( indexOfBestAC != 0 && AFresult.log10AlleleFrequencyPosteriors[i][indexOfBestAC] > AFresult.log10PosteriorOfAFzero ) { altAllelesToUse[i] = true; bestGuessIsRef = false; } @@ -392,7 +393,7 @@ public class UnifiedGenotyperEngine { } // create the genotypes - final GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse, myAlleles); + final GenotypesContext genotypes = assignGenotypes(vc, altAllelesToUse); // print out stats if we have a writer if ( verboseWriter != null && !limitedContext ) @@ -771,13 +772,10 @@ public class UnifiedGenotyperEngine { /** * @param vc variant context with genotype likelihoods * @param allelesToUse bit vector describing which alternate alleles from the vc are okay to use - * @param newAlleles a list of the final new alleles to use - * * @return genotypes */ public static GenotypesContext assignGenotypes(final VariantContext vc, - final boolean[] allelesToUse, - final List newAlleles) { + final boolean[] allelesToUse) { // the no-called genotypes final GenotypesContext GLs = vc.getGenotypes(); @@ -790,6 +788,12 @@ public class UnifiedGenotyperEngine { // we need to determine which of the alternate alleles (and hence the likelihoods) to use and carry forward final int numOriginalAltAlleles = allelesToUse.length; + final List newAlleles = new ArrayList(numOriginalAltAlleles+1); + newAlleles.add(vc.getReference()); + for ( int i = 0; i < numOriginalAltAlleles; i++ ) { + if ( allelesToUse[i] ) + newAlleles.add(vc.getAlternateAllele(i)); + } final int numNewAltAlleles = newAlleles.size() - 1; ArrayList likelihoodIndexesToUse = null; From 67298f8a11f08d08b169b58d55fe0e45f8cdb32d Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Mon, 19 Dec 2011 23:14:26 -0500 Subject: [PATCH 332/380] AFCR made public (for use in VSS) Minor changes to ValidationSiteSelector logic (SampleSelectors determine whether a site is valid for output, no actual subset context need be operated on beyond that determination). Implementation of GL-based site selection. Minor changes to EJG. --- .../AlleleFrequencyCalculationResult.java | 10 ++++- .../FrequencyModeSelector.java | 6 +-- .../GLBasedSampleSelector.java | 43 ++++++++++++++++--- .../GTBasedSampleSelector.java | 10 +++-- .../KeepAFSpectrumFrequencySelector.java | 4 +- .../NullSampleSelector.java | 5 +-- .../SampleSelector.java | 2 +- .../UniformSamplingFrequencySelector.java | 4 +- .../ValidationSiteSelectorWalker.java | 17 +++++--- 9 files changed, 74 insertions(+), 27 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java index ed80dce0d..9c4af8512 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/AlleleFrequencyCalculationResult.java @@ -48,8 +48,16 @@ public class AlleleFrequencyCalculationResult { double log10LikelihoodOfAFzero = 0.0; double log10PosteriorOfAFzero = 0.0; - AlleleFrequencyCalculationResult(int maxAltAlleles, int numChr) { + public AlleleFrequencyCalculationResult(int maxAltAlleles, int numChr) { log10AlleleFrequencyLikelihoods = new double[maxAltAlleles][numChr+1]; log10AlleleFrequencyPosteriors = new double[maxAltAlleles][numChr+1]; } + + public double getLog10LikelihoodOfAFzero() { + return log10LikelihoodOfAFzero; + } + + public double getLog10PosteriorOfAFzero() { + return log10PosteriorOfAFzero; + } } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java index 5c0d6cb87..62305d3c0 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/FrequencyModeSelector.java @@ -38,10 +38,10 @@ public abstract class FrequencyModeSelector implements Cloneable{ protected FrequencyModeSelector(GenomeLocParser parser) { this.parser = parser; } - protected void logCurrentSiteData(VariantContext vc, VariantContext subVC) { - logCurrentSiteData(vc, subVC, false, false); + protected void logCurrentSiteData(VariantContext vc, boolean passesCriteria) { + logCurrentSiteData(vc, passesCriteria, false, false); } - protected abstract void logCurrentSiteData(VariantContext vc, VariantContext subVC, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC); + protected abstract void logCurrentSiteData(VariantContext vc, boolean included, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC); protected abstract ArrayList selectValidationSites(int numValidationSites); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java index d229e718e..ff3fe6506 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GLBasedSampleSelector.java @@ -23,21 +23,52 @@ */ package org.broadinstitute.sting.gatk.walkers.validation.validationsiteselector; +import org.broadinstitute.sting.gatk.walkers.genotyper.AlleleFrequencyCalculationResult; +import org.broadinstitute.sting.gatk.walkers.genotyper.ExactAFCalculationModel; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.TreeSet; public class GLBasedSampleSelector extends SampleSelector { - public GLBasedSampleSelector(TreeSet sm) { + Map numAllelePriorMatrix = new HashMap(); + double referenceLikelihood; + public GLBasedSampleSelector(TreeSet sm, double refLik) { super(sm); + referenceLikelihood = refLik; } - public VariantContext subsetSiteToSamples(VariantContext vc) { - /* todo - Look at sample array, and create a new vc with samples for which GL's indicate they should be included. - For example, include all samples (and corresponding genotypes) whose GL's are such that argmax(GL) = HET or HOMVAR. */ - throw new ReviewedStingException("GLBasedSampleSelector not implemented yet!"); - //return true; + public boolean selectSiteInSamples(VariantContext vc) { + if ( samples == null || samples.isEmpty() ) + return true; + // want to include a site in the given samples if it is *likely* to be variant (via the EXACT model) + // first subset to the samples + VariantContext subContext = vc.subContextFromSamples(samples); + + // now check to see (using EXACT model) whether this should be variant + // do we want to apply a prior? maybe user-spec? + double[][] flatPrior = createFlatPrior(vc.getAlleles()); + AlleleFrequencyCalculationResult result = new AlleleFrequencyCalculationResult(vc.getAlternateAlleles().size(),2*samples.size()); + ExactAFCalculationModel.linearExactMultiAllelic(subContext.getGenotypes(),vc.getAlternateAlleles().size(),flatPrior,result,true); + // do we want to let this qual go up or down? + if ( result.getLog10PosteriorOfAFzero() < referenceLikelihood ) { + return true; + } + + return false; + } + + private double[][] createFlatPrior(List alleles) { + if ( ! numAllelePriorMatrix.containsKey(alleles.size()) ) { + numAllelePriorMatrix.put(alleles.size(), new double[alleles.size()][1+2*samples.size()]); + } + + return numAllelePriorMatrix.get(alleles.size()); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java index 44fca71fb..c3987b9db 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/GTBasedSampleSelector.java @@ -38,14 +38,18 @@ public class GTBasedSampleSelector extends SampleSelector{ super(sm); } - public VariantContext subsetSiteToSamples(VariantContext vc) { + public boolean selectSiteInSamples(VariantContext vc) { // Super class already defined initialization which filled data structure "samples" with desired samples. // We only need to check if current vc if polymorphic in that set of samples if ( samples == null || samples.isEmpty() ) - return vc; + return true; - return vc.subContextFromSamples(samples, vc.getAlleles()); + VariantContext subContext = vc.subContextFromSamples(samples, vc.getAlleles()); + if ( subContext.isPolymorphicInSamples() ) { + return true; + } + return false; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java index 739cfcab4..15274d21c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/KeepAFSpectrumFrequencySelector.java @@ -60,7 +60,7 @@ public class KeepAFSpectrumFrequencySelector extends FrequencyModeSelector { postSampleSelectionHistogram = new int[NUM_BINS]; } - public void logCurrentSiteData(VariantContext vc, VariantContext subVC, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC) { + public void logCurrentSiteData(VariantContext vc, boolean selectedInTargetSamples, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC) { // this method is called for every variant of a selected type, regardless of whether it will be selectable or not // get AC,AF,AN attributes from vc @@ -107,7 +107,7 @@ public class KeepAFSpectrumFrequencySelector extends FrequencyModeSelector { numTotalSites++; // now process VC subsetted to samples of interest - if (!subVC.isPolymorphicInSamples() && !IGNORE_POLYMORPHIC) + if (! selectedInTargetSamples && !IGNORE_POLYMORPHIC) return; //System.out.format("Post:%4.4f %d\n",af0, binIndex); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java index 35b8893c2..a48bcb8a1 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/NullSampleSelector.java @@ -33,8 +33,7 @@ public class NullSampleSelector extends SampleSelector{ super(sm); } - public VariantContext subsetSiteToSamples(VariantContext vc) { - return vc; - + public boolean selectSiteInSamples(VariantContext vc) { + return true; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java index e75fcef5d..afbff93d0 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/SampleSelector.java @@ -34,7 +34,7 @@ public abstract class SampleSelector implements Cloneable { samples = new TreeSet(sm); } - protected abstract VariantContext subsetSiteToSamples(VariantContext vc); + protected abstract boolean selectSiteInSamples(VariantContext vc); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java index 506a6b2e4..66720a252 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/UniformSamplingFrequencySelector.java @@ -42,14 +42,14 @@ public class UniformSamplingFrequencySelector extends FrequencyModeSelector { } - public void logCurrentSiteData(VariantContext vc, VariantContext subVC, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC) { + public void logCurrentSiteData(VariantContext vc, boolean selectedInTargetSamples, boolean IGNORE_GENOTYPES, boolean IGNORE_POLYMORPHIC) { HashMap attributes = new HashMap(); if (vc.hasGenotypes() && !IGNORE_GENOTYPES) { // recompute AF,AC,AN based on genotypes: VariantContextUtils.calculateChromosomeCounts(vc, attributes, false); - if (!subVC.isPolymorphicInSamples() && !IGNORE_POLYMORPHIC) + if (! selectedInTargetSamples && !IGNORE_POLYMORPHIC) return; } else { if ( attributes.containsKey(VCFConstants.ALLELE_COUNT_KEY) ) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java index 10d2f639c..ae11d8102 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/validation/validationsiteselector/ValidationSiteSelectorWalker.java @@ -124,6 +124,10 @@ public class ValidationSiteSelectorWalker extends RodWalker { @Argument(fullName="sampleMode", shortName="sampleMode", doc="Sample selection mode", required=false) private SAMPLE_SELECTION_MODE sampleMode = SAMPLE_SELECTION_MODE.NONE; + @Argument(shortName="samplePNonref",fullName="samplePNonref", doc="GL-based selection mode only: the probability" + + " that a site is non-reference in the samples for which to include the site",required=false) + private double samplePNonref = 0.99; + @Argument(fullName="numValidationSites", shortName="numSites", doc="Number of output validation sites", required=true) private int numValidationSites; @@ -231,13 +235,14 @@ public class ValidationSiteSelectorWalker extends RodWalker { continue; - // do anything required by frequency selector before we select for samples - VariantContext subVC; + // does this site pass the criteria for the samples we are interested in? + boolean passesSampleSelectionCriteria; if (samples.isEmpty()) - subVC = vc; + passesSampleSelectionCriteria = true; else - subVC = sampleSelector.subsetSiteToSamples(vc); - frequencyModeSelector.logCurrentSiteData(vc, subVC, IGNORE_GENOTYPES, IGNORE_POLYMORPHIC); + passesSampleSelectionCriteria = sampleSelector.selectSiteInSamples(vc); + + frequencyModeSelector.logCurrentSiteData(vc,passesSampleSelectionCriteria,IGNORE_GENOTYPES,IGNORE_POLYMORPHIC); } return 1; } @@ -263,7 +268,7 @@ public class ValidationSiteSelectorWalker extends RodWalker { SampleSelector sm; switch ( sampleMode ) { case POLY_BASED_ON_GL: - sm = new GLBasedSampleSelector(samples); + sm = new GLBasedSampleSelector(samples, Math.log10(1.0-samplePNonref)); break; case POLY_BASED_ON_GT: sm = new GTBasedSampleSelector(samples); From 78d9bf7196a76ceb901a7ec5f90c55c9f4d20457 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 19 Dec 2011 14:56:19 -0500 Subject: [PATCH 333/380] Added REVERT_SOFTCLIPPED_BASES capability to ReadClipper * New ClippingOp REVERT_SOFTCLIPPED_BASES turns soft clipped bases into matches. * Added functionality to clipping op to revert all soft clip bases in a read into matches * Added revertSoftClipBases function to the ReadClipper for public use * Wrote systematic unit tests --- .../sting/utils/clipping/ClippingOp.java | 38 ++++++++++++++++++- .../clipping/ClippingRepresentation.java | 7 +++- .../sting/utils/clipping/ReadClipper.java | 5 +++ .../utils/clipping/ReadClipperUnitTest.java | 31 ++++++++++++--- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java index f440eda5d..7eb853097 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java @@ -104,6 +104,10 @@ public class ClippingOp { break; + case REVERT_SOFTCLIPPED_BASES: + read = revertSoftClippedBases(read); + break; + default: throw new IllegalStateException("Unexpected Clipping operator type " + algorithm); } @@ -111,6 +115,38 @@ public class ClippingOp { return read; } + private GATKSAMRecord revertSoftClippedBases(GATKSAMRecord read) { + GATKSAMRecord unclipped; + + // shallow copy of the read bases and quals should be fine here because they are immutable in the original read + try { + unclipped = (GATKSAMRecord) read.clone(); + } catch (CloneNotSupportedException e) { + throw new ReviewedStingException("Where did the clone go?"); + } + + Cigar unclippedCigar = new Cigar(); + int matchesCount = 0; + for (CigarElement element : read.getCigar().getCigarElements()) { + if (element.getOperator() == CigarOperator.SOFT_CLIP || element.getOperator() == CigarOperator.MATCH_OR_MISMATCH) + matchesCount += element.getLength(); + else if (matchesCount > 0) { + unclippedCigar.add(new CigarElement(matchesCount, CigarOperator.MATCH_OR_MISMATCH)); + matchesCount = 0; + unclippedCigar.add(element); + } + else + unclippedCigar.add(element); + } + if (matchesCount > 0) + unclippedCigar.add(new CigarElement(matchesCount, CigarOperator.MATCH_OR_MISMATCH)); + + unclipped.setCigar(unclippedCigar); + unclipped.setAlignmentStart(read.getAlignmentStart() + calculateAlignmentStartShift(read.getCigar(), unclippedCigar)); + + return unclipped; + } + /** * Given a cigar string, get the number of bases hard or soft clipped at the start */ @@ -472,7 +508,7 @@ public class ClippingOp { for (CigarElement cigarElement : oldCigar.getCigarElements()) { if (cigarElement.getOperator() == CigarOperator.HARD_CLIP || cigarElement.getOperator() == CigarOperator.SOFT_CLIP ) - oldShift += Math.min(cigarElement.getLength(), newShift - oldShift); + oldShift += cigarElement.getLength(); else if (readHasStarted) break; } diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java index c9d8730d1..f0765665a 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingRepresentation.java @@ -29,5 +29,10 @@ public enum ClippingRepresentation { * lossy) operation. Note that this can only be applied to cases where the clipped * bases occur at the start or end of a read. */ - HARDCLIP_BASES + HARDCLIP_BASES, + + /** + * Turn all soft-clipped bases into matches + */ + REVERT_SOFTCLIPPED_BASES, } diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java index 6e3f37980..f19a7a04f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java @@ -213,4 +213,9 @@ public class ReadClipper { } return clipRead(ClippingRepresentation.HARDCLIP_BASES); } + + public GATKSAMRecord revertSoftClippedBases() { + this.addOp(new ClippingOp(0, 0)); // UNSOFTCLIP_BASES doesn't need coordinates + return this.clipRead(ClippingRepresentation.REVERT_SOFTCLIPPED_BASES); + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java index 3b4b0abce..e228762b7 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java @@ -279,9 +279,9 @@ public class ReadClipperUnitTest extends BaseTest { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); GATKSAMRecord clippedRead = (new ReadClipper(read)).hardClipLeadingInsertions(); - int expectedLength = read.getReadLength() - leadingInsertionLength(read.getCigar()); + int expectedLength = read.getReadLength() - leadingCigarElementLength(read.getCigar(), CigarOperator.INSERTION); if (cigarHasElementsDifferentThanInsertionsAndHardClips(read.getCigar())) - expectedLength -= leadingInsertionLength(ReadClipperTestUtils.invertCigar(read.getCigar())); + expectedLength -= leadingCigarElementLength(ReadClipperTestUtils.invertCigar(read.getCigar()), CigarOperator.INSERTION); if (! clippedRead.isEmpty()) { Assert.assertEquals(expectedLength, clippedRead.getReadLength(), String.format("%s -> %s", read.getCigarString(), clippedRead.getCigarString())); // check that everything else is still there @@ -293,6 +293,27 @@ public class ReadClipperUnitTest extends BaseTest { } } + @Test(enabled = true) + public void testRevertSoftClippedBases() + { + for (Cigar cigar: cigarList) { + final int leadingSoftClips = leadingCigarElementLength(cigar, CigarOperator.SOFT_CLIP); + final int tailSoftClips = leadingCigarElementLength(ReadClipperTestUtils.invertCigar(cigar), CigarOperator.SOFT_CLIP); + + final GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); + final GATKSAMRecord unclipped = (new ReadClipper(read)).revertSoftClippedBases(); + + if ( leadingSoftClips > 0 || tailSoftClips > 0) { + final int expectedStart = read.getAlignmentStart() - leadingSoftClips; + final int expectedEnd = read.getAlignmentEnd() + tailSoftClips; + + Assert.assertEquals(unclipped.getAlignmentStart(), expectedStart); + Assert.assertEquals(unclipped.getAlignmentEnd(), expectedEnd); + } + else + Assert.assertEquals(read.getCigarString(), unclipped.getCigarString()); + } + } private void assertNoLowQualBases(GATKSAMRecord read, byte low_qual) { @@ -304,12 +325,12 @@ public class ReadClipperUnitTest extends BaseTest { } private boolean startsWithInsertion(Cigar cigar) { - return leadingInsertionLength(cigar) > 0; + return leadingCigarElementLength(cigar, CigarOperator.INSERTION) > 0; } - private int leadingInsertionLength(Cigar cigar) { + private int leadingCigarElementLength(Cigar cigar, CigarOperator operator) { for (CigarElement cigarElement : cigar.getCigarElements()) { - if (cigarElement.getOperator() == CigarOperator.INSERTION) + if (cigarElement.getOperator() == operator) return cigarElement.getLength(); if (cigarElement.getOperator() != CigarOperator.HARD_CLIP) break; From 37e0044c4887c0992ea13a655990c510667f2f43 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 20 Dec 2011 00:11:26 -0500 Subject: [PATCH 334/380] Removing unclipSoftClipBases from ReadUtils * it was buggy and dangerous. * Updated Chris' code to use the ReadClipper. --- .../sting/utils/sam/ReadUtils.java | 44 ------------------- 1 file changed, 44 deletions(-) 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 ac1f00437..a46a7f0bb 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -851,50 +851,6 @@ public class ReadUtils { return new Pair(readBases, fallsInsideDeletion); } - public static GATKSAMRecord unclipSoftClippedBases(GATKSAMRecord read) { - int newReadStart = read.getAlignmentStart(); - int newReadEnd = read.getAlignmentEnd(); - List newCigarElements = new ArrayList(read.getCigar().getCigarElements().size()); - int heldOver = -1; - boolean sSeen = false; - for ( CigarElement e : read.getCigar().getCigarElements() ) { - if ( e.getOperator().equals(CigarOperator.S) ) { - newCigarElements.add(new CigarElement(e.getLength(),CigarOperator.M)); - if ( sSeen ) { - newReadEnd += e.getLength(); - sSeen = true; - } else { - newReadStart -= e.getLength(); - } - } else { - newCigarElements.add(e); - } - } - // merge duplicate operators together - int idx = 0; - List finalCigarElements = new ArrayList(read.getCigar().getCigarElements().size()); - while ( idx < newCigarElements.size() -1 ) { - if ( newCigarElements.get(idx).getOperator().equals(newCigarElements.get(idx+1).getOperator()) ) { - int combSize = newCigarElements.get(idx).getLength(); - int offset = 0; - while ( idx + offset < newCigarElements.size()-1 && newCigarElements.get(idx+offset).getOperator().equals(newCigarElements.get(idx+1+offset).getOperator()) ) { - combSize += newCigarElements.get(idx+offset+1).getLength(); - offset++; - } - finalCigarElements.add(new CigarElement(combSize,newCigarElements.get(idx).getOperator())); - idx = idx + offset -1; - } else { - finalCigarElements.add(newCigarElements.get(idx)); - } - idx++; - } - - read.setCigar(new Cigar(finalCigarElements)); - read.setAlignmentStart(newReadStart); - - return read; - } - /** * Compares two SAMRecords only the basis on alignment start. Note that * comparisons are performed ONLY on the basis of alignment start; any From 6f52bd580b2c8aea0363092c0cd6925315067211 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 20 Dec 2011 12:47:38 -0500 Subject: [PATCH 335/380] --multiallelic mode is not hidden anymore (but it is annotated as advanced); added docs --- .../genotyper/UnifiedArgumentCollection.java | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index 53600b145..a13801c7c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java @@ -25,10 +25,7 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; -import org.broadinstitute.sting.commandline.Argument; -import org.broadinstitute.sting.commandline.Hidden; -import org.broadinstitute.sting.commandline.Input; -import org.broadinstitute.sting.commandline.RodBinding; +import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.utils.variantcontext.VariantContext; @@ -106,6 +103,15 @@ public class UnifiedArgumentCollection { @Argument(fullName = "max_deletion_fraction", shortName = "deletions", doc = "Maximum fraction of reads with deletions spanning this locus for it to be callable [to disable, set to < 0 or > 1; default:0.05]", required = false) public Double MAX_DELETION_FRACTION = 0.05; + /** + * The default behavior of the Unified Genotyper is to allow the genotyping of just one alternate allele in discovery mode; using this flag + * will enable the discovery of multiple alternate alleles. Please note that this works for SNPs only and that it is still highly experimental. + * For advanced users only. + */ + @Advanced + @Argument(fullName = "multiallelic", shortName = "multiallelic", doc = "Allow the discovery of multiple alleles (SNPs only)", required = false) + public boolean MULTI_ALLELIC = false; + // indel-related arguments /** * A candidate indel is genotyped (and potentially called) if there are this number of reads with a consensus indel at a site. @@ -132,15 +138,6 @@ public class UnifiedArgumentCollection { @Argument(fullName = "indelHaplotypeSize", shortName = "indelHSize", doc = "Indel haplotype size", required = false) public int INDEL_HAPLOTYPE_SIZE = 80; - //gdebug+ - // experimental arguments, NOT TO BE USED BY ANYONE WHOSE INITIALS AREN'T GDA!!! -// @Hidden -// @Argument(fullName = "getGapPenaltiesFromData", shortName = "dataGP", doc = "Vary gap penalties by context - EXPERIMENTAL, DO NO USE", required = false) -// public boolean GET_GAP_PENALTIES_FROM_DATA = false; -// -// @Hidden -// @Argument(fullName="indel_recal_file", shortName="recalFile", required=false, doc="Filename for the input covariates table recalibration .csv file - EXPERIMENTAL, DO NO USE") -// public File INDEL_RECAL_FILE = new File("indel.recal_data.csv"); @Hidden @Argument(fullName = "bandedIndel", shortName = "bandedIndel", doc = "Banded Indel likelihood computation", required = false) public boolean BANDED_INDEL_COMPUTATION = false; @@ -153,10 +150,6 @@ public class UnifiedArgumentCollection { @Argument(fullName = "ignoreSNPAlleles", shortName = "ignoreSNPAlleles", doc = "expt", required = false) public boolean IGNORE_SNP_ALLELES = false; - @Hidden - @Argument(fullName = "multiallelic", shortName = "multiallelic", doc = "Allow multiple alleles in discovery", required = false) - public boolean MULTI_ALLELIC = false; - /** * If there are more than this number of alternate alleles presented to the genotyper (either through discovery or GENOTYPE_GIVEN ALLELES), * then this site will be skipped and a warning printed. Note that genotyping sites with many alternate alleles is both CPU and memory intensive. From 8ade2d6ac2fe5a41a38ea5079a1704c058da3a83 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 20 Dec 2011 12:59:02 -0500 Subject: [PATCH 336/380] max_alternate_alleles also ready to be made public --- .../genotyper/UnifiedArgumentCollection.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index a13801c7c..5713432b4 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java @@ -112,6 +112,13 @@ public class UnifiedArgumentCollection { @Argument(fullName = "multiallelic", shortName = "multiallelic", doc = "Allow the discovery of multiple alleles (SNPs only)", required = false) public boolean MULTI_ALLELIC = false; + /** + * If there are more than this number of alternate alleles presented to the genotyper (either through discovery or GENOTYPE_GIVEN ALLELES), + * then this site will be skipped and a warning printed. Note that genotyping sites with many alternate alleles is both CPU and memory intensive. + */ + @Argument(fullName = "max_alternate_alleles", shortName = "maxAlleles", doc = "Maximum number of alternate alleles to genotype", required = false) + public int MAX_ALTERNATE_ALLELES = 5; + // indel-related arguments /** * A candidate indel is genotyped (and potentially called) if there are this number of reads with a consensus indel at a site. @@ -150,14 +157,6 @@ public class UnifiedArgumentCollection { @Argument(fullName = "ignoreSNPAlleles", shortName = "ignoreSNPAlleles", doc = "expt", required = false) public boolean IGNORE_SNP_ALLELES = false; - /** - * If there are more than this number of alternate alleles presented to the genotyper (either through discovery or GENOTYPE_GIVEN ALLELES), - * then this site will be skipped and a warning printed. Note that genotyping sites with many alternate alleles is both CPU and memory intensive. - */ - @Hidden - @Argument(fullName = "max_alternate_alleles", shortName = "maxAlleles", doc = "Maximum number of alternate alleles to genotype", required = false) - public int MAX_ALTERNATE_ALLELES = 5; - // Developers must remember to add any newly added arguments to the list here as well otherwise they won't get changed from their default value! public UnifiedArgumentCollection clone() { From 0cc5c3d799d43886b64239f532e6889381011868 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 19 Dec 2011 15:46:32 -0500 Subject: [PATCH 337/380] General improvements to Queue -- Support for collecting resources info from DRMAA runners -- Disabled the non-standard mem_free argument so that we can actually use our own SGE cluster gsa4 -- NCoresRequest is a testing queue script for this. -- Added two command line arguments: -- multiCoreJerk: don't request multiple cores for jobs with nt > 1. This was the old behavior but it's really not the best way to run parallel jobs. Now with queue if you run nt = 4 the system requests 4 cores on your host. If this flag is thrown, though, it will only request 1 and you'll just use 4, like a jerk -- job_parallel_env: parallel environment named used with SGE to request multicore jobs. Equivalent to -pe job_parallel_env NT for NT > 1 jobs --- .../gatk/GATKExtensionsGenerator.java | 18 +++++++++------ .../sting/queue/QSettings.scala | 7 ++++++ .../queue/engine/drmaa/DrmaaJobRunner.scala | 14 +++++++++++- .../gridengine/GridEngineJobRunner.scala | 20 +++++++++++++++-- .../queue/engine/lsf/Lsf706JobRunner.scala | 22 ++++++++++++++++++- .../queue/function/CommandLineFunction.scala | 10 +++++++++ 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java index 67010c4d5..9c40fb976 100644 --- a/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java +++ b/public/java/src/org/broadinstitute/sting/queue/extensions/gatk/GATKExtensionsGenerator.java @@ -147,7 +147,7 @@ public class GATKExtensionsGenerator extends CommandLineProgram { String clpConstructor = String.format("analysisName = \"%s\"%njavaMainClass = \"%s\"%n", clpClassName, clp.getName()); writeClass("org.broadinstitute.sting.queue.function.JavaCommandLineFunction", clpClassName, - false, clpConstructor, ArgumentDefinitionField.getArgumentFields(parser,clp), dependents); + false, clpConstructor, ArgumentDefinitionField.getArgumentFields(parser,clp), dependents, false); if (clp == CommandLineGATK.class) { for (Entry>> walkersByPackage: walkerManager.getWalkerNamesByPackage(false).entrySet()) { @@ -169,7 +169,7 @@ public class GATKExtensionsGenerator extends CommandLineProgram { } writeClass(GATK_EXTENSIONS_PACKAGE_NAME + "." + clpClassName, walkerName, - isScatter, constructor, argumentFields, dependents); + isScatter, constructor, argumentFields, dependents, true); } catch (Exception e) { throw new ReviewedStingException("Error generating wrappers for walker " + walkerType, e); } @@ -241,8 +241,9 @@ public class GATKExtensionsGenerator extends CommandLineProgram { * @throws IOException If the file cannot be written. */ private void writeClass(String baseClass, String className, boolean isScatter, - String constructor, List argumentFields, Set> dependents) throws IOException { - String content = getContent(CLASS_TEMPLATE, baseClass, className, constructor, isScatter, "", argumentFields, dependents); + String constructor, List argumentFields, + Set> dependents, boolean isGATKWalker) throws IOException { + String content = getContent(CLASS_TEMPLATE, baseClass, className, constructor, isScatter, "", argumentFields, dependents, isGATKWalker); writeFile(GATK_EXTENSIONS_PACKAGE_NAME + "." + className, content); } @@ -256,7 +257,7 @@ public class GATKExtensionsGenerator extends CommandLineProgram { */ private void writeFilter(String className, List argumentFields, Set> dependents) throws IOException { String content = getContent(TRAIT_TEMPLATE, "org.broadinstitute.sting.queue.function.CommandLineFunction", - className, "", false, String.format(" + \" -read_filter %s\"", className), argumentFields, dependents); + className, "", false, String.format(" + \" -read_filter %s\"", className), argumentFields, dependents, false); writeFile(GATK_EXTENSIONS_PACKAGE_NAME + "." + className, content); } @@ -350,7 +351,8 @@ public class GATKExtensionsGenerator extends CommandLineProgram { */ private static String getContent(String scalaTemplate, String baseClass, String className, String constructor, boolean isScatter, String commandLinePrefix, - List argumentFields, Set> dependents) { + List argumentFields, Set> dependents, + boolean isGATKWalker) { StringBuilder arguments = new StringBuilder(); StringBuilder commandLine = new StringBuilder(commandLinePrefix); @@ -384,8 +386,10 @@ public class GATKExtensionsGenerator extends CommandLineProgram { StringBuffer freezeFieldOverride = new StringBuffer(); for (String freezeField: freezeFields) freezeFieldOverride.append(freezeField); - if (freezeFieldOverride.length() > 0) { + if (freezeFieldOverride.length() > 0 || isGATKWalker) { freezeFieldOverride.insert(0, String.format("override def freezeFieldValues = {%nsuper.freezeFieldValues%n")); + if ( isGATKWalker ) + freezeFieldOverride.append(String.format("if ( num_threads.isDefined ) nCoresRequest = num_threads%n")); freezeFieldOverride.append(String.format("}%n%n")); } diff --git a/public/scala/src/org/broadinstitute/sting/queue/QSettings.scala b/public/scala/src/org/broadinstitute/sting/queue/QSettings.scala index 648f9ffef..e8ac26a57 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/QSettings.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/QSettings.scala @@ -62,6 +62,13 @@ class QSettings { @Argument(fullName="resident_memory_request", shortName="resMemReq", doc="Default resident memory request for jobs, in gigabytes.", required=false) var residentRequest: Option[Double] = None + /** The name of the parallel environment (required for SGE, for example) */ + @Argument(fullName="job_parallel_env", shortName="jobParaEnv", doc="An SGE style parallel environment to use for jobs requesting more than 1 core. Equivalent to submitting jobs with -pe ARG nt for jobs with nt > 1", required=false) + var parallelEnvironmentName: String = "smp_pe" // Broad default + + @Argument(fullName="dontRequestMultipleCores", shortName="multiCoreJerk", doc="If provided, Queue will not request multiple processors for jobs using multiple processors. Sometimes you eat the bear, sometimes the bear eats you.", required=false) + var dontRequestMultipleCores: Boolean = false + @Argument(fullName="run_directory", shortName="runDir", doc="Root directory to run functions from.", required=false) var runDirectory = new File(".") diff --git a/public/scala/src/org/broadinstitute/sting/queue/engine/drmaa/DrmaaJobRunner.scala b/public/scala/src/org/broadinstitute/sting/queue/engine/drmaa/DrmaaJobRunner.scala index 227261912..2aae2fc6b 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/engine/drmaa/DrmaaJobRunner.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/engine/drmaa/DrmaaJobRunner.scala @@ -28,8 +28,8 @@ import org.broadinstitute.sting.queue.QException import org.broadinstitute.sting.queue.util.{Logging,Retry} import org.broadinstitute.sting.queue.function.CommandLineFunction import org.broadinstitute.sting.queue.engine.{RunnerStatus, CommandLineJobRunner} -import java.util.Collections import org.ggf.drmaa._ +import java.util.{Date, Collections} /** * Runs jobs using DRMAA. @@ -103,6 +103,18 @@ class DrmaaJobRunner(val session: Session, val function: CommandLineFunction) ex case Session.QUEUED_ACTIVE => returnStatus = RunnerStatus.RUNNING case Session.DONE => val jobInfo: JobInfo = session.wait(jobId, Session.TIMEOUT_NO_WAIT) + + // Update jobInfo + def convertDRMAATime(key: String): Date = { + val v = jobInfo.getResourceUsage.get(key) + if ( v != null ) new Date(v.toString.toDouble.toLong * 1000) else null; + } + if ( jobInfo.getResourceUsage != null ) { + getRunInfo.startTime = convertDRMAATime("start_time") + getRunInfo.doneTime = convertDRMAATime("end_time") + getRunInfo.exechosts = "unknown" + } + if ((jobInfo.hasExited && jobInfo.getExitStatus != 0) || jobInfo.hasSignaled || jobInfo.wasAborted) diff --git a/public/scala/src/org/broadinstitute/sting/queue/engine/gridengine/GridEngineJobRunner.scala b/public/scala/src/org/broadinstitute/sting/queue/engine/gridengine/GridEngineJobRunner.scala index 96e3ffd95..fca92a7a1 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/engine/gridengine/GridEngineJobRunner.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/engine/gridengine/GridEngineJobRunner.scala @@ -52,13 +52,28 @@ class GridEngineJobRunner(session: Session, function: CommandLineFunction) exten nativeSpec += " -q " + function.jobQueue // If the resident set size is requested pass on the memory request - if (function.residentRequest.isDefined) - nativeSpec += " -l mem_free=%dM".format(function.residentRequest.map(_ * 1024).get.ceil.toInt) + // NOTE: 12/20/11: depristo commented this out because mem_free isn't + // such a standard feature in SGE (gsa-engineering queue doesn't support it) + // requiring it can make SGE not so usable. It's dangerous to not enforce + // that we have enough memory to run our jobs, but I'd rather be dangerous + // than not be able to run my jobs at all. +// if (function.residentRequest.isDefined) +// nativeSpec += " -l mem_free=%dM".format(function.residentRequest.map(_ * 1024).get.ceil.toInt) // If the resident set size limit is defined specify the memory limit if (function.residentLimit.isDefined) nativeSpec += " -l h_rss=%dM".format(function.residentLimit.map(_ * 1024).get.ceil.toInt) + // If more than 1 core is requested, set the proper request + // if we aren't being jerks and just stealing cores (previous behavior) + if ( function.nCoresRequest.getOrElse(1) > 1 ) { + if ( function.qSettings.dontRequestMultipleCores ) + logger.warn("Sending multicore job %s to farm without requesting appropriate number of cores (%d)".format( + function.jobName, function.nCoresRequest.get)) + else + nativeSpec += " -pe %s %d".format(function.qSettings.parallelEnvironmentName, function.nCoresRequest.get) + } + // Pass on any job resource requests nativeSpec += function.jobResourceRequests.map(" -l " + _).mkString @@ -70,6 +85,7 @@ class GridEngineJobRunner(session: Session, function: CommandLineFunction) exten if (priority.isDefined) nativeSpec += " -p " + priority.get + logger.debug("Native spec is: %s".format(nativeSpec)) (nativeSpec + " " + super.functionNativeSpec).trim() } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/engine/lsf/Lsf706JobRunner.scala b/public/scala/src/org/broadinstitute/sting/queue/engine/lsf/Lsf706JobRunner.scala index 323cc63ff..5ef78500c 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/engine/lsf/Lsf706JobRunner.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/engine/lsf/Lsf706JobRunner.scala @@ -56,6 +56,7 @@ class Lsf706JobRunner(val function: CommandLineFunction) extends CommandLineJobR private val selectString = new StringBuffer() private val usageString = new StringBuffer() private val requestString = new StringBuffer() + private val spanString = new StringBuffer() /** * Dispatches the function on the LSF cluster. @@ -100,6 +101,23 @@ class Lsf706JobRunner(val function: CommandLineFunction) extends CommandLineJobR appendRequest("rusage", usageString, ",", "mem=%d".format(memInUnits)) } + // + // Request multiple cores on the same host. If nCoresRequest > 1, and we + // aren't being jerks and stealing cores, set numProcessors and maxNumProcessors + // and the span[host=1] parameters to get us exactly the right number of + // cores on a single host + // + if ( function.nCoresRequest.getOrElse(1) > 1 ) { + if ( function.qSettings.dontRequestMultipleCores ) + logger.warn("Sending multicore job %s to farm without requesting appropriate number of cores (%d)".format( + function.jobName, function.nCoresRequest.get)) + else { + request.numProcessors = function.nCoresRequest.get + request.maxNumProcessors = request.numProcessors + appendRequest("span", spanString, ",", "hosts=1") + } + } + val resReq = getResourceRequest if (resReq.length > 0) { request.resReq = resReq @@ -167,10 +185,12 @@ class Lsf706JobRunner(val function: CommandLineFunction) extends CommandLineJobR requestString.setLength(0) selectString.setLength(0) usageString.setLength(0) + spanString.setLength(0) requestString.append(function.jobResourceRequests.mkString(" ")) extractSection(requestString, "select", selectString) extractSection(requestString, "rusage", usageString) + extractSection(requestString, "span", spanString) } private def extractSection(requestString: StringBuffer, section: String, sectionString: StringBuffer) { @@ -196,7 +216,7 @@ class Lsf706JobRunner(val function: CommandLineFunction) extends CommandLineJobR sectionString.insert(sectionString.length() - 1, separator + request) } - private def getResourceRequest = "%s %s %s".format(selectString, usageString, requestString).trim() + private def getResourceRequest = "%s %s %s %s".format(selectString, usageString, spanString, requestString).trim() } object Lsf706JobRunner extends Logging { diff --git a/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala index b08832f22..167dcb593 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/function/CommandLineFunction.scala @@ -17,6 +17,9 @@ trait CommandLineFunction extends QFunction with Logging { /** Resident memory request */ var residentRequest: Option[Double] = None + /** the number of SMP cores this job wants */ + var nCoresRequest: Option[Int] = None + /** Job project to run the command */ var jobProject: String = _ @@ -45,6 +48,9 @@ trait CommandLineFunction extends QFunction with Logging { if (commandLineFunction.residentRequest.isEmpty) commandLineFunction.residentRequest = this.residentRequest + if (commandLineFunction.nCoresRequest.isEmpty) + commandLineFunction.nCoresRequest = this.nCoresRequest + if (commandLineFunction.jobProject == null) commandLineFunction.jobProject = this.jobProject @@ -100,6 +106,10 @@ trait CommandLineFunction extends QFunction with Logging { if (residentRequest.isEmpty) residentRequest = qSettings.residentRequest + // the default value is 1 core + if (nCoresRequest.isEmpty) + nCoresRequest = Some(1) + if (residentRequest.isEmpty) residentRequest = memoryLimit From f73ad1c2e27d2418e428eb80d5047c5bf2b2770d Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 19 Dec 2011 14:13:55 -0500 Subject: [PATCH 342/380] Bugfix/Rewrite: Algorithm to determine adaptor boundaries The algorithm wasn't accounting for the case where the read is the reverse strand and the insert size is negative. * Fixed and rewrote for more clarity (with Ryan, Mark and Eric). * Restructured the code to handle GATKSAMRecords only * Cleaned up the other structures and functions around it to minimize clutter and potential for error. * Added unit tests for all 4 cases of adaptor boundaries. --- .../gatk/iterators/LocusIteratorByState.java | 7 +- .../sting/utils/sam/ReadUtils.java | 269 +++++++----------- .../utils/{ => sam}/ReadUtilsUnitTest.java | 47 ++- 3 files changed, 155 insertions(+), 168 deletions(-) rename public/java/test/org/broadinstitute/sting/utils/{ => sam}/ReadUtilsUnitTest.java (66%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java index ee3ea63eb..75e787e05 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -39,7 +39,6 @@ import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.ReservoirDownsampler; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.pileup.ExtendedEventPileupElement; import org.broadinstitute.sting.utils.pileup.PileupElement; @@ -432,7 +431,7 @@ public class LocusIteratorByState extends LocusIterator { while(iterator.hasNext()) { SAMRecordState state = iterator.next(); if ( state.getCurrentCigarOperator() != CigarOperator.D && state.getCurrentCigarOperator() != CigarOperator.N ) { - if ( filterBaseInRead(state.getRead(), location.getStart()) ) { + if ( filterBaseInRead((GATKSAMRecord) state.getRead(), location.getStart()) ) { //discarded_bases++; //printStatus("Adaptor bases", discarded_adaptor_bases); continue; @@ -481,8 +480,8 @@ public class LocusIteratorByState extends LocusIterator { * @param pos * @return */ - private static boolean filterBaseInRead(SAMRecord rec, long pos) { - return ReadUtils.readPairBaseOverlapType(rec, pos) == ReadUtils.OverlapType.IN_ADAPTOR; + private static boolean filterBaseInRead(GATKSAMRecord rec, long pos) { + return ReadUtils.isBaseInsideAdaptor(rec, pos); } private void updateReadStates() { 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 a46a7f0bb..aa90ecf6f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -45,85 +45,13 @@ import java.util.*; public class ReadUtils { private ReadUtils() { } - // ---------------------------------------------------------------------------------------------------- - // - // Reduced read utilities - // - // ---------------------------------------------------------------------------------------------------- + private static int DEFAULT_ADAPTOR_SIZE = 100; - // ---------------------------------------------------------------------------------------------------- - // - // General utilities - // - // ---------------------------------------------------------------------------------------------------- - public static SAMFileHeader copySAMFileHeader(SAMFileHeader toCopy) { - SAMFileHeader copy = new SAMFileHeader(); - - copy.setSortOrder(toCopy.getSortOrder()); - copy.setGroupOrder(toCopy.getGroupOrder()); - copy.setProgramRecords(toCopy.getProgramRecords()); - copy.setReadGroups(toCopy.getReadGroups()); - copy.setSequenceDictionary(toCopy.getSequenceDictionary()); - - for (Map.Entry e : toCopy.getAttributes()) - copy.setAttribute(e.getKey(), e.getValue()); - - return copy; + public enum ClippingTail { + LEFT_TAIL, + RIGHT_TAIL } - public static SAMFileWriter createSAMFileWriterWithCompression(SAMFileHeader header, boolean presorted, String file, int compression) { - if (file.endsWith(".bam")) - return new SAMFileWriterFactory().makeBAMWriter(header, presorted, new File(file), compression); - return new SAMFileWriterFactory().makeSAMOrBAMWriter(header, presorted, new File(file)); - } - - public static boolean isPlatformRead(SAMRecord read, String name) { - SAMReadGroupRecord readGroup = read.getReadGroup(); - if (readGroup != null) { - Object readPlatformAttr = readGroup.getAttribute("PL"); - if (readPlatformAttr != null) - return readPlatformAttr.toString().toUpperCase().contains(name); - } - return false; - } - - // --------------------------------------------------------------------------------------------------------- - // - // utilities for detecting overlapping reads - // - // --------------------------------------------------------------------------------------------------------- - - /** - * Detects read pairs where the reads are so long relative to the over fragment size that they are - * reading into each other's adaptors. - * - * Normally, fragments are sufficiently far apart that reads aren't reading into each other. - * - * |--------------------> first read - * <--------------------| second read - * - * Sometimes, mostly due to lab errors or constraints, fragment library are made too short relative to the - * length of the reads. For example, it's possible to have 76bp PE reads with 125 bp inserts, so that ~25 bp of each - * read overlaps with its mate. - * - * |--------OOOOOOOOOOOO> first read - * first read - * [record in hand] - * s2 - * <-----------------------| - * - * s1, e1, and s2 are all in the record. From isize we can can compute e2 as s1 + isize + 1 - * - * s2 - * |-----------------------> - * s1 e1 - * <-----------------------| [record in hand] - * - * Here we cannot calculate e2 since the record carries s2 and e1 + isize is s2 now! - * - * This makes the following code a little nasty, since we can only detect if a base is in the adaptor, but not - * if it overlaps the read. - * - * @param read - * @param basePos - * @param adaptorLength - * @return - */ - public static OverlapType readPairBaseOverlapType(final SAMRecord read, long basePos, final int adaptorLength) { - OverlapType state = OverlapType.NOT_OVERLAPPING; + public static SAMFileHeader copySAMFileHeader(SAMFileHeader toCopy) { + SAMFileHeader copy = new SAMFileHeader(); - Pair adaptorBoundaries = getAdaptorBoundaries(read, adaptorLength); + copy.setSortOrder(toCopy.getSortOrder()); + copy.setGroupOrder(toCopy.getGroupOrder()); + copy.setProgramRecords(toCopy.getProgramRecords()); + copy.setReadGroups(toCopy.getReadGroups()); + copy.setSequenceDictionary(toCopy.getSequenceDictionary()); - if ( adaptorBoundaries != null ) { // we're not an unmapped pair -- cannot filter out + for (Map.Entry e : toCopy.getAttributes()) + copy.setAttribute(e.getKey(), e.getValue()); - boolean inAdapator = basePos >= adaptorBoundaries.first && basePos <= adaptorBoundaries.second; - - if ( inAdapator ) { - state = OverlapType.IN_ADAPTOR; - //System.out.printf("baseOverlapState: %50s negStrand=%b base=%d start=%d stop=%d, adaptorStart=%d adaptorEnd=%d isize=%d => %s%n", - // read.getReadName(), read.getReadNegativeStrandFlag(), basePos, read.getAlignmentStart(), read.getAlignmentEnd(), adaptorBoundaries.first, adaptorBoundaries.second, read.getInferredInsertSize(), state); - } - } - - return state; + return copy; } - private static Pair getAdaptorBoundaries(SAMRecord read, int adaptorLength) { - int isize = read.getInferredInsertSize(); - if ( isize == 0 ) - return null; // don't worry about unmapped pairs + public static SAMFileWriter createSAMFileWriterWithCompression(SAMFileHeader header, boolean presorted, String file, int compression) { + if (file.endsWith(".bam")) + return new SAMFileWriterFactory().makeBAMWriter(header, presorted, new File(file), compression); + return new SAMFileWriterFactory().makeSAMOrBAMWriter(header, presorted, new File(file)); + } - int adaptorStart, adaptorEnd; - - if ( read.getReadNegativeStrandFlag() ) { - // we are on the negative strand, so our mate is on the positive strand - int mateStart = read.getMateAlignmentStart(); - adaptorStart = mateStart - adaptorLength - 1; - adaptorEnd = mateStart - 1; - } else { - // we are on the positive strand, so our mate is on the negative strand - int mateEnd = read.getAlignmentStart() + isize - 1; - adaptorStart = mateEnd + 1; - adaptorEnd = mateEnd + adaptorLength; + public static boolean isPlatformRead(SAMRecord read, String name) { + SAMReadGroupRecord readGroup = read.getReadGroup(); + if (readGroup != null) { + Object readPlatformAttr = readGroup.getAttribute("PL"); + if (readPlatformAttr != null) + return readPlatformAttr.toString().toUpperCase().contains(name); } + return false; + } - return new Pair(adaptorStart, adaptorEnd); + + /** + * is this base inside the adaptor of the read? + * + * There are two cases to treat here: + * + * 1) Read is in the negative strand => Adaptor boundary is on the left tail + * 2) Read is in the positive strand => Adaptor boundary is on the right tail + * + * Note: We return false to all reads that are UNMAPPED or have an weird big insert size (probably due to mismapping or bigger event) + * + * @param read the read to test + * @param basePos base position in REFERENCE coordinates (not read coordinates) + * @return whether or not the base is in the adaptor + */ + public static boolean isBaseInsideAdaptor(final GATKSAMRecord read, long basePos) { + Integer adaptorBoundary = getAdaptorBoundary(read); + if (adaptorBoundary == null || read.getInferredInsertSize() > DEFAULT_ADAPTOR_SIZE) + return false; + + return read.getReadNegativeStrandFlag() ? basePos <= adaptorBoundary : basePos >= adaptorBoundary; } /** + * Finds the adaptor boundary around the read and returns the first base inside the adaptor that is closest to + * the read boundary. If the read is in the positive strand, this is the first base after the end of the + * fragment (Picard calls it 'insert'), if the read is in the negative strand, this is the first base before the + * beginning of the fragment. * - * @param read original SAM record - * @param adaptorLength length of adaptor sequence - * @return a new read with adaptor sequence hard-clipped out or null if read is fully clipped + * There are two cases we need to treat here: + * + * 1) Our read is in the reverse strand : + * + * <----------------------| * + * |---------------------> + * + * in these cases, the adaptor boundary is at the mate start (minus one) + * + * 2) Our read is in the forward strand : + * + * |----------------------> * + * <----------------------| + * + * in these cases the adaptor boundary is at the start of the read plus the inferred insert size (plus one) + * + * @param read the read being tested for the adaptor boundary + * @return the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read. NULL if the read is unmapped or the insert size cannot be determined (and is necessary for the calculation). */ - public static GATKSAMRecord hardClipAdaptorSequence(final GATKSAMRecord read, int adaptorLength) { + protected static Integer getAdaptorBoundary(final SAMRecord read) { + if ( read.getReadUnmappedFlag() ) + return null; // don't worry about unmapped pairs - Pair adaptorBoundaries = getAdaptorBoundaries(read, adaptorLength); - GATKSAMRecord result = read; + final int isize = Math.abs(read.getInferredInsertSize()); // the inferred insert size can be negative if the mate is mapped before the read (so we take the absolute value) + int adaptorBoundary; // the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read) - if ( adaptorBoundaries != null ) { - if ( read.getReadNegativeStrandFlag() && adaptorBoundaries.second >= read.getAlignmentStart() && adaptorBoundaries.first < read.getAlignmentEnd() ) - result = hardClipStartOfRead(read, adaptorBoundaries.second); - else if ( !read.getReadNegativeStrandFlag() && adaptorBoundaries.first <= read.getAlignmentEnd() ) - result = hardClipEndOfRead(read, adaptorBoundaries.first); - } + if ( read.getReadNegativeStrandFlag() ) + adaptorBoundary = read.getMateAlignmentStart() - 1; // case 1 (see header) + else if (isize > 0) + adaptorBoundary = read.getAlignmentStart() + isize + 1; // case 2 (see header) + else + return null; // this is a case 2 where for some reason we cannot estimate the insert size - return result; + return adaptorBoundary; } // return true if the read needs to be completely clipped @@ -557,7 +494,6 @@ public class ReadUtils { } - private static int DEFAULT_ADAPTOR_SIZE = 100; /** * @@ -565,12 +501,14 @@ public class ReadUtils { * @return a new read with adaptor sequence hard-clipped out or null if read is fully clipped */ public static GATKSAMRecord hardClipAdaptorSequence(final GATKSAMRecord read) { - return hardClipAdaptorSequence(read, DEFAULT_ADAPTOR_SIZE); + final Integer adaptorBoundary = getAdaptorBoundary(read); + + if (adaptorBoundary == null || !isInsideRead(read, adaptorBoundary)) + return read; + + return (read.getReadNegativeStrandFlag()) ? hardClipStartOfRead(read, adaptorBoundary) : hardClipEndOfRead(read, adaptorBoundary); } - public static OverlapType readPairBaseOverlapType(final SAMRecord read, long basePos) { - return readPairBaseOverlapType(read, basePos, DEFAULT_ADAPTOR_SIZE); - } public static boolean is454Read(SAMRecord read) { return isPlatformRead(read, "454"); @@ -716,18 +654,6 @@ public class ReadUtils { return (lastOperator == CigarOperator.HARD_CLIP) ? stop-1 : stop+shift-1 ; } - private static boolean readIsEntirelyInsertion(GATKSAMRecord read) { - for (CigarElement cigarElement : read.getCigar().getCigarElements()) { - if (cigarElement.getOperator() != CigarOperator.INSERTION) - return false; - } - return true; - } - - public enum ClippingTail { - LEFT_TAIL, - RIGHT_TAIL - } /** * Pre-processes the results of getReadCoordinateForReferenceCoordinate(GATKSAMRecord, int) in case it falls in @@ -866,7 +792,24 @@ public class ReadUtils { return comp.compare(read1, read2); } - // TEST UTILITIES + /** + * Is a base inside a read? + * + * @param read the read to evaluate + * @param referenceCoordinate the reference coordinate of the base to test + * @return true if it is inside the read, false otherwise. + */ + public static boolean isInsideRead(final GATKSAMRecord read, final int referenceCoordinate) { + return referenceCoordinate >= read.getAlignmentStart() && referenceCoordinate <= read.getAlignmentEnd(); + } + + private static boolean readIsEntirelyInsertion(GATKSAMRecord read) { + for (CigarElement cigarElement : read.getCigar().getCigarElements()) { + if (cigarElement.getOperator() != CigarOperator.INSERTION) + return false; + } + return true; + } diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java similarity index 66% rename from public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java rename to public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java index 630beaece..3878cbfa9 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.utils; +package org.broadinstitute.sting.utils.sam; import net.sf.samtools.SAMFileHeader; import net.sf.samtools.SAMRecord; @@ -69,4 +69,49 @@ public class ReadUtilsUnitTest extends BaseTest { Assert.assertEquals(reducedreadp.getRepresentativeCount(), REDUCED_READ_COUNTS[0]); Assert.assertEquals(reducedreadp.getQual(), readp.getQual()); } + + @Test + public void testGetAdaptorBoundary() { + final byte [] bases = {'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T'}; + final byte [] quals = {30, 30, 30, 30, 30, 30, 30, 30}; + final String cigar = "8M"; + final int fragmentSize = 10; + final int mateStart = 1000; + final int BEFORE = mateStart - 2; + final int AFTER = mateStart + 2; + int myStart, boundary; + + GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(bases, quals, cigar); + read.setMateAlignmentStart(mateStart); + read.setInferredInsertSize(fragmentSize); + + // Test case 1: positive strand, first read + myStart = BEFORE; + read.setAlignmentStart(myStart); + read.setReadNegativeStrandFlag(false); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertEquals(boundary, myStart + fragmentSize + 1); + + // Test case 2: positive strand, second read + myStart = AFTER; + read.setAlignmentStart(myStart); + read.setReadNegativeStrandFlag(false); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertEquals(boundary, myStart + fragmentSize + 1); + + // Test case 3: negative strand, second read + myStart = AFTER; + read.setAlignmentStart(myStart); + read.setReadNegativeStrandFlag(true); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertEquals(boundary, mateStart - 1); + + // Test case 4: negative strand, first read + myStart = BEFORE; + read.setAlignmentStart(myStart); + read.setReadNegativeStrandFlag(true); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertEquals(boundary, mateStart - 1); + + } } From 1c4774c475bb8f3e4a1a02336ea6836363f96c6c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 20 Dec 2011 14:30:06 -0500 Subject: [PATCH 343/380] Static versions of the hard clipping utilities For simplified access to the hard clipping utilities. No need to create a ReadClipper object if you are not doing multiple complicated clipping operations, just use the static methods. examples: ReadClipper.hardClipLowQualEnds(2); ReadClipper.hardClipAdaptorSequence(); --- .../sting/utils/clipping/ReadClipper.java | 114 +++++++++++++----- 1 file changed, 83 insertions(+), 31 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java index f19a7a04f..d82472bae 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java @@ -50,6 +50,44 @@ public class ReadClipper { return read; } + /** + * Return a new read corresponding to this.read that's been clipped according to ops, if any are present. + * + * @param algorithm + * @return + */ + public GATKSAMRecord clipRead(ClippingRepresentation algorithm) { + if (ops == null) + return getRead(); + else { + try { + GATKSAMRecord clippedRead = (GATKSAMRecord) read.clone(); + for (ClippingOp op : getOps()) { + //check if the clipped read can still be clipped in the range requested + if (op.start < clippedRead.getReadLength()) { + ClippingOp fixedOperation = op; + if (op.stop >= clippedRead.getReadLength()) + fixedOperation = new ClippingOp(op.start, clippedRead.getReadLength() - 1); + + clippedRead = fixedOperation.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 + } + } + } + + + + + // QUICK USE UTILITY FUNCTION + public GATKSAMRecord hardClipByReferenceCoordinatesLeftTail(int refStop) { return hardClipByReferenceCoordinates(-1, refStop); } @@ -163,39 +201,13 @@ public class ReadClipper { return clipRead(ClippingRepresentation.HARDCLIP_BASES); } + public GATKSAMRecord hardClipAdaptorSequence () { + final Integer adaptorBoundary = ReadUtils.getAdaptorBoundary(read); + if (adaptorBoundary == null || !ReadUtils.isInsideRead(read, adaptorBoundary)) + return read; - /** - * Return a new read corresponding to this.read that's been clipped according to ops, if any are present. - * - * @param algorithm - * @return - */ - public GATKSAMRecord clipRead(ClippingRepresentation algorithm) { - if (ops == null) - return getRead(); - else { - try { - GATKSAMRecord clippedRead = (GATKSAMRecord) read.clone(); - for (ClippingOp op : getOps()) { - //check if the clipped read can still be clipped in the range requested - if (op.start < clippedRead.getReadLength()) { - ClippingOp fixedOperation = op; - if (op.stop >= clippedRead.getReadLength()) - fixedOperation = new ClippingOp(op.start, clippedRead.getReadLength() - 1); - - clippedRead = fixedOperation.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 - } - } + return read.getReadNegativeStrandFlag() ? hardClipByReferenceCoordinatesLeftTail(adaptorBoundary) : hardClipByReferenceCoordinatesRightTail(adaptorBoundary); } public GATKSAMRecord hardClipLeadingInsertions() { @@ -218,4 +230,44 @@ public class ReadClipper { this.addOp(new ClippingOp(0, 0)); // UNSOFTCLIP_BASES doesn't need coordinates return this.clipRead(ClippingRepresentation.REVERT_SOFTCLIPPED_BASES); } + + + + // STATIC VERSIONS OF THE QUICK CLIPPING FUNCTIONS + + public static GATKSAMRecord hardClipByReferenceCoordinatesLeftTail(GATKSAMRecord read, int refStop) { + return (new ReadClipper(read)).hardClipByReferenceCoordinates(-1, refStop); + } + + public static GATKSAMRecord hardClipByReferenceCoordinatesRightTail(GATKSAMRecord read, int refStart) { + return (new ReadClipper(read)).hardClipByReferenceCoordinates(refStart, -1); + } + + public static GATKSAMRecord hardClipByReadCoordinates(GATKSAMRecord read, int start, int stop) { + return (new ReadClipper(read)).hardClipByReadCoordinates(start, stop); + } + + public static GATKSAMRecord hardClipBothEndsByReferenceCoordinates(GATKSAMRecord read, int left, int right) { + return (new ReadClipper(read)).hardClipBothEndsByReferenceCoordinates(left, right); + } + + public static GATKSAMRecord hardClipLowQualEnds(GATKSAMRecord read, byte lowQual) { + return (new ReadClipper(read)).hardClipLowQualEnds(lowQual); + } + + public static GATKSAMRecord hardClipSoftClippedBases (GATKSAMRecord read) { + return (new ReadClipper(read)).hardClipSoftClippedBases(); + } + + public static GATKSAMRecord hardClipAdaptorSequence (GATKSAMRecord read) { + return (new ReadClipper(read)).hardClipAdaptorSequence(); + } + + public static GATKSAMRecord hardClipLeadingInsertions(GATKSAMRecord read) { + return (new ReadClipper(read)).hardClipLeadingInsertions(); + } + + public static GATKSAMRecord revertSoftClippedBases(GATKSAMRecord read) { + return (new ReadClipper(read)).revertSoftClippedBases(); + } } From 07128a2ad2cfe4d2b63a4df947ef7831573bbe1d Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 20 Dec 2011 14:35:33 -0500 Subject: [PATCH 344/380] ReadUtils cleanup * Removed all clipping functionality from ReadUtils (it should all be done using the ReadClipper now) * Cleaned up functionality that wasn't being used or had been superseded by other code (in an effort to reduce multiple unsupported implementations) * Made all meaningful functions public and added better comments/explanation to the headers --- .../sting/gatk/walkers/ClipReadsWalker.java | 5 +- .../gatk/walkers/SplitSamFileWalker.java | 18 +- ...elGenotypeLikelihoodsCalculationModel.java | 3 +- .../gatk/walkers/indels/IndelRealigner.java | 2 +- .../indels/PairHMMIndelErrorModel.java | 3 +- .../sting/utils/sam/ReadUtils.java | 457 ++++-------------- 6 files changed, 112 insertions(+), 376 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java index ff3867120..c28e205bf 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java @@ -299,9 +299,8 @@ public class ClipReadsWalker extends ReadWalker readGroups = new ArrayList(); header.setReadGroups(readGroups); @@ -121,4 +121,20 @@ public class SplitSamFileWalker extends ReadWalker e : toCopy.getAttributes()) + copy.setAttribute(e.getKey(), e.getValue()); + + return copy; + } + } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java index 653a6f6e7..fe2086d47 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/IndelGenotypeLikelihoodsCalculationModel.java @@ -34,6 +34,7 @@ import org.broadinstitute.sting.gatk.walkers.indels.PairHMMIndelErrorModel; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.Haplotype; +import org.broadinstitute.sting.utils.clipping.ReadClipper; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.pileup.ExtendedEventPileupElement; @@ -125,7 +126,7 @@ public class IndelGenotypeLikelihoodsCalculationModel extends GenotypeLikelihood for ( ExtendedEventPileupElement p : indelPileup.toExtendedIterable() ) { //SAMRecord read = p.getRead(); - GATKSAMRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); + GATKSAMRecord read = ReadClipper.hardClipAdaptorSequence(p.getRead()); if (read == null) continue; if(ReadUtils.is454Read(read)) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/IndelRealigner.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/IndelRealigner.java index ba031c497..8f939fc49 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/IndelRealigner.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/IndelRealigner.java @@ -463,7 +463,7 @@ public class IndelRealigner extends ReadWalker { private void emitReadLists() { // pre-merge lists to sort them in preparation for constrained SAMFileWriter readsNotToClean.addAll(readsToClean.getReads()); - ReadUtils.coordinateSortReads(readsNotToClean); + ReadUtils.sortReadsByCoordinate(readsNotToClean); manager.addReads(readsNotToClean, readsActuallyCleaned); readsToClean.clear(); readsNotToClean.clear(); 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 09968f47e..d893e620e 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 @@ -32,6 +32,7 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.clipping.ReadClipper; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.sam.ReadUtils; @@ -409,7 +410,7 @@ public class PairHMMIndelErrorModel { } else { //System.out.format("%d %s\n",p.getRead().getAlignmentStart(), p.getRead().getClass().getName()); - SAMRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); + SAMRecord read = ReadClipper.hardClipAdaptorSequence(p.getRead()); if (read == null) continue; 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 aa90ecf6f..0c3433eba 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -47,11 +47,34 @@ public class ReadUtils { private static int DEFAULT_ADAPTOR_SIZE = 100; + /** + * A marker to tell which end of the read has been clipped + */ public enum ClippingTail { LEFT_TAIL, RIGHT_TAIL } + /** + * A HashMap of the SAM spec read flag names + * + * Note: This is not being used right now, but can be useful in the future + */ + private static final Map readFlagNames = new HashMap(); + static { + readFlagNames.put(0x1, "Paired"); + readFlagNames.put(0x2, "Proper"); + readFlagNames.put(0x4, "Unmapped"); + readFlagNames.put(0x8, "MateUnmapped"); + readFlagNames.put(0x10, "Forward"); + //readFlagNames.put(0x20, "MateForward"); + readFlagNames.put(0x40, "FirstOfPair"); + readFlagNames.put(0x80, "SecondOfPair"); + readFlagNames.put(0x100, "NotPrimary"); + readFlagNames.put(0x200, "NON-PF"); + readFlagNames.put(0x400, "Duplicate"); + } + /** * This enum represents all the different ways in which a read can overlap an interval. * @@ -96,38 +119,22 @@ public class ReadUtils { */ public enum ReadAndIntervalOverlap {NO_OVERLAP_CONTIG, NO_OVERLAP_LEFT, NO_OVERLAP_RIGHT, NO_OVERLAP_HARDCLIPPED_LEFT, NO_OVERLAP_HARDCLIPPED_RIGHT, OVERLAP_LEFT, OVERLAP_RIGHT, OVERLAP_LEFT_AND_RIGHT, OVERLAP_CONTAINED} - public static SAMFileHeader copySAMFileHeader(SAMFileHeader toCopy) { - SAMFileHeader copy = new SAMFileHeader(); - - copy.setSortOrder(toCopy.getSortOrder()); - copy.setGroupOrder(toCopy.getGroupOrder()); - copy.setProgramRecords(toCopy.getProgramRecords()); - copy.setReadGroups(toCopy.getReadGroups()); - copy.setSequenceDictionary(toCopy.getSequenceDictionary()); - - for (Map.Entry e : toCopy.getAttributes()) - copy.setAttribute(e.getKey(), e.getValue()); - - return copy; - } - + /** + * Creates a SAMFileWriter with the given compression level if you request a bam file. Creates a regular + * SAMFileWriter without compression otherwise. + * + * @param header + * @param presorted + * @param file + * @param compression + * @return a SAMFileWriter with the compression level if it is a bam. + */ public static SAMFileWriter createSAMFileWriterWithCompression(SAMFileHeader header, boolean presorted, String file, int compression) { if (file.endsWith(".bam")) return new SAMFileWriterFactory().makeBAMWriter(header, presorted, new File(file), compression); return new SAMFileWriterFactory().makeSAMOrBAMWriter(header, presorted, new File(file)); } - public static boolean isPlatformRead(SAMRecord read, String name) { - SAMReadGroupRecord readGroup = read.getReadGroup(); - if (readGroup != null) { - Object readPlatformAttr = readGroup.getAttribute("PL"); - if (readPlatformAttr != null) - return readPlatformAttr.toString().toUpperCase().contains(name); - } - return false; - } - - /** * is this base inside the adaptor of the read? * @@ -175,7 +182,7 @@ public class ReadUtils { * @param read the read being tested for the adaptor boundary * @return the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read. NULL if the read is unmapped or the insert size cannot be determined (and is necessary for the calculation). */ - protected static Integer getAdaptorBoundary(final SAMRecord read) { + public static Integer getAdaptorBoundary(final SAMRecord read) { if ( read.getReadUnmappedFlag() ) return null; // don't worry about unmapped pairs @@ -192,363 +199,54 @@ public class ReadUtils { return adaptorBoundary; } - // return true if the read needs to be completely clipped - private static GATKSAMRecord hardClipStartOfRead(GATKSAMRecord oldRec, int stopPosition) { - - if ( stopPosition >= oldRec.getAlignmentEnd() ) { - // BAM representation issue -- we can't clip away all bases in a read, just leave it alone and let the filter deal with it - //System.out.printf("Entire read needs to be clipped: %50s %n", read.getReadName()); - return null; - } - - GATKSAMRecord read; - try { - read = (GATKSAMRecord)oldRec.clone(); - } catch (Exception e) { - return null; - } - - //System.out.printf("Clipping start of read: %50s start=%d adaptorEnd=%d isize=%d %n", - // read.getReadName(), read.getAlignmentStart(), stopPosition, read.getInferredInsertSize()); - - Cigar oldCigar = read.getCigar(); - LinkedList newCigarElements = new LinkedList(); - int currentPos = read.getAlignmentStart(); - int basesToClip = 0; - int basesAlreadyClipped = 0; - - for ( CigarElement ce : oldCigar.getCigarElements() ) { - - if ( currentPos > stopPosition) { - newCigarElements.add(ce); - continue; - } - - int elementLength = ce.getLength(); - switch ( ce.getOperator() ) { - case M: - for (int i = 0; i < elementLength; i++, currentPos++, basesToClip++) { - if ( currentPos > stopPosition ) { - newCigarElements.add(new CigarElement(elementLength - i, CigarOperator.M)); - break; - } - } - break; - case I: - case S: - basesToClip += elementLength; - break; - case D: - case N: - currentPos += elementLength; - break; - case H: - basesAlreadyClipped += elementLength; - case P: - break; - default: throw new ReviewedStingException("The " + ce.getOperator() + " cigar element is not currently supported"); - } - - } - - // copy over the unclipped bases - final byte[] bases = read.getReadBases(); - final byte[] quals = read.getBaseQualities(); - int newLength = bases.length - basesToClip; - byte[] newBases = new byte[newLength]; - byte[] newQuals = new byte[newLength]; - System.arraycopy(bases, basesToClip, newBases, 0, newLength); - System.arraycopy(quals, basesToClip, newQuals, 0, newLength); - read.setReadBases(newBases); - read.setBaseQualities(newQuals); - - // now add a CIGAR element for the clipped bases - newCigarElements.addFirst(new CigarElement(basesToClip + basesAlreadyClipped, CigarOperator.H)); - Cigar newCigar = new Cigar(newCigarElements); - read.setCigar(newCigar); - - // adjust the start accordingly - read.setAlignmentStart(stopPosition + 1); - - return read; - } - - private static GATKSAMRecord hardClipEndOfRead(GATKSAMRecord oldRec, int startPosition) { - - if ( startPosition <= oldRec.getAlignmentStart() ) { - // BAM representation issue -- we can't clip away all bases in a read, just leave it alone and let the filter deal with it - //System.out.printf("Entire read needs to be clipped: %50s %n", read.getReadName()); - return null; - } - - GATKSAMRecord read; - try { - read = (GATKSAMRecord)oldRec.clone(); - } catch (Exception e) { - return null; - } - - //System.out.printf("Clipping end of read: %50s adaptorStart=%d end=%d isize=%d %n", - // read.getReadName(), startPosition, read.getAlignmentEnd(), read.getInferredInsertSize()); - - Cigar oldCigar = read.getCigar(); - LinkedList newCigarElements = new LinkedList(); - int currentPos = read.getAlignmentStart(); - int basesToKeep = 0; - int basesAlreadyClipped = 0; - - for ( CigarElement ce : oldCigar.getCigarElements() ) { - - int elementLength = ce.getLength(); - - if ( currentPos >= startPosition ) { - if ( ce.getOperator() == CigarOperator.H ) - basesAlreadyClipped += elementLength; - continue; - } - - switch ( ce.getOperator() ) { - case M: - for (int i = 0; i < elementLength; i++, currentPos++, basesToKeep++) { - if ( currentPos == startPosition ) { - newCigarElements.add(new CigarElement(i, CigarOperator.M)); - break; - } - } - - if ( currentPos != startPosition ) - newCigarElements.add(ce); - break; - case I: - case S: - newCigarElements.add(ce); - basesToKeep += elementLength; - break; - case D: - case N: - newCigarElements.add(ce); - currentPos += elementLength; - break; - case H: - case P: - newCigarElements.add(ce); - break; - default: throw new ReviewedStingException("The " + ce.getOperator() + " cigar element is not currently supported"); - } - - } - - // copy over the unclipped bases - final byte[] bases = read.getReadBases(); - final byte[] quals = read.getBaseQualities(); - byte[] newBases = new byte[basesToKeep]; - byte[] newQuals = new byte[basesToKeep]; - System.arraycopy(bases, 0, newBases, 0, basesToKeep); - System.arraycopy(quals, 0, newQuals, 0, basesToKeep); - read.setReadBases(newBases); - read.setBaseQualities(newQuals); - - // now add a CIGAR element for the clipped bases - newCigarElements.add(new CigarElement((bases.length - basesToKeep) + basesAlreadyClipped, CigarOperator.H)); - Cigar newCigar = new Cigar(newCigarElements); - read.setCigar(newCigar); - - // adjust the stop accordingly - // read.setAlignmentEnd(startPosition - 1); - - return read; - } - /** - * Hard clips away (i.e.g, removes from the read) bases that were previously soft clipped. + * is the read a 454 read ? * - * @param read - * @return + * @param read the read to test + * @return checks the read group tag PL for the default 454 tag */ - @Requires("read != null") - @Ensures("result != null") - public static GATKSAMRecord hardClipSoftClippedBases(GATKSAMRecord read) { - List cigarElts = read.getCigar().getCigarElements(); - - if ( cigarElts.size() == 1 ) // can't be soft clipped, just return - return read; - - int keepStart = 0, keepEnd = read.getReadLength() - 1; - List newCigarElements = new LinkedList(); - - for ( int i = 0; i < cigarElts.size(); i++ ) { - CigarElement ce = cigarElts.get(i); - int l = ce.getLength(); - switch ( ce.getOperator() ) { - case S: - if ( i == 0 ) - keepStart = l; - else - keepEnd = read.getReadLength() - l - 1; - newCigarElements.add(new CigarElement(l, CigarOperator.HARD_CLIP)); - break; - - default: - newCigarElements.add(ce); - break; - } - } - - // Merges tandem cigar elements like 5H10H or 2S5S to 15H or 7S - // this will happen if you soft clip a read that has been hard clipped before - // like: 5H20S => 5H20H - List mergedCigarElements = new LinkedList(); - Iterator cigarElementIterator = newCigarElements.iterator(); - CigarOperator currentOperator = null; - int currentOperatorLength = 0; - while (cigarElementIterator.hasNext()) { - CigarElement cigarElement = cigarElementIterator.next(); - if (currentOperator != cigarElement.getOperator()) { - if (currentOperator != null) - mergedCigarElements.add(new CigarElement(currentOperatorLength, currentOperator)); - currentOperator = cigarElement.getOperator(); - currentOperatorLength = cigarElement.getLength(); - } - else - currentOperatorLength += cigarElement.getLength(); - } - mergedCigarElements.add(new CigarElement(currentOperatorLength, currentOperator)); - - return hardClipBases(read, keepStart, keepEnd, mergedCigarElements); - } - - /** - * Hard clips out the bases in read, keeping the bases from keepStart to keepEnd, inclusive. Note these - * are offsets, so they are 0 based - * - * @param read - * @param keepStart - * @param keepEnd - * @param newCigarElements - * @return - */ - @Requires({ - "read != null", - "keepStart >= 0", - "keepEnd < read.getReadLength()", - "read.getReadUnmappedFlag() || newCigarElements != null"}) - @Ensures("result != null") - public static GATKSAMRecord hardClipBases(GATKSAMRecord read, int keepStart, int keepEnd, List newCigarElements) { - int newLength = keepEnd - keepStart + 1; - if ( newLength != read.getReadLength() ) { - try { - read = (GATKSAMRecord)read.clone(); - // copy over the unclipped bases - final byte[] bases = read.getReadBases(); - final byte[] quals = read.getBaseQualities(); - byte[] newBases = new byte[newLength]; - byte[] newQuals = new byte[newLength]; - System.arraycopy(bases, keepStart, newBases, 0, newLength); - System.arraycopy(quals, keepStart, newQuals, 0, newLength); - read.setReadBases(newBases); - read.setBaseQualities(newQuals); - - // now add a CIGAR element for the clipped bases, if the read isn't unmapped - if ( ! read.getReadUnmappedFlag() ) { - Cigar newCigar = new Cigar(newCigarElements); - read.setCigar(newCigar); - } - } catch ( CloneNotSupportedException e ) { - throw new ReviewedStingException("WTF, where did clone go?", e); - } - } - - return read; - } - - public static GATKSAMRecord replaceSoftClipsWithMatches(GATKSAMRecord read) { - List newCigarElements = new ArrayList(); - - for ( CigarElement ce : read.getCigar().getCigarElements() ) { - if ( ce.getOperator() == CigarOperator.SOFT_CLIP ) - newCigarElements.add(new CigarElement(ce.getLength(), CigarOperator.MATCH_OR_MISMATCH)); - else - newCigarElements.add(ce); - } - - if ( newCigarElements.size() > 1 ) { // - CigarElement first = newCigarElements.get(0); - CigarElement second = newCigarElements.get(1); - if ( first.getOperator() == CigarOperator.MATCH_OR_MISMATCH && second.getOperator() == CigarOperator.MATCH_OR_MISMATCH ) { - newCigarElements.set(0, new CigarElement(first.getLength() + second.getLength(), CigarOperator.MATCH_OR_MISMATCH)); - newCigarElements.remove(1); - } - } - - if ( newCigarElements.size() > 1 ) { // - CigarElement penult = newCigarElements.get(newCigarElements.size()-2); - CigarElement last = newCigarElements.get(newCigarElements.size()-1); - if ( penult.getOperator() == CigarOperator.MATCH_OR_MISMATCH && penult.getOperator() == CigarOperator.MATCH_OR_MISMATCH ) { - newCigarElements.set(newCigarElements.size()-2, new CigarElement(penult.getLength() + last.getLength(), CigarOperator.MATCH_OR_MISMATCH)); - newCigarElements.remove(newCigarElements.size()-1); - } - } - - read.setCigar(new Cigar(newCigarElements)); - return read; - } - - - - /** - * - * @param read original SAM record - * @return a new read with adaptor sequence hard-clipped out or null if read is fully clipped - */ - public static GATKSAMRecord hardClipAdaptorSequence(final GATKSAMRecord read) { - final Integer adaptorBoundary = getAdaptorBoundary(read); - - if (adaptorBoundary == null || !isInsideRead(read, adaptorBoundary)) - return read; - - return (read.getReadNegativeStrandFlag()) ? hardClipStartOfRead(read, adaptorBoundary) : hardClipEndOfRead(read, adaptorBoundary); - } - - public static boolean is454Read(SAMRecord read) { return isPlatformRead(read, "454"); } + /** + * is the read a SOLiD read ? + * + * @param read the read to test + * @return checks the read group tag PL for the default SOLiD tag + */ public static boolean isSOLiDRead(SAMRecord read) { return isPlatformRead(read, "SOLID"); } + /** + * is the read a SLX read ? + * + * @param read the read to test + * @return checks the read group tag PL for the default SLX tag + */ public static boolean isSLXRead(SAMRecord read) { return isPlatformRead(read, "ILLUMINA"); } - private static final Map readFlagNames - = new HashMap(); - - static { - readFlagNames.put(0x1, "Paired"); - readFlagNames.put(0x2, "Proper"); - readFlagNames.put(0x4, "Unmapped"); - readFlagNames.put(0x8, "MateUnmapped"); - readFlagNames.put(0x10, "Forward"); - //readFlagNames.put(0x20, "MateForward"); - readFlagNames.put(0x40, "FirstOfPair"); - readFlagNames.put(0x80, "SecondOfPair"); - readFlagNames.put(0x100, "NotPrimary"); - readFlagNames.put(0x200, "NON-PF"); - readFlagNames.put(0x400, "Duplicate"); - } - - public static String readFlagsAsString(GATKSAMRecord read) { - String flags = ""; - for (int flag : readFlagNames.keySet()) { - if ((read.getFlags() & flag) != 0) { - flags += readFlagNames.get(flag) + " "; - } + /** + * checks if the read has a platform tag in the readgroup equal to 'name' ? + * + * @param read the read to test + * @param name the platform name to test + * @return whether or not name == PL tag in the read group of read + */ + public static boolean isPlatformRead(SAMRecord read, String name) { + SAMReadGroupRecord readGroup = read.getReadGroup(); + if (readGroup != null) { + Object readPlatformAttr = readGroup.getAttribute("PL"); + if (readPlatformAttr != null) + return readPlatformAttr.toString().toUpperCase().contains(name); } - return flags; + return false; } + /** * Returns the collections of reads sorted in coordinate order, according to the order defined * in the reads themselves @@ -556,12 +254,19 @@ public class ReadUtils { * @param reads * @return */ - public final static List coordinateSortReads(List reads) { + public final static List sortReadsByCoordinate(List reads) { final SAMRecordComparator comparer = new SAMRecordCoordinateComparator(); Collections.sort(reads, comparer); return reads; } + /** + * If a read starts in INSERTION, returns the first element length. + * + * Warning: If the read has Hard or Soft clips before the insertion this function will return 0. + * @param read + * @return the length of the first insertion, or 0 if there is none (see warning). + */ public final static int getFirstInsertionOffset(SAMRecord read) { CigarElement e = read.getCigar().getCigarElement(0); if ( e.getOperator() == CigarOperator.I ) @@ -570,8 +275,15 @@ public class ReadUtils { return 0; } + /** + * If a read ends in INSERTION, returns the last element length. + * + * Warning: If the read has Hard or Soft clips after the insertion this function will return 0. + * @param read + * @return the length of the last insertion, or 0 if there is none (see warning). + */ public final static int getLastInsertionOffset(SAMRecord read) { - CigarElement e = read.getCigar().getCigarElement(read.getCigarLength()-1); + CigarElement e = read.getCigar().getCigarElement(read.getCigarLength() - 1); if ( e.getOperator() == CigarOperator.I ) return e.getLength(); else @@ -622,6 +334,7 @@ public class ReadUtils { return ReadAndIntervalOverlap.OVERLAP_RIGHT; } + @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedStart(GATKSAMRecord read) { int start = read.getUnclippedStart(); @@ -803,7 +516,13 @@ public class ReadUtils { return referenceCoordinate >= read.getAlignmentStart() && referenceCoordinate <= read.getAlignmentEnd(); } - private static boolean readIsEntirelyInsertion(GATKSAMRecord read) { + /** + * Is this read all insertion? + * + * @param read + * @return whether or not the only element in the cigar string is an Insertion + */ + public static boolean readIsEntirelyInsertion(GATKSAMRecord read) { for (CigarElement cigarElement : read.getCigar().getCigarElements()) { if (cigarElement.getOperator() != CigarOperator.INSERTION) return false; From cadff4024721a22c4694a68d0fb5e369960e441e Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 20 Dec 2011 14:58:18 -0500 Subject: [PATCH 345/380] getRefCoordSoftUnclippedStart and End refactor These functions are methods of the read, and supplement getAlignmentStart() and getUnclippedStart() by calculating the unclipped start counting only soft clips. * Removed from ReadUtils * Added to GATKSAMRecord * Changed name to getSoftStart() and getSoftEnd * Updated third party code accordingly. --- .../sting/utils/sam/GATKSAMRecord.java | 53 +++++++++++++++++-- .../sting/utils/sam/ReadUtils.java | 42 ++------------- .../utils/clipping/ReadClipperUnitTest.java | 9 ++-- 3 files changed, 57 insertions(+), 47 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 d3a52167a..e8df869e6 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -24,10 +24,8 @@ package org.broadinstitute.sting.utils.sam; -import net.sf.samtools.BAMRecord; -import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMReadGroupRecord; -import net.sf.samtools.SAMRecord; +import com.google.java.contract.Ensures; +import net.sf.samtools.*; import org.broadinstitute.sting.utils.NGSPlatform; import java.util.HashMap; @@ -273,5 +271,52 @@ public class GATKSAMRecord extends BAMRecord { setReadGroup(rg); } + /** + * Calculates the reference coordinate for the beginning of the read taking into account soft clips but not hard clips. + * + * Note: getUnclippedStart() adds soft and hard clips, this function only adds soft clips. + * + * @return the unclipped start of the read taking soft clips (but not hard clips) into account + */ + @Ensures({"result >= getUnclippedStart()", "result <= getUnclippedEnd() || ReadUtils.readIsEntirelyInsertion(this)"}) + public int getSoftStart() { + int start = this.getUnclippedStart(); + for (CigarElement cigarElement : this.getCigar().getCigarElements()) { + if (cigarElement.getOperator() == CigarOperator.HARD_CLIP) + start += cigarElement.getLength(); + else + break; + } + return start; + } + + /** + * Calculates the reference coordinate for the end of the read taking into account soft clips but not hard clips. + * + * Note: getUnclippedStart() adds soft and hard clips, this function only adds soft clips. + * + * @return the unclipped end of the read taking soft clips (but not hard clips) into account + */ + @Ensures({"result >= getUnclippedStart()", "result <= getUnclippedEnd() || ReadUtils.readIsEntirelyInsertion(this)"}) + public int getSoftEnd() { + int stop = this.getUnclippedStart(); + + if (ReadUtils.readIsEntirelyInsertion(this)) + return stop; + + int shift = 0; + CigarOperator lastOperator = null; + for (CigarElement cigarElement : this.getCigar().getCigarElements()) { + stop += shift; + lastOperator = cigarElement.getOperator(); + if (cigarElement.getOperator().consumesReferenceBases() || cigarElement.getOperator() == CigarOperator.SOFT_CLIP || cigarElement.getOperator() == CigarOperator.HARD_CLIP) + shift = cigarElement.getLength(); + else + shift = 0; + } + return (lastOperator == CigarOperator.HARD_CLIP) ? stop-1 : stop+shift-1 ; + } + + } 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 0c3433eba..24fd48387 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -299,8 +299,8 @@ public class ReadUtils { */ public static ReadAndIntervalOverlap getReadAndIntervalOverlapType(GATKSAMRecord read, GenomeLoc interval) { - int sStart = getRefCoordSoftUnclippedStart(read); - int sStop = getRefCoordSoftUnclippedEnd(read); + int sStart = read.getSoftStart(); + int sStop = read.getSoftEnd(); int uStart = read.getUnclippedStart(); int uStop = read.getUnclippedEnd(); @@ -334,40 +334,6 @@ public class ReadUtils { return ReadAndIntervalOverlap.OVERLAP_RIGHT; } - - @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) - public static int getRefCoordSoftUnclippedStart(GATKSAMRecord read) { - int start = read.getUnclippedStart(); - for (CigarElement cigarElement : read.getCigar().getCigarElements()) { - if (cigarElement.getOperator() == CigarOperator.HARD_CLIP) - start += cigarElement.getLength(); - else - break; - } - return start; - } - - @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) - public static int getRefCoordSoftUnclippedEnd(GATKSAMRecord read) { - int stop = read.getUnclippedStart(); - - if (readIsEntirelyInsertion(read)) - return stop; - - int shift = 0; - CigarOperator lastOperator = null; - for (CigarElement cigarElement : read.getCigar().getCigarElements()) { - stop += shift; - lastOperator = cigarElement.getOperator(); - if (cigarElement.getOperator().consumesReferenceBases() || cigarElement.getOperator() == CigarOperator.SOFT_CLIP || cigarElement.getOperator() == CigarOperator.HARD_CLIP) - shift = cigarElement.getLength(); - else - shift = 0; - } - return (lastOperator == CigarOperator.HARD_CLIP) ? stop-1 : stop+shift-1 ; - } - - /** * Pre-processes the results of getReadCoordinateForReferenceCoordinate(GATKSAMRecord, int) in case it falls in * a deletion following the typical clipping needs. If clipping the left tail (beginning of the read) returns @@ -407,14 +373,14 @@ public class ReadUtils { * @param refCoord * @return the read coordinate corresponding to the requested reference coordinate. (see warning!) */ - @Requires({"refCoord >= getRefCoordSoftUnclippedStart(read)", "refCoord <= getRefCoordSoftUnclippedEnd(read)"}) + @Requires({"refCoord >= read.getSoftStart()", "refCoord <= read.getSoftEnd()"}) @Ensures({"result.getFirst() >= 0", "result.getFirst() < read.getReadLength()"}) public static Pair getReadCoordinateForReferenceCoordinate(GATKSAMRecord read, int refCoord) { int readBases = 0; int refBases = 0; boolean fallsInsideDeletion = false; - int goal = refCoord - getRefCoordSoftUnclippedStart(read); // The goal is to move this many reference bases + int goal = refCoord - read.getSoftStart(); // The goal is to move this many reference bases boolean goalReached = refBases == goal; Iterator cigarElementIterator = read.getCigar().getCigarElements().iterator(); diff --git a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java index e228762b7..d1d5ddd8f 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java @@ -32,7 +32,6 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import org.broadinstitute.sting.utils.sam.ReadUtils; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -90,13 +89,13 @@ public class ReadClipperUnitTest extends BaseTest { int alnStart = read.getAlignmentStart(); int alnEnd = read.getAlignmentEnd(); for (int i=alnStart; i<=alnEnd; i++) { - if (ReadUtils.getRefCoordSoftUnclippedStart(read) == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side + if (read.getSoftStart() == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipByReferenceCoordinates(alnStart, i); if (!clipLeft.isEmpty()) Assert.assertTrue(clipLeft.getAlignmentStart() >= i + 1, String.format("Clipped alignment start (%d) is less the expected (%d): %s -> %s", clipLeft.getAlignmentStart(), i + 1, read.getCigarString(), clipLeft.getCigarString())); } - if (ReadUtils.getRefCoordSoftUnclippedEnd(read) == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side + if (read.getSoftEnd() == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReferenceCoordinates(i, alnEnd); if (!clipRight.isEmpty() && clipRight.getAlignmentStart() <= clipRight.getAlignmentEnd()) // alnStart > alnEnd if the entire read is a soft clip now. We can't test those. Assert.assertTrue(clipRight.getAlignmentEnd() <= i - 1, String.format("Clipped alignment end (%d) is greater than expected (%d): %s -> %s", clipRight.getAlignmentEnd(), i - 1, read.getCigarString(), clipRight.getCigarString())); @@ -111,7 +110,7 @@ public class ReadClipperUnitTest extends BaseTest { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); int alnStart = read.getAlignmentStart(); int alnEnd = read.getAlignmentEnd(); - if (ReadUtils.getRefCoordSoftUnclippedStart(read) == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side + if (read.getSoftStart() == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side for (int i=alnStart; i<=alnEnd; i++) { GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipByReferenceCoordinates(alnStart, i); if (!clipLeft.isEmpty()) @@ -127,7 +126,7 @@ public class ReadClipperUnitTest extends BaseTest { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); int alnStart = read.getAlignmentStart(); int alnEnd = read.getAlignmentEnd(); - if (ReadUtils.getRefCoordSoftUnclippedEnd(read) == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side + if (read.getSoftEnd() == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side for (int i=alnStart; i<=alnEnd; i++) { GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReferenceCoordinates(i, alnEnd); if (!clipRight.isEmpty() && clipRight.getAlignmentStart() <= clipRight.getAlignmentEnd()) // alnStart > alnEnd if the entire read is a soft clip now. We can't test those. From 731a4634152e682a61d7acd268c3656ae8b2d1c0 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 20 Dec 2011 17:44:21 -0500 Subject: [PATCH 346/380] Updated IntegrationTests with new adaptor clipper phew! --- .../walkers/PileupWalkerIntegrationTest.java | 5 ++- .../VariantAnnotatorIntegrationTest.java | 12 +++--- .../CallableLociWalkerIntegrationTest.java | 8 ++-- .../DepthOfCoverageB36IntegrationTest.java | 15 +++---- .../DepthOfCoverageIntegrationTest.java | 40 +++++++++---------- .../UnifiedGenotyperIntegrationTest.java | 22 +++++----- .../RecalibrationWalkersIntegrationTest.java | 30 +++++++------- .../sting/utils/sam/ReadUtilsUnitTest.java | 14 ------- 8 files changed, 69 insertions(+), 77 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java index a01a3f281..8bea5385a 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java @@ -29,9 +29,12 @@ public class PileupWalkerIntegrationTest extends WalkerTest { String gatk_args = "-T Pileup -I " + validationDataLocation + "OV-0930.normal.chunk.bam " + "-R " + hg18Reference + " -show_indels -o %s"; - String expected_md5="06eedc2e7927650961d99d703f4301a4"; + String expected_md5="da2a02d02abac9de14cc4b187d8595a1"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args,1,Arrays.asList(expected_md5)); executeTest("Testing the extended pileup with indel records included on a small chunk of Ovarian dataset with 20 indels (1 D, 19 I)", spec); + // before Adaptor clipping + // String expected_md5="06eedc2e7927650961d99d703f4301a4"; + } } 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 245fda3c7..83c3d3a1e 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("fbb656369eaa48153d127bd12db59d8f")); + Arrays.asList("ac5f409856a1b79316469733e62abb91")); 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("2977bb30c8b84a5f4094fe6090658561")); + Arrays.asList("f9aa7bee5a61ac1a9187d0cf1e8af471")); executeTest("test file has annotations, asking for annotations, #2", spec); } @@ -66,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("42dd979a0a931c18dc9be40308bac321")); + Arrays.asList("6f27fd863b6718d59d2a2d8e2a20bcae")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -74,7 +74,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("0948cd1dba7d61f283cc4cf2a7757d92")); + Arrays.asList("40bbd3d5a2397a007c0e74211fb33433")); executeTest("test file doesn't have annotations, asking for annotations, #2", spec); } @@ -82,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("477eac07989593b58bb361f3429c085a")); + Arrays.asList("40622d39072b298440a77ecc794116e7")); executeTest("test exclude annotations", spec); } @@ -90,7 +90,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("062155edec46a8c52243475fbf3a2943")); + Arrays.asList("31faae1bc588d195ff553cf6c47fabfa")); executeTest("test overwriting header", spec); } 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 3783525d1..186e5c3b7 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 @@ -32,13 +32,13 @@ import java.util.Arrays; public class CallableLociWalkerIntegrationTest extends WalkerTest { final static String commonArgs = "-R " + b36KGReference + " -T CallableLoci -I " + validationDataLocation + "/NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s"; - final static String SUMMARY_MD5 = "ed4c255bb78313b8e7982127caf3d6c4"; + final static String SUMMARY_MD5 = "cd597a8dae35c226a2cb110b1c9f32d5"; @Test public void testCallableLociWalkerBed() { String gatk_args = commonArgs + " -format BED -L 1:10,000,000-11,000,000 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, - Arrays.asList("884c9c2d96419d990a708d2bd98fcefa", SUMMARY_MD5)); + Arrays.asList("c86ac1ef404c11d5e5452e020c8f7ce9", SUMMARY_MD5)); executeTest("formatBed", spec); } @@ -46,7 +46,7 @@ public class CallableLociWalkerIntegrationTest extends WalkerTest { public void testCallableLociWalkerPerBase() { String gatk_args = commonArgs + " -format STATE_PER_BASE -L 1:10,000,000-11,000,000 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, - Arrays.asList("fb4524f8b3b213060c0c5b85362b5902", SUMMARY_MD5)); + Arrays.asList("d8536a55fe5f6fdb1ee6c9511082fdfd", SUMMARY_MD5)); executeTest("format_state_per_base", spec); } @@ -62,7 +62,7 @@ public class CallableLociWalkerIntegrationTest extends WalkerTest { public void testCallableLociWalker3() { String gatk_args = commonArgs + " -format BED -L 1:10,000,000-11,000,000 -minDepth 10 -maxDepth 100 --minBaseQuality 10 --minMappingQuality 20 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, - Arrays.asList("86bd1a5f79356b3656412c4b1c60709a", "6fefb144a60b89c27293ce5ca6e10e6a")); + Arrays.asList("bc966060184bf4605a31da7fe383464e", "d624eda8f6ed14b9251ebeec73e37867")); executeTest("formatBed lots of arguments", spec); } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java index 043b2eaf2..f4cd4968b 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java @@ -61,13 +61,14 @@ public class DepthOfCoverageB36IntegrationTest extends WalkerTest { File baseOutputFile = this.createTempFile("depthofcoveragemapq0",".tmp"); spec.setOutputFileLocation(baseOutputFile); - spec.addAuxFile("f39af6ad99520fd4fb27b409ab0344a0",baseOutputFile); - spec.addAuxFile("6b15f5330414b6d4e2f6caea42139fa1", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); - spec.addAuxFile("cc6640d82077991dde8a2b523935cdff", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); + spec.addAuxFile("5b6c16a1c667c844882e9dce71454fc4",baseOutputFile); + spec.addAuxFile("fc161ec1b61dc67bc6a5ce36cb2d02c9", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); + spec.addAuxFile("89321bbfb76a4e1edc0905d50503ba1f", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); spec.addAuxFile("0fb627234599c258a3fee1b2703e164a", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); - spec.addAuxFile("cb73a0fa0cee50f1fb8f249315d38128", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); - spec.addAuxFile("347b47ef73fbd4e277704ddbd7834f69", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); - spec.addAuxFile("4ec920335d4b9573f695c39d62748089", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); + spec.addAuxFile("4dd16b659065e331ed4bd3ab0dae6c1b", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); + spec.addAuxFile("2be0c18b501f4a3d8c5e5f99738b4713", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); + spec.addAuxFile("5a26ef61f586f58310812580ce842462", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); + execute("testMapQ0Only",spec); } @@ -83,7 +84,7 @@ public class DepthOfCoverageB36IntegrationTest extends WalkerTest { File baseOutputFile = this.createTempFile("testManySamples",".tmp"); spec.setOutputFileLocation(baseOutputFile); - spec.addAuxFile("c9561b52344536d2b06ab97b0bb1a234",baseOutputFile); + spec.addAuxFile("d73fa1fc492f7dcc1d75056f8c12c92a",baseOutputFile); execute("testLotsOfSamples",spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java index 646fb5e77..9d6638d53 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java @@ -55,26 +55,26 @@ public class DepthOfCoverageIntegrationTest extends WalkerTest { spec.setOutputFileLocation(baseOutputFile); // now add the expected files that get generated - spec.addAuxFile("423571e4c05e7934322172654ac6dbb7", baseOutputFile); - spec.addAuxFile("9df5e7e07efeb34926c94a724714c219", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_counts")); - spec.addAuxFile("229b9b5bc2141c86dbc69c8acc9eba6a", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_proportions")); - spec.addAuxFile("9cd395f47b329b9dd00ad024fcac9929", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_statistics")); - spec.addAuxFile("471c34ad2e4f7228efd20702d5941ba9", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); - spec.addAuxFile("9667c77284c2c08e647b162d0e9652d4", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_statistics")); - spec.addAuxFile("5a96c75f96d6fa6ee617451d731dae37", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_summary")); - spec.addAuxFile("b82846df660f0aac8429aec57c2a62d6", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_counts")); - spec.addAuxFile("d32a8c425fadcc4c048bd8b48d0f61e5", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_proportions")); - spec.addAuxFile("7b9d0e93bf5b5313995be7010ef1f528", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_statistics")); - spec.addAuxFile("2aae346204c5f15517158da8e61a6c16", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_summary")); - spec.addAuxFile("e70952f241eebb9b5448f2e7cb288131", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_statistics")); - spec.addAuxFile("054ed1e184f46d6a170dc9bf6524270c", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_summary")); - spec.addAuxFile("d53431022f7387fe9ac47814ab1fcd88", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); - spec.addAuxFile("a395dafde101971d2b9e5ddb6cd4b7d0", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); - spec.addAuxFile("df0ba76e0e6082c0d29fcfd68efc6b77", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); - spec.addAuxFile("e013cb5b11b0321a81c8dbd7c1863787", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); - spec.addAuxFile("661160f571def8c323345b5859cfb9da", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); - spec.addAuxFile("c95a7a6840334cadd0e520939615c77b", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); - + spec.addAuxFile("19e862f7ed3de97f2569803f766b7433", baseOutputFile); + spec.addAuxFile("c64cc5636d4880b80b71169ed1832cd7", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_counts")); + spec.addAuxFile("1a8ba07a60e55f9fdadc89d00b1f3394", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_proportions")); + spec.addAuxFile("0075cead73a901e3a9d07c5d9c2b75f4", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_statistics")); + spec.addAuxFile("d757be2f953f893e66eff1ef1f0fff4e", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); + spec.addAuxFile("de08996729c774590d6a4954c906fe84", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_statistics")); + spec.addAuxFile("58ad39b100d1f2af7d119f28ba626bfd", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_summary")); + spec.addAuxFile("0b4ce6059e6587ae5a986afbbcc7d783", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_counts")); + spec.addAuxFile("adc2b2babcdd72a843878acf2d510ca7", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_proportions")); + spec.addAuxFile("884281c139241c5db3c9f90e8684d084", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_statistics")); + spec.addAuxFile("b90636cad74ff4f6b9ff9a596e145bd6", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_summary")); + spec.addAuxFile("ad540b355ef90c566bebaeabd70026d2", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_statistics")); + spec.addAuxFile("27fe09a02a5b381e0ed633587c0f4b23", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_summary")); + spec.addAuxFile("5fcd53b4bd167b5e6d5f92329cf8678e", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); + spec.addAuxFile("7a2a19e54f73a8e07de2f020f1f913dd", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); + spec.addAuxFile("852a079c5e9e93e7daad31fd6a9f4a49", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); + spec.addAuxFile("0828762842103edfaf115ef4e50809c6", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); + spec.addAuxFile("5c5aeb28419bba1decb17f8a166777f2", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); + spec.addAuxFile("e5fd6216b3d6a751f3a90677b4e5bf3c", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); + execute("testBaseOutputNoFiltering",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 e049af064..ba33108cf 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 @@ -28,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("a2d3839c4ebb390b0012d495e4e53b3a")); + Arrays.asList("66ed60c6c1190754abd8a0a9d1d8d61e")); executeTest("test MultiSample Pilot1", spec); } @@ -129,8 +129,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testOutputParameter() { HashMap e = new HashMap(); e.put( "-sites_only", "44f3b5b40e6ad44486cddfdb7e0bfcd8" ); - e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "d971d392956aea69c3707da64d24485b" ); - e.put( "--output_mode EMIT_ALL_SITES", "21993e71ca1a06a0d1f11d58e3cc26c3" ); + e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "42e4ea7878ef8d96215accb3ba4e97b7" ); + e.put( "--output_mode EMIT_ALL_SITES", "e0443c720149647469f2a2f3fb73942f" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -189,7 +189,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("f0fbe472f155baf594b1eeb58166edef")); + Arrays.asList("2b2729414ae855d390e7940956745bce")); executeTest(String.format("test multiple technologies"), spec); } @@ -208,7 +208,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -L 1:10,000,000-10,100,000" + " -baq CALCULATE_AS_NECESSARY", 1, - Arrays.asList("8c87c749a7bb5a76ed8504d4ec254272")); + Arrays.asList("95c6120efb92e5a325a5cec7d77c2dab")); executeTest(String.format("test calling with BAQ"), spec); } @@ -227,7 +227,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("a64d2e65b5927260e4ce0d948760cc5c")); + Arrays.asList("d87ce4b405d4f7926d1c36aee7053975")); executeTest(String.format("test indel caller in SLX"), spec); } @@ -255,7 +255,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("69107157632714150fc068d412e31939")); + Arrays.asList("c5989e5d67d9e5fe8c5c956f12a975da")); executeTest(String.format("test indel calling, multiple technologies"), spec); } @@ -265,7 +265,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("4ffda07590e06d58ed867ae326d74b2d")); + Arrays.asList("daca0741278de32e507ad367e67753b6")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -275,7 +275,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("45633d905136c86e9d3f90ce613255e5")); + Arrays.asList("0ccc4e876809566510429c64adece2c7")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -285,7 +285,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("8bd5028cf294850b8a95b3c0af23d728")); + Arrays.asList("cff6dd0f4eb1ef0b6fc476da6ffead19")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -294,7 +294,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("814dcd66950635a870602d90a1618cce")); + Arrays.asList("1e2a4aab26e9ab0dae709d33a669e036")); executeTest("test MultiSample Phase1 indels with complicated records", spec4); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java index cbcd5835f..b4b1f7b8e 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java @@ -31,10 +31,11 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @DataProvider(name = "cctestdata") public Object[][] createCCTestData() { - new CCTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "5a52b00d9794d27af723bcf93366681e" ); + + new CCTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "9469d6b65880abe4e5babc1c1a69889d" ); new CCTest( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "17d4b8001c982a70185e344929cf3941"); - new CCTest( validationDataLocation + "NA12873.454.SRP000031.2009_06.chr1.10_20mb.bam", "714e65d6cb51ae32221a77ce84cbbcdc" ); - new CCTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "64e9f17a1cf6fc04c1f2717c2d2eca67" ); + new CCTest( validationDataLocation + "NA12873.454.SRP000031.2009_06.chr1.10_20mb.bam", "36c0c467b6245c2c6c4e9c956443a154" ); + new CCTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "ed15f8bf03bb2ea9b7c26844be829c0d" ); return CCTest.getTests(CCTest.class); } @@ -88,10 +89,11 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @DataProvider(name = "trtestdata") public Object[][] createTRTestData() { - new TRTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "2864f231fab7030377f3c8826796e48f" ); + new TRTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "f020725d9f75ad8f1c14bfae056e250f" ); new TRTest( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "d04cf1f6df486e45226ebfbf93a188a5"); - new TRTest( validationDataLocation + "NA12873.454.SRP000031.2009_06.chr1.10_20mb.bam", "74314e5562c1a65547bb0edaacffe602" ); - new TRTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "2a37c6001826bfabf87063b1dfcf594f" ); + new TRTest( validationDataLocation + "NA12873.454.SRP000031.2009_06.chr1.10_20mb.bam", "b2f4757bc47cf23bd9a09f756c250787" ); + new TRTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "313a21a8a88e3460b6e71ec5ffc50f0f" ); + return TRTest.getTests(TRTest.class); } @@ -121,7 +123,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesUseOriginalQuals() { HashMap e = new HashMap(); - e.put( validationDataLocation + "originalQuals.1kg.chr1.1-1K.bam", "278846c55d97bd9812b758468a83f559"); + e.put( validationDataLocation + "originalQuals.1kg.chr1.1-1K.bam", "bd8288b1fc7629e2e8c2cf7f65fefa8f"); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -145,7 +147,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testTableRecalibratorMaxQ70() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "2864f231fab7030377f3c8826796e48f" ); + e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "f020725d9f75ad8f1c14bfae056e250f" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -174,7 +176,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesSolidIndelsRemoveRefBias() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "8379f24cf5312587a1f92c162ecc220f" ); + e.put( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "1f643bca090478ba68aac88db835a629" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -200,7 +202,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testTableRecalibratorSolidIndelsRemoveRefBias() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "2ad4c17ac3ed380071137e4e53a398a5" ); + e.put( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "613fb2bbe01af8cbe6a188a10c1582ca" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -228,7 +230,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesBED() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "b460478d9683e827784e42bc352db8bb"); + e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "b00e99219aeafe2516c6232b7d6a0a00"); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -252,7 +254,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesVCFPlusDBsnp() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "9131d96f39badbf9753653f55b148012"); + e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "7b92788ce92f49415af3a75a2e4a2b33"); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -280,7 +282,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesNoIndex() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.noindex.bam", "8993d32df5cb66c7149f59eccbd57f4c" ); + e.put( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.noindex.bam", "f34f7141351a5dbf9664c67260f94e96" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -306,7 +308,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testTableRecalibratorNoIndex() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.noindex.bam", "5f913c98ca99754902e9d34f99df468f" ); + e.put( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.noindex.bam", "13c83656567cee9e93bda9874ee80234" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); diff --git a/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java index 3878cbfa9..e9269ff48 100755 --- a/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java @@ -33,20 +33,6 @@ public class ReadUtilsUnitTest extends BaseTest { reducedRead.setAttribute(GATKSAMRecord.REDUCED_READ_CONSENSUS_TAG, REDUCED_READ_COUNTS_TAG); } - private void testReadBasesAndQuals(GATKSAMRecord read, int expectedStart, int expectedStop) { - SAMRecord clipped = ReadUtils.hardClipBases(read, expectedStart, expectedStop - 1, null); - String expectedBases = BASES.substring(expectedStart, expectedStop); - String expectedQuals = QUALS.substring(expectedStart, expectedStop); - Assert.assertEquals(clipped.getReadBases(), expectedBases.getBytes(), "Clipped bases not those expected"); - Assert.assertEquals(clipped.getBaseQualityString(), expectedQuals, "Clipped quals not those expected"); - } - - @Test public void testNoClip() { testReadBasesAndQuals(read, 0, 4); } - @Test public void testClip1Front() { testReadBasesAndQuals(read, 1, 4); } - @Test public void testClip2Front() { testReadBasesAndQuals(read, 2, 4); } - @Test public void testClip1Back() { testReadBasesAndQuals(read, 0, 3); } - @Test public void testClip2Back() { testReadBasesAndQuals(read, 0, 2); } - @Test public void testReducedReads() { Assert.assertFalse(read.isReducedRead(), "isReducedRead is false for normal read"); From d50f9b98bbad3f37b6ca94e1e8205eb52669aa80 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Tue, 20 Dec 2011 21:42:30 -0500 Subject: [PATCH 347/380] Make sure that the temporary ReadWalker performance improvement hack works well in the binary release, jic GATK 1.4 arrives before I get a Picard patch. --- build.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.xml b/build.xml index 232b074f6..04cb243d4 100644 --- a/build.xml +++ b/build.xml @@ -847,7 +847,6 @@ - @@ -858,6 +857,7 @@ + @@ -1118,6 +1118,11 @@ + + + + + From 3358c132a84105fa1749746ffed8d180f0a0929a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 21 Dec 2011 15:14:05 -0500 Subject: [PATCH 351/380] Updating the MD5s Clipping adaptor boundaries changed the results of CountCovariates which affected the PPP output. a few more loci were visible to locus walkers. --- .../sting/queue/pipeline/PacbioProcessingPipelineTest.scala | 4 ++-- .../pipeline/examples/ExampleCountLociPipelineTest.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala index 50aa66367..1278b6e16 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PacbioProcessingPipelineTest.scala @@ -29,7 +29,7 @@ import org.broadinstitute.sting.BaseTest class PacbioProcessingPipelineTest { @Test - def testBAM { + def testPacbioProcessingPipeline { val testOut = "exampleBAM.recal.bam" val spec = new PipelineTestSpec spec.name = "pacbioProcessingPipeline" @@ -40,7 +40,7 @@ class PacbioProcessingPipelineTest { " -blasr ", " -test ", " -D " + BaseTest.testDir + "exampleDBSNP.vcf").mkString - spec.fileMD5s += testOut -> "f0adce660b55cb91d5f987f9a145471e" + spec.fileMD5s += testOut -> "cf147e7f56806598371f8d5d6794b852" PipelineTest.executeTest(spec) } } diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala index f657e4be1..e737e52ea 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/examples/ExampleCountLociPipelineTest.scala @@ -39,7 +39,7 @@ class ExampleCountLociPipelineTest { " -R " + BaseTest.testDir + "exampleFASTA.fasta", " -I " + BaseTest.testDir + "exampleBAM.bam", " -o " + testOut).mkString - spec.fileMD5s += testOut -> "67823e4722495eb10a5e4c42c267b3a6" + spec.fileMD5s += testOut -> "ade93df31a6150321c1067e749cae9be" PipelineTest.executeTest(spec) } -} +} \ No newline at end of file From 6d260ec6ae0b3bfb65be9408920a6acb58d8e502 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 22 Dec 2011 15:40:59 -0500 Subject: [PATCH 353/380] Start printing traversal stats after 30 seconds. I can't stand waiting 2 minutes. --- .../sting/gatk/GenomeAnalysisEngine.java | 2 +- .../reference/ReferenceDataSource.java | 57 +++++++++++++------ .../gatk/traversals/TraversalEngine.java | 2 +- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index d37116215..f6956f530 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -469,7 +469,7 @@ public class GenomeAnalysisEngine { throw new ReviewedStingException("Unable to determine walker type for walker " + walker.getClass().getName()); } else { - final int SHARD_SIZE = walker instanceof RodWalker ? 1000000 : 100000; + final int SHARD_SIZE = walker instanceof RodWalker ? 1000000 : 100000; // TODO -- make it a multiple of 16K if(intervals == null) return referenceDataSource.createShardsOverEntireReference(readsDataSource,genomeLocParser,SHARD_SIZE); else 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 2c33a19b8..b6c6a7564 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 @@ -30,7 +30,6 @@ 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; @@ -46,7 +45,6 @@ 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; /** @@ -230,26 +228,51 @@ public class ReferenceDataSource { /** * 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. + * @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 targetShardSize the suggested - and maximum - shard size which can be used to create this list; we will merge intervals greedily so that we generate shards up to but not greater than the target size. * @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(); + public Iterable createShardsOverIntervals(final SAMDataSource readsDataSource, final GenomeLocSortedSet intervals, final int targetShardSize) { + final List shards = new ArrayList(); + final GenomeLocParser parser = intervals.getGenomeLocParser(); + GenomeLoc currentInterval = null; + 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()); + // if the next interval is too big, we can safely shard currentInterval and then break down this one + if (interval.size() > targetShardSize) { + if (currentInterval != null) + shards.add(createShardFromInterval(currentInterval, readsDataSource, parser)); + currentInterval = interval; + while(currentInterval.size() > targetShardSize) { + final GenomeLoc partialInterval = parser.createGenomeLoc(currentInterval.getContig(), currentInterval.getStart(), currentInterval.getStart()+targetShardSize-1); + shards.add(createShardFromInterval(partialInterval, readsDataSource, parser)); + currentInterval = parser.createGenomeLoc(currentInterval.getContig(),currentInterval.getStart()+targetShardSize,currentInterval.getStop()); + } + } + // otherwise, we need to check whether we can merge this interval with currentInterval (and either shard currentInterval or merge accordingly) + else { + if (currentInterval == null) { + currentInterval = interval; + } + else if (currentInterval.compareContigs(interval) != 0 || interval.getStop() - currentInterval.getStart() + 1 > targetShardSize) { + shards.add(createShardFromInterval(currentInterval, readsDataSource, parser)); + currentInterval = interval; + } else { + currentInterval = parser.createGenomeLoc(currentInterval.getContig(),currentInterval.getStart(),interval.getStop()); + } } - shards.add(new LocusShard(intervals.getGenomeLocParser(), - readsDataSource, - Collections.singletonList(interval), - null)); } + if (currentInterval != null) + shards.add(createShardFromInterval(currentInterval, readsDataSource, parser)); return shards; } + + private static Shard createShardFromInterval(final GenomeLoc interval, final SAMDataSource readsDataSource, final GenomeLocParser parser) { + System.out.println("Adding shard " + interval); + return new LocusShard(parser, + readsDataSource, + Collections.singletonList(interval), + null); + } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java index fd691735f..4ef255524 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java @@ -121,7 +121,7 @@ public abstract class TraversalEngine,Provide private static final int PRINT_PROGRESS_CHECK_FREQUENCY_IN_CYCLES = 1000; private int printProgressCheckCounter = 0; private long lastProgressPrintTime = -1; // When was the last time we printed progress log? - private long MIN_ELAPSED_TIME_BEFORE_FIRST_PROGRESS = 120 * 1000; // in milliseconds + private long MIN_ELAPSED_TIME_BEFORE_FIRST_PROGRESS = 30 * 1000; // in milliseconds private long PROGRESS_PRINT_FREQUENCY = 10 * 1000; // in milliseconds private final double TWO_HOURS_IN_SECONDS = 2.0 * 60.0 * 60.0; private final double TWELVE_HOURS_IN_SECONDS = 12.0 * 60.0 * 60.0; From a815e875a8b8ee3f2aa5c7eedf5bc37b32760c07 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 22 Dec 2011 15:49:11 -0500 Subject: [PATCH 354/380] Removing debugging output --- .../sting/gatk/datasources/reference/ReferenceDataSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b6c6a7564..be33a5691 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 @@ -269,7 +269,7 @@ public class ReferenceDataSource { } private static Shard createShardFromInterval(final GenomeLoc interval, final SAMDataSource readsDataSource, final GenomeLocParser parser) { - System.out.println("Adding shard " + interval); + //logger.debug("Adding shard " + interval); return new LocusShard(parser, readsDataSource, Collections.singletonList(interval), From 8762313a0dffa7f04c8fd40e3c0edf06a3209657 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 22 Dec 2011 20:54:35 -0500 Subject: [PATCH 355/380] Better TODO message --- .../org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java | 5 ++++- 1 file changed, 4 insertions(+), 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 f6956f530..f954d7650 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -469,7 +469,10 @@ public class GenomeAnalysisEngine { throw new ReviewedStingException("Unable to determine walker type for walker " + walker.getClass().getName()); } else { - final int SHARD_SIZE = walker instanceof RodWalker ? 1000000 : 100000; // TODO -- make it a multiple of 16K + // TODO -- Determine what the ideal shard size should be here. Matt suggested that a multiple of 16K might work well + // TODO -- (because of how VCF indexes work), but my empirical experience has been simply that the larger the shard + // TODO -- size the more efficient the traversal (at least for RODWalkers). Keeping the previous values for now. [EB] + final int SHARD_SIZE = walker instanceof RodWalker ? 1000000 : 100000; if(intervals == null) return referenceDataSource.createShardsOverEntireReference(readsDataSource,genomeLocParser,SHARD_SIZE); else From 24c84da60de5c8769e23a83231252bd646d2d258 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 23 Dec 2011 15:39:12 -0500 Subject: [PATCH 356/380] 'Fixing' the changes in ReferenceDataSource so that a shard properly contains a list of GenomeLocs instead of a single merged one. However, that uncovered a probable bug in the engine, so instead of letting this code fester unfixed in the build (affecting everyone in the group) I've decided to revert the previous (slow, but working) version and fix the engine in my own branch. --- .../reference/ReferenceDataSource.java | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) 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 be33a5691..4ecfe472d 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 @@ -45,6 +45,7 @@ import org.broadinstitute.sting.utils.file.FileSystemInabilityToLockException; import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; /** @@ -226,6 +227,28 @@ public class ReferenceDataSource { return shards; } + + 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; + } + + /** * 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. @@ -233,46 +256,50 @@ public class ReferenceDataSource { * @param targetShardSize the suggested - and maximum - shard size which can be used to create this list; we will merge intervals greedily so that we generate shards up to but not greater than the target size. * @return Creates a schedule for performing a traversal over the entire reference. */ +/* public Iterable createShardsOverIntervals(final SAMDataSource readsDataSource, final GenomeLocSortedSet intervals, final int targetShardSize) { final List shards = new ArrayList(); final GenomeLocParser parser = intervals.getGenomeLocParser(); - GenomeLoc currentInterval = null; + LinkedList currentIntervals = new LinkedList(); for(GenomeLoc interval: intervals) { // if the next interval is too big, we can safely shard currentInterval and then break down this one if (interval.size() > targetShardSize) { - if (currentInterval != null) - shards.add(createShardFromInterval(currentInterval, readsDataSource, parser)); - currentInterval = interval; - while(currentInterval.size() > targetShardSize) { - final GenomeLoc partialInterval = parser.createGenomeLoc(currentInterval.getContig(), currentInterval.getStart(), currentInterval.getStart()+targetShardSize-1); - shards.add(createShardFromInterval(partialInterval, readsDataSource, parser)); - currentInterval = parser.createGenomeLoc(currentInterval.getContig(),currentInterval.getStart()+targetShardSize,currentInterval.getStop()); + if (!currentIntervals.isEmpty()) + shards.add(createShardFromInterval(currentIntervals, readsDataSource, parser)); + while(interval.size() > targetShardSize) { + final GenomeLoc partialInterval = parser.createGenomeLoc(interval.getContig(), interval.getStart(), interval.getStart()+targetShardSize-1); + shards.add(createShardFromInterval(Collections.singletonList(partialInterval), readsDataSource, parser)); + interval = parser.createGenomeLoc(interval.getContig(), interval.getStart() + targetShardSize, interval.getStop()); } + currentIntervals = new LinkedList(); + currentIntervals.add(interval); } // otherwise, we need to check whether we can merge this interval with currentInterval (and either shard currentInterval or merge accordingly) else { - if (currentInterval == null) { - currentInterval = interval; + if (currentIntervals.isEmpty()) { + currentIntervals.add(interval); } - else if (currentInterval.compareContigs(interval) != 0 || interval.getStop() - currentInterval.getStart() + 1 > targetShardSize) { - shards.add(createShardFromInterval(currentInterval, readsDataSource, parser)); - currentInterval = interval; - } else { - currentInterval = parser.createGenomeLoc(currentInterval.getContig(),currentInterval.getStart(),interval.getStop()); + else { + if (currentIntervals.getLast().compareContigs(interval) != 0 || interval.getStop() - currentIntervals.getLast().getStart() + 1 > targetShardSize) { + shards.add(createShardFromInterval(currentIntervals, readsDataSource, parser)); + currentIntervals = new LinkedList(); + } + currentIntervals.add(interval); } } } - if (currentInterval != null) - shards.add(createShardFromInterval(currentInterval, readsDataSource, parser)); + if (!currentIntervals.isEmpty()) + shards.add(createShardFromInterval(currentIntervals, readsDataSource, parser)); return shards; } - private static Shard createShardFromInterval(final GenomeLoc interval, final SAMDataSource readsDataSource, final GenomeLocParser parser) { + private static Shard createShardFromInterval(final List intervals, final SAMDataSource readsDataSource, final GenomeLocParser parser) { //logger.debug("Adding shard " + interval); return new LocusShard(parser, readsDataSource, - Collections.singletonList(interval), + intervals, null); } +*/ } From 506c0e9c9773135a46a3ad3740f1fb2e875eee83 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Fri, 23 Dec 2011 16:44:28 -0500 Subject: [PATCH 357/380] Disabling SnpEff support in the GATK and SnpEff annotation in the HybridSelectionPipeline SnpEff support will remain disabled until SnpEff 2.0.4 has been officially released and we've verified the quality of its annotations. --- .../broadinstitute/sting/gatk/walkers/annotator/SnpEff.java | 6 ++++++ .../walkers/annotator/VariantAnnotatorIntegrationTest.java | 4 ++-- .../walkers/genotyper/UnifiedGenotyperIntegrationTest.java | 2 +- .../walkers/varianteval/VariantEvalIntegrationTest.java | 2 +- 4 files changed, 10 insertions(+), 4 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 1956dac6c..5d215603a 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 @@ -204,6 +204,11 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio } public void initialize ( AnnotatorCompatibleWalker walker, GenomeAnalysisEngine toolkit, Set headerLines ) { + throw new UserException("SnpEff support is currently disabled in the GATK until SnpEff 2.0.4 is officially released " + + "due to a serious issue with SnpEff versions prior to 2.0.4. Please see this page for more details: " + + "http://www.broadinstitute.org/gsa/wiki/index.php/Adding_Genomic_Annotations_Using_SnpEff_and_VariantAnnotator"); + + /* // Make sure that we actually have a valid SnpEff rod binding (just in case the user specified -A SnpEff // without providing a SnpEff rod via --snpEffFile): validateRodBinding(walker.getSnpEffRodBinding()); @@ -223,6 +228,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio // mistaken in the future for a SnpEff output file: headerLines.add(new VCFHeaderLine(OUTPUT_VCF_HEADER_VERSION_LINE_KEY, snpEffVersionLine.getValue())); headerLines.add(new VCFHeaderLine(OUTPUT_VCF_HEADER_COMMAND_LINE_KEY, snpEffCommandLine.getValue())); + */ } public Map annotate ( RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc ) { 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 83c3d3a1e..fa0b62cfd 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 @@ -145,7 +145,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { } } - @Test + @Test(enabled = false) public void testSnpEffAnnotations() { WalkerTestSpec spec = new WalkerTestSpec( "-T VariantAnnotator -R " + hg19Reference + " -NO_HEADER -o %s -A SnpEff --variant " + @@ -157,7 +157,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { executeTest("Testing SnpEff annotations", spec); } - @Test + @Test(enabled = false) public void testSnpEffAnnotationsUnsupportedVersion() { WalkerTestSpec spec = new WalkerTestSpec( "-T VariantAnnotator -R " + hg19Reference + " -NO_HEADER -o %s -A SnpEff --variant " + 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 ba33108cf..3c6131d6c 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 @@ -298,7 +298,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { executeTest("test MultiSample Phase1 indels with complicated records", spec4); } - @Test + @Test(enabled = false) public void testSnpEffAnnotationRequestedWithoutRodBinding() { 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 " + 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 4e3d38c4f..3ef4e5e9f 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 @@ -14,7 +14,7 @@ public class VariantEvalIntegrationTest extends WalkerTest { private static String cmdRoot = "-T VariantEval" + " -R " + b36KGReference; - @Test + @Test(enabled = false) public void testFunctionClassWithSnpeff() { WalkerTestSpec spec = new WalkerTestSpec( buildCommandLine( From 35c41409a118eaf6405ca3dbfe199aeb8b3b2b96 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 23 Dec 2011 19:35:00 -0500 Subject: [PATCH 358/380] Better contracts and docs for the ReadClipper * Described the ReadClipper contract in the top of the class * Added contracts where applicable * Added descriptive information to all tools in the read clipper * Organized public members and static methods together with the same javadoc --- .../sting/utils/clipping/ReadClipper.java | 277 +++++++++++++----- 1 file changed, 202 insertions(+), 75 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java index d82472bae..5deee122f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java @@ -11,17 +11,53 @@ import java.util.ArrayList; import java.util.List; /** - * A simple collection of the clipping operations to apply to a read along with its read + * A comprehensive clipping tool. + * + * General Contract: + * - All clipping operations return a new read with the clipped bases requested, it never modifies the original read. + * - If a read is fully clipped, return an empty GATKSAMRecord, never null. + * - When hard clipping, add cigar operator H for every *reference base* removed (i.e. Matches, SoftClips and Deletions, but *not* insertions). See Hard Clipping notes for details. + * + * + * There are several types of clipping to use: + * + * Write N's: + * Change the bases to N's in the desired region. This can be applied anywhere in the read. + * + * Write Q0's: + * Change the quality of the bases in the desired region to Q0. This can be applied anywhere in the read. + * + * Write both N's and Q0's: + * Same as the two independent operations, put together. + * + * Soft Clipping: + * Do not change the read, just mark the reads as soft clipped in the Cigar String + * and adjust the alignment start and end of the read. + * + * Hard Clipping: + * Creates a new read without the hard clipped bases (and base qualities). The cigar string + * will be updated with the cigar operator H for every reference base removed (i.e. Matches, + * Soft clipped bases and deletions, but *not* insertions). This contract with the cigar + * is necessary to allow read.getUnclippedStart() / End() to recover the original alignment + * of the read (before clipping). + * */ public class ReadClipper { - GATKSAMRecord read; + final GATKSAMRecord read; boolean wasClipped; List ops = null; /** - * We didn't do any clipping work on this read, just leave everything as a default + * Initializes a ReadClipper object. * - * @param read + * You can set up your clipping operations using the addOp method. When you're ready to + * generate a new read with all the clipping operations, use clipRead(). + * + * Note: Use this if you want to set up multiple operations on the read using the ClippingOp + * class. If you just want to apply one of the typical modes of clipping, use the static + * clipping functions available in this class instead. + * + * @param read the read to clip */ public ReadClipper(final GATKSAMRecord read) { this.read = read; @@ -29,32 +65,55 @@ public class ReadClipper { } /** - * Add another clipping operation to apply to this read + * Add clipping operation to the read. * - * @param op + * You can add as many operations as necessary to this read before clipping. Beware that the + * order in which you add these operations matter. For example, if you hard clip the beginning + * of a read first then try to hard clip the end, the indices will have changed. Make sure you + * know what you're doing, otherwise just use the static functions below that take care of the + * ordering for you. + * + * Note: You only choose the clipping mode when you use clipRead() + * + * @param op a ClippingOp object describing the area you want to clip. */ public void addOp(ClippingOp op) { if (ops == null) ops = new ArrayList(); ops.add(op); } + /** + * Check the list of operations set up for this read. + * + * @return a list of the operations set up for this read. + */ public List getOps() { return ops; } + /** + * Check whether or not this read has been clipped. + * @return true if this read has produced a clipped read, false otherwise. + */ public boolean wasClipped() { return wasClipped; } + /** + * The original read. + * + * @return returns the read to be clipped (original) + */ public GATKSAMRecord getRead() { return read; } /** - * Return a new read corresponding to this.read that's been clipped according to ops, if any are present. + * Creates a new read that's been clipped according to ops and the chosen algorithm. + * The original read is unmodified. * - * @param algorithm - * @return + * @param algorithm What mode of clipping do you want to apply for the stacked operations. + * @return a new read with the clipping applied. */ public GATKSAMRecord clipRead(ClippingRepresentation algorithm) { if (ops == null) @@ -84,41 +143,47 @@ public class ReadClipper { } - - - // QUICK USE UTILITY FUNCTION - + /** + * Hard clips the left tail of a read up to (and including) refStop using reference + * coordinates. + * + * @param refStop the last base to be hard clipped in the left tail of the read. + * @return a new read, without the left tail. + */ + @Requires("!read.getReadUnmappedFlag()") // can't handle unmapped reads, as we're using reference coordinates to clip public GATKSAMRecord hardClipByReferenceCoordinatesLeftTail(int refStop) { return hardClipByReferenceCoordinates(-1, refStop); } + public static GATKSAMRecord hardClipByReferenceCoordinatesLeftTail(GATKSAMRecord read, int refStop) { + return (new ReadClipper(read)).hardClipByReferenceCoordinates(-1, refStop); + } + + + /** + * Hard clips the right tail of a read starting at (and including) refStart using reference + * coordinates. + * + * @param refStart refStop the first base to be hard clipped in the right tail of the read. + * @return a new read, without the right tail. + */ + @Requires("!read.getReadUnmappedFlag()") // can't handle unmapped reads, as we're using reference coordinates to clip public GATKSAMRecord hardClipByReferenceCoordinatesRightTail(int refStart) { return hardClipByReferenceCoordinates(refStart, -1); } - - @Requires("!read.getReadUnmappedFlag()") - protected GATKSAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { - int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart, ReadUtils.ClippingTail.RIGHT_TAIL); - int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop, ReadUtils.ClippingTail.LEFT_TAIL); - - if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) - return new GATKSAMRecord(read.getHeader()); - - if (start < 0 || stop > read.getReadLength() - 1) - throw new ReviewedStingException("Trying to clip before the start or after the end of a read"); - - if ( start > stop ) - throw new ReviewedStingException("START > STOP -- this should never happen -- call Mauricio!"); - - if ( start > 0 && stop < read.getReadLength() - 1) - throw new ReviewedStingException(String.format("Trying to clip the middle of the read: start %d, stop %d, cigar: %s", start, stop, read.getCigarString())); - - this.addOp(new ClippingOp(start, stop)); - GATKSAMRecord clippedRead = clipRead(ClippingRepresentation.HARDCLIP_BASES); - this.ops = null; - return clippedRead; + public static GATKSAMRecord hardClipByReferenceCoordinatesRightTail(GATKSAMRecord read, int refStart) { + return (new ReadClipper(read)).hardClipByReferenceCoordinates(refStart, -1); } + /** + * Hard clips a read using read coordinates. + * + * @param start the first base to clip (inclusive) + * @param stop the last base to clip (inclusive) + * @return a new read, without the clipped bases + */ + @Requires({"start >= 0 && stop <= read.getReadLength() - 1", // start and stop have to be within the read + "start == 0 || stop == read.getReadLength() - 1"}) // cannot clip the middle of the read public GATKSAMRecord hardClipByReadCoordinates(int start, int stop) { if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) return new GATKSAMRecord(read.getHeader()); @@ -126,8 +191,23 @@ public class ReadClipper { this.addOp(new ClippingOp(start, stop)); return clipRead(ClippingRepresentation.HARDCLIP_BASES); } + public static GATKSAMRecord hardClipByReadCoordinates(GATKSAMRecord read, int start, int stop) { + return (new ReadClipper(read)).hardClipByReadCoordinates(start, stop); + } - @Requires("left <= right") + + /** + * Hard clips both tails of a read. + * Left tail goes from the beginning to the 'left' coordinate (inclusive) + * Right tail goes from the 'right' coordinate (inclusive) until the end of the read + * + * @param left the coordinate of the last base to be clipped in the left tail (inclusive) + * @param right the coordinate of the first base to be clipped in the right tail (inclusive) + * @return a new read, without the clipped bases + */ + @Requires({"left <= right", // tails cannot overlap + "left >= read.getAlignmentStart()", // coordinate has to be within the mapped read + "right <= read.getAlignmentEnd()"}) // coordinate has to be within the mapped read public GATKSAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { if (read.isEmpty() || left == right) return new GATKSAMRecord(read.getHeader()); @@ -141,7 +221,20 @@ public class ReadClipper { ReadClipper clipper = new ReadClipper(leftTailRead); return clipper.hardClipByReferenceCoordinatesLeftTail(left); } + public static GATKSAMRecord hardClipBothEndsByReferenceCoordinates(GATKSAMRecord read, int left, int right) { + return (new ReadClipper(read)).hardClipBothEndsByReferenceCoordinates(left, right); + } + + /** + * Hard clips any contiguous tail (left, right or both) with base quality lower than lowQual. + * + * This function will look for low quality tails and hard clip them away. A low quality tail + * ends when a base has base quality greater than lowQual. + * + * @param lowQual every base quality lower than or equal to this in the tail of the read will be hard clipped + * @return a new read without low quality tails + */ public GATKSAMRecord hardClipLowQualEnds(byte lowQual) { if (read.isEmpty()) return read; @@ -154,7 +247,7 @@ public class ReadClipper { while (rightClipIndex >= 0 && quals[rightClipIndex] <= lowQual) rightClipIndex--; while (leftClipIndex < read.getReadLength() && quals[leftClipIndex] <= lowQual) leftClipIndex++; - // if the entire read should be clipped, then return an empty read. (--todo: maybe null is better? testing this for now) + // if the entire read should be clipped, then return an empty read. if (leftClipIndex > rightClipIndex) return (new GATKSAMRecord(read.getHeader())); @@ -166,7 +259,16 @@ public class ReadClipper { } return this.clipRead(ClippingRepresentation.HARDCLIP_BASES); } + public static GATKSAMRecord hardClipLowQualEnds(GATKSAMRecord read, byte lowQual) { + return (new ReadClipper(read)).hardClipLowQualEnds(lowQual); + } + + /** + * Will hard clip every soft clipped bases in the read. + * + * @return a new read without the soft clipped bases + */ public GATKSAMRecord hardClipSoftClippedBases () { if (read.isEmpty()) return read; @@ -200,7 +302,18 @@ public class ReadClipper { return clipRead(ClippingRepresentation.HARDCLIP_BASES); } + public static GATKSAMRecord hardClipSoftClippedBases (GATKSAMRecord read) { + return (new ReadClipper(read)).hardClipSoftClippedBases(); + } + + /** + * Checks if a read contains adaptor sequences. If it does, hard clips them out. + * + * Note: To see how a read is checked for adaptor sequence see ReadUtils.getAdaptorBoundary() + * + * @return a new read without adaptor sequence + */ public GATKSAMRecord hardClipAdaptorSequence () { final Integer adaptorBoundary = ReadUtils.getAdaptorBoundary(read); @@ -209,7 +322,16 @@ public class ReadClipper { return read.getReadNegativeStrandFlag() ? hardClipByReferenceCoordinatesLeftTail(adaptorBoundary) : hardClipByReferenceCoordinatesRightTail(adaptorBoundary); } + public static GATKSAMRecord hardClipAdaptorSequence (GATKSAMRecord read) { + return (new ReadClipper(read)).hardClipAdaptorSequence(); + } + + /** + * Hard clips any leading insertions in the read. Only looks at the beginning of the read, not the end. + * + * @return a new read without leading insertions + */ public GATKSAMRecord hardClipLeadingInsertions() { if (read.isEmpty()) return read; @@ -225,49 +347,54 @@ public class ReadClipper { } return clipRead(ClippingRepresentation.HARDCLIP_BASES); } - - public GATKSAMRecord revertSoftClippedBases() { - this.addOp(new ClippingOp(0, 0)); // UNSOFTCLIP_BASES doesn't need coordinates - return this.clipRead(ClippingRepresentation.REVERT_SOFTCLIPPED_BASES); - } - - - - // STATIC VERSIONS OF THE QUICK CLIPPING FUNCTIONS - - public static GATKSAMRecord hardClipByReferenceCoordinatesLeftTail(GATKSAMRecord read, int refStop) { - return (new ReadClipper(read)).hardClipByReferenceCoordinates(-1, refStop); - } - - public static GATKSAMRecord hardClipByReferenceCoordinatesRightTail(GATKSAMRecord read, int refStart) { - return (new ReadClipper(read)).hardClipByReferenceCoordinates(refStart, -1); - } - - public static GATKSAMRecord hardClipByReadCoordinates(GATKSAMRecord read, int start, int stop) { - return (new ReadClipper(read)).hardClipByReadCoordinates(start, stop); - } - - public static GATKSAMRecord hardClipBothEndsByReferenceCoordinates(GATKSAMRecord read, int left, int right) { - return (new ReadClipper(read)).hardClipBothEndsByReferenceCoordinates(left, right); - } - - public static GATKSAMRecord hardClipLowQualEnds(GATKSAMRecord read, byte lowQual) { - return (new ReadClipper(read)).hardClipLowQualEnds(lowQual); - } - - public static GATKSAMRecord hardClipSoftClippedBases (GATKSAMRecord read) { - return (new ReadClipper(read)).hardClipSoftClippedBases(); - } - - public static GATKSAMRecord hardClipAdaptorSequence (GATKSAMRecord read) { - return (new ReadClipper(read)).hardClipAdaptorSequence(); - } - public static GATKSAMRecord hardClipLeadingInsertions(GATKSAMRecord read) { return (new ReadClipper(read)).hardClipLeadingInsertions(); } + + /** + * Turns soft clipped bases into matches + * + * @return a new read with every soft clip turned into a match + */ + public GATKSAMRecord revertSoftClippedBases() { + this.addOp(new ClippingOp(0, 0)); // UNSOFTCLIP_BASES doesn't need coordinates + return this.clipRead(ClippingRepresentation.REVERT_SOFTCLIPPED_BASES); + } public static GATKSAMRecord revertSoftClippedBases(GATKSAMRecord read) { return (new ReadClipper(read)).revertSoftClippedBases(); } + + /** + * Generic functionality to hard clip a read, used internally by hardClipByReferenceCoordinatesLeftTail + * and hardClipByReferenceCoordinatesRightTail. Should not be used directly. + * + * @param refStart first base to clip (inclusive) + * @param refStop last base to clip (inclusive) + * @return a new read, without the clipped bases + */ + @Requires("!read.getReadUnmappedFlag()") // can't handle unmapped reads, as we're using reference coordinates to clip + protected GATKSAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { + int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart, ReadUtils.ClippingTail.RIGHT_TAIL); + int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop, ReadUtils.ClippingTail.LEFT_TAIL); + + if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) + return new GATKSAMRecord(read.getHeader()); + + if (start < 0 || stop > read.getReadLength() - 1) + throw new ReviewedStingException("Trying to clip before the start or after the end of a read"); + + if ( start > stop ) + throw new ReviewedStingException("START > STOP -- this should never happen -- call Mauricio!"); + + if ( start > 0 && stop < read.getReadLength() - 1) + throw new ReviewedStingException(String.format("Trying to clip the middle of the read: start %d, stop %d, cigar: %s", start, stop, read.getCigarString())); + + this.addOp(new ClippingOp(start, stop)); + GATKSAMRecord clippedRead = clipRead(ClippingRepresentation.HARDCLIP_BASES); + this.ops = null; + return clippedRead; + } + + } From 2130b39f33996c0cd19929a0b6d0c10adde7f221 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 26 Dec 2011 14:45:19 -0500 Subject: [PATCH 362/380] Found the bug in the engine: RodLocusView was using the wrong seek method so that it would only move to the first locus of a shard (and with multi-locus shards, this meant that we never processed RODs from the other positions). In fact, because the seek(Shard) method is extremely misleading and now no longer used, I think it's safer to delete it and make everyone use the much more transparent seek(GenomeLoc). Note that I have not re-enabled my improvements to the intervals accumulation of ReferenceDataSource because that inefficiency is still present downstream in RodLocusView; need to discuss those changes with Matt. --- .../gatk/datasources/providers/RodLocusView.java | 2 +- .../datasources/rmd/ReferenceOrderedDataSource.java | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java index c38b09334..54f8b44ed 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/providers/RodLocusView.java @@ -80,7 +80,7 @@ public class RodLocusView extends LocusView implements ReferenceOrderedView { // grab the ROD iterator from the data source, and compute the first location in this shard, forwarding // the iterator to immediately before it, so that it can be added to the merging iterator primed for // next() to return the first real ROD in this shard - LocationAwareSeekableRODIterator it = dataSource.seek(provider.getShard()); + LocationAwareSeekableRODIterator it = dataSource.seek(provider.getLocus()); it.seekForward(genomeLocParser.createGenomeLoc(loc.getContig(), loc.getStart()-1)); states.add(new ReferenceOrderedDataState(dataSource,it)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/rmd/ReferenceOrderedDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/rmd/ReferenceOrderedDataSource.java index 18679dd77..5b4be2fc6 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/rmd/ReferenceOrderedDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/rmd/ReferenceOrderedDataSource.java @@ -26,7 +26,6 @@ package org.broadinstitute.sting.gatk.datasources.rmd; import net.sf.samtools.SAMSequenceDictionary; import org.broadinstitute.sting.commandline.Tags; -import org.broadinstitute.sting.gatk.datasources.reads.Shard; import org.broadinstitute.sting.gatk.refdata.SeekableRODIterator; import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrack; import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrackBuilder; @@ -154,16 +153,6 @@ public class ReferenceOrderedDataSource { return (name.equals(fileDescriptor.getName()) && (type.getClass().isAssignableFrom(getType().getClass()))); } - /** - * Seek to the specified position and return an iterator through the data. - * @param shard Shard that points to the selected position. - * @return Iterator through the data. - */ - public LocationAwareSeekableRODIterator seek( Shard shard ) { - DataStreamSegment dataStreamSegment = shard.getGenomeLocs().size() != 0 ? new MappedStreamSegment(shard.getGenomeLocs().get(0)) : new EntireStream(); - return iteratorPool.iterator(dataStreamSegment); - } - /** * Seek to the specified position and return an iterator through the data. * From 4633637af61da1586c892a6b0588171279d2a596 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 26 Dec 2011 01:05:24 -0500 Subject: [PATCH 366/380] Moved ReduceReads to static ReadClipper * all clipping done in ReduceReads is done using the static methods of the ReadClipper now. --- ivy.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ivy.xml b/ivy.xml index ee24bc367..ef885d24d 100644 --- a/ivy.xml +++ b/ivy.xml @@ -78,6 +78,9 @@ + + + From f7a57520253c7c87d8730f740acccd13a0ad586d Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 26 Dec 2011 21:55:02 -0500 Subject: [PATCH 368/380] Let this one slip through my commits. --- ivy.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/ivy.xml b/ivy.xml index ef885d24d..ee24bc367 100644 --- a/ivy.xml +++ b/ivy.xml @@ -78,9 +78,6 @@ - - - From 17bfe48d5efafb29e18922dd6c3bb681912c3e34 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 27 Dec 2011 01:29:13 -0500 Subject: [PATCH 370/380] Made all class methods private in the ReadClipper * ReadClipperUnitTest now uses static methods * Haplotype caller now uses static methods * Exon Junction Genotyper now uses static methods --- .../sting/gatk/walkers/ClipReadsWalker.java | 3 +- .../sting/utils/clipping/ReadClipper.java | 18 +++++------ .../utils/clipping/ReadClipperUnitTest.java | 30 +++++++++---------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java index c28e205bf..74d8a8180 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/ClipReadsWalker.java @@ -43,7 +43,6 @@ import org.broadinstitute.sting.utils.clipping.ClippingRepresentation; import org.broadinstitute.sting.utils.clipping.ReadClipper; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import org.broadinstitute.sting.utils.sam.ReadUtils; import java.io.File; import java.io.PrintStream; @@ -300,7 +299,7 @@ public class ClipReadsWalker extends ReadWalker= 0 && stop <= read.getReadLength() - 1", // start and stop have to be within the read "start == 0 || stop == read.getReadLength() - 1"}) // cannot clip the middle of the read - public GATKSAMRecord hardClipByReadCoordinates(int start, int stop) { + private GATKSAMRecord hardClipByReadCoordinates(int start, int stop) { if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) return new GATKSAMRecord(read.getHeader()); @@ -208,7 +208,7 @@ public class ReadClipper { @Requires({"left <= right", // tails cannot overlap "left >= read.getAlignmentStart()", // coordinate has to be within the mapped read "right <= read.getAlignmentEnd()"}) // coordinate has to be within the mapped read - public GATKSAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { + private GATKSAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { if (read.isEmpty() || left == right) return new GATKSAMRecord(read.getHeader()); GATKSAMRecord leftTailRead = hardClipByReferenceCoordinates(right, -1); @@ -235,7 +235,7 @@ public class ReadClipper { * @param lowQual every base quality lower than or equal to this in the tail of the read will be hard clipped * @return a new read without low quality tails */ - public GATKSAMRecord hardClipLowQualEnds(byte lowQual) { + private GATKSAMRecord hardClipLowQualEnds(byte lowQual) { if (read.isEmpty()) return read; @@ -269,7 +269,7 @@ public class ReadClipper { * * @return a new read without the soft clipped bases */ - public GATKSAMRecord hardClipSoftClippedBases () { + private GATKSAMRecord hardClipSoftClippedBases () { if (read.isEmpty()) return read; @@ -314,7 +314,7 @@ public class ReadClipper { * * @return a new read without adaptor sequence */ - public GATKSAMRecord hardClipAdaptorSequence () { + private GATKSAMRecord hardClipAdaptorSequence () { final Integer adaptorBoundary = ReadUtils.getAdaptorBoundary(read); if (adaptorBoundary == null || !ReadUtils.isInsideRead(read, adaptorBoundary)) @@ -332,7 +332,7 @@ public class ReadClipper { * * @return a new read without leading insertions */ - public GATKSAMRecord hardClipLeadingInsertions() { + private GATKSAMRecord hardClipLeadingInsertions() { if (read.isEmpty()) return read; @@ -357,7 +357,7 @@ public class ReadClipper { * * @return a new read with every soft clip turned into a match */ - public GATKSAMRecord revertSoftClippedBases() { + private GATKSAMRecord revertSoftClippedBases() { this.addOp(new ClippingOp(0, 0)); // UNSOFTCLIP_BASES doesn't need coordinates return this.clipRead(ClippingRepresentation.REVERT_SOFTCLIPPED_BASES); } diff --git a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java index d1d5ddd8f..4dad68dc5 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java @@ -60,7 +60,7 @@ public class ReadClipperUnitTest extends BaseTest { int alnEnd = read.getAlignmentEnd(); int readLength = alnStart - alnEnd; for (int i=0; i= alnStart + i, String.format("Clipped alignment start is less than original read (minus %d): %s -> %s", i, read.getCigarString(), clippedRead.getCigarString())); Assert.assertTrue(clippedRead.getAlignmentEnd() <= alnEnd + i, String.format("Clipped alignment end is greater than original read (minus %d): %s -> %s", i, read.getCigarString(), clippedRead.getCigarString())); } @@ -73,10 +73,10 @@ public class ReadClipperUnitTest extends BaseTest { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); int readLength = read.getReadLength(); for (int i=0; i %s", i, read.getCigarString(), clipLeft.getCigarString())); - GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReadCoordinates(i, readLength-1); + GATKSAMRecord clipRight = ReadClipper.hardClipByReadCoordinates(read, i, readLength-1); Assert.assertTrue(clipRight.getReadLength() <= i, String.format("Clipped read length is greater than original read length (minus %d): %s -> %s", i, read.getCigarString(), clipRight.getCigarString())); } } @@ -112,7 +112,7 @@ public class ReadClipperUnitTest extends BaseTest { int alnEnd = read.getAlignmentEnd(); if (read.getSoftStart() == alnStart) { // we can't test left clipping if the read has hanging soft clips on the left side for (int i=alnStart; i<=alnEnd; i++) { - GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipByReferenceCoordinates(alnStart, i); + GATKSAMRecord clipLeft = ReadClipper.hardClipByReferenceCoordinatesLeftTail(read, i); if (!clipLeft.isEmpty()) Assert.assertTrue(clipLeft.getAlignmentStart() >= i + 1, String.format("Clipped alignment start (%d) is less the expected (%d): %s -> %s", clipLeft.getAlignmentStart(), i + 1, read.getCigarString(), clipLeft.getCigarString())); } @@ -126,9 +126,9 @@ public class ReadClipperUnitTest extends BaseTest { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); int alnStart = read.getAlignmentStart(); int alnEnd = read.getAlignmentEnd(); - if (read.getSoftEnd() == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side + if (read.getSoftEnd() == alnEnd) { // we can't test right clipping if the read has hanging soft clips on the right side for (int i=alnStart; i<=alnEnd; i++) { - GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipByReferenceCoordinates(i, alnEnd); + GATKSAMRecord clipRight = ReadClipper.hardClipByReferenceCoordinatesRightTail(read, i); if (!clipRight.isEmpty() && clipRight.getAlignmentStart() <= clipRight.getAlignmentEnd()) // alnStart > alnEnd if the entire read is a soft clip now. We can't test those. Assert.assertTrue(clipRight.getAlignmentEnd() <= i - 1, String.format("Clipped alignment end (%d) is greater than expected (%d): %s -> %s", clipRight.getAlignmentEnd(), i - 1, read.getCigarString(), clipRight.getCigarString())); } @@ -154,7 +154,7 @@ public class ReadClipperUnitTest extends BaseTest { for (int addLeft = 0; addLeft < nLowQualBases; addLeft++) quals[addLeft] = LOW_QUAL; read.setBaseQualities(quals); - GATKSAMRecord clipLeft = (new ReadClipper(read)).hardClipLowQualEnds(LOW_QUAL); + GATKSAMRecord clipLeft = ReadClipper.hardClipLowQualEnds(read, LOW_QUAL); // Tests @@ -162,14 +162,14 @@ public class ReadClipperUnitTest extends BaseTest { assertNoLowQualBases(clipLeft, LOW_QUAL); // Can't run this test with the current contract of no hanging insertions - //Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipLeft.getReadLength(), readLength - nLowQualBases, read.getCigarString(), clipLeft.getCigarString())); +// Assert.assertEquals(clipLeft.getReadLength(), readLength - nLowQualBases, String.format("Clipped read size (%d) is different than the number high qual bases (%d) -- Cigars: %s -> %s", clipLeft.getReadLength(), readLength - nLowQualBases, read.getCigarString(), clipLeft.getCigarString())); // create a read with nLowQualBases in the right tail Utils.fillArrayWithByte(quals, HIGH_QUAL); for (int addRight = 0; addRight < nLowQualBases; addRight++) quals[readLength - addRight - 1] = LOW_QUAL; read.setBaseQualities(quals); - GATKSAMRecord clipRight = (new ReadClipper(read)).hardClipLowQualEnds(LOW_QUAL); + GATKSAMRecord clipRight = ReadClipper.hardClipLowQualEnds(read, LOW_QUAL); // Tests @@ -187,7 +187,7 @@ public class ReadClipperUnitTest extends BaseTest { quals[readLength - addBoth - 1] = LOW_QUAL; } read.setBaseQualities(quals); - GATKSAMRecord clipBoth = (new ReadClipper(read)).hardClipLowQualEnds(LOW_QUAL); + GATKSAMRecord clipBoth = ReadClipper.hardClipLowQualEnds(read, LOW_QUAL); // Tests @@ -214,8 +214,7 @@ public class ReadClipperUnitTest extends BaseTest { GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(BASES, QUALS, CIGAR); GATKSAMRecord expected = ArtificialSAMUtils.createArtificialRead(CLIPPED_BASES, CLIPPED_QUALS, CLIPPED_CIGAR); - ReadClipper lowQualClipper = new ReadClipper(read); - ReadClipperTestUtils.assertEqualReads(lowQualClipper.hardClipLowQualEnds((byte) 2), expected); + ReadClipperTestUtils.assertEqualReads(ReadClipper.hardClipLowQualEnds(read, (byte) 2), expected); } @Test(enabled = true) @@ -224,8 +223,7 @@ public class ReadClipperUnitTest extends BaseTest { // Generate a list of cigars to test for (Cigar cigar : cigarList) { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); - ReadClipper readClipper = new ReadClipper(read); - GATKSAMRecord clippedRead = readClipper.hardClipSoftClippedBases(); + GATKSAMRecord clippedRead = ReadClipper.hardClipSoftClippedBases(read); int sumHardClips = 0; int sumMatches = 0; @@ -276,7 +274,7 @@ public class ReadClipperUnitTest extends BaseTest { for (Cigar cigar : cigarList) { if (startsWithInsertion(cigar)) { GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); - GATKSAMRecord clippedRead = (new ReadClipper(read)).hardClipLeadingInsertions(); + GATKSAMRecord clippedRead = ReadClipper.hardClipLeadingInsertions(read); int expectedLength = read.getReadLength() - leadingCigarElementLength(read.getCigar(), CigarOperator.INSERTION); if (cigarHasElementsDifferentThanInsertionsAndHardClips(read.getCigar())) @@ -300,7 +298,7 @@ public class ReadClipperUnitTest extends BaseTest { final int tailSoftClips = leadingCigarElementLength(ReadClipperTestUtils.invertCigar(cigar), CigarOperator.SOFT_CLIP); final GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(cigar); - final GATKSAMRecord unclipped = (new ReadClipper(read)).revertSoftClippedBases(); + final GATKSAMRecord unclipped = ReadClipper.revertSoftClippedBases(read); if ( leadingSoftClips > 0 || tailSoftClips > 0) { final int expectedStart = read.getAlignmentStart() - leadingSoftClips; From 8259c748f228ba2a5b5175f6b468682bc6da32bb Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 27 Dec 2011 14:54:35 -0500 Subject: [PATCH 372/380] No more Filtered Reads tag. All synthetic reads are marked with the reduced read tag. --- .../src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 1 - 1 file changed, 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 e8df869e6..aadfb9ff1 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -45,7 +45,6 @@ import java.util.Map; */ public class GATKSAMRecord extends BAMRecord { public static final String REDUCED_READ_CONSENSUS_TAG = "RR"; - public static final String REDUCED_READ_FILTERED_TAG = "RF"; // the SAMRecord data we're caching private String mReadString = null; From f6929119030c31a1263831099b9115bfdd94ec33 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 27 Dec 2011 17:00:47 -0500 Subject: [PATCH 373/380] GATKSAMRecord emptyRead static constructor * Creates an empty GATKSAMRecord with empty (not null) Cigar, bases and quals. Allows empty reads to be probed without breaking. * All ReadClipper utilities now emit empty reads for fully clipped reads --- .../sting/utils/clipping/ClippingOp.java | 4 +- .../sting/utils/clipping/ReadClipper.java | 18 ++++++--- .../sting/utils/sam/GATKSAMRecord.java | 40 +++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java index 7eb853097..921a0a599 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ClippingOp.java @@ -286,7 +286,9 @@ public class ClippingOp { @Requires({"start <= stop", "start == 0 || stop == read.getReadLength() - 1"}) private GATKSAMRecord hardClip (GATKSAMRecord read, int start, int stop) { if (start == 0 && stop == read.getReadLength() - 1) - return new GATKSAMRecord(read.getHeader()); + return GATKSAMRecord.emptyRead(read); +// return new GATKSAMRecord(read.getHeader()); + // If the read is unmapped there is no Cigar string and neither should we create a new cigar string CigarShift cigarShift = (read.getReadUnmappedFlag()) ? new CigarShift(new Cigar(), 0, 0) : hardClipCigar(read.getCigar(), start, stop); diff --git a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java index e41e66518..afe7fa975 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipping/ReadClipper.java @@ -134,7 +134,8 @@ public class ReadClipper { wasClipped = true; ops.clear(); if ( clippedRead.isEmpty() ) - return new GATKSAMRecord( clippedRead.getHeader() ); + return GATKSAMRecord.emptyRead(clippedRead); +// return new GATKSAMRecord( clippedRead.getHeader() ); return clippedRead; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); // this should never happen @@ -186,7 +187,8 @@ public class ReadClipper { "start == 0 || stop == read.getReadLength() - 1"}) // cannot clip the middle of the read private GATKSAMRecord hardClipByReadCoordinates(int start, int stop) { if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) - return new GATKSAMRecord(read.getHeader()); + return GATKSAMRecord.emptyRead(read); +// return new GATKSAMRecord(read.getHeader()); this.addOp(new ClippingOp(start, stop)); return clipRead(ClippingRepresentation.HARDCLIP_BASES); @@ -210,13 +212,15 @@ public class ReadClipper { "right <= read.getAlignmentEnd()"}) // coordinate has to be within the mapped read private GATKSAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { if (read.isEmpty() || left == right) - return new GATKSAMRecord(read.getHeader()); + return GATKSAMRecord.emptyRead(read); +// return new GATKSAMRecord(read.getHeader()); GATKSAMRecord leftTailRead = hardClipByReferenceCoordinates(right, -1); // after clipping one tail, it is possible that the consequent hard clipping of adjacent deletions // make the left cut index no longer part of the read. In that case, clip the read entirely. if (left > leftTailRead.getAlignmentEnd()) - return new GATKSAMRecord(read.getHeader()); + return GATKSAMRecord.emptyRead(read); +// return new GATKSAMRecord(read.getHeader()); ReadClipper clipper = new ReadClipper(leftTailRead); return clipper.hardClipByReferenceCoordinatesLeftTail(left); @@ -249,7 +253,8 @@ public class ReadClipper { // if the entire read should be clipped, then return an empty read. if (leftClipIndex > rightClipIndex) - return (new GATKSAMRecord(read.getHeader())); + return GATKSAMRecord.emptyRead(read); +// return (new GATKSAMRecord(read.getHeader())); if (rightClipIndex < read.getReadLength() - 1) { this.addOp(new ClippingOp(rightClipIndex + 1, read.getReadLength() - 1)); @@ -379,7 +384,8 @@ public class ReadClipper { int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop, ReadUtils.ClippingTail.LEFT_TAIL); if (read.isEmpty() || (start == 0 && stop == read.getReadLength() - 1)) - return new GATKSAMRecord(read.getHeader()); + return GATKSAMRecord.emptyRead(read); +// return new GATKSAMRecord(read.getHeader()); if (start < 0 || stop > read.getReadLength() - 1) throw new ReviewedStingException("Trying to clip before the start or after the end of a read"); 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 aadfb9ff1..96713edc2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -316,6 +316,46 @@ public class GATKSAMRecord extends BAMRecord { return (lastOperator == CigarOperator.HARD_CLIP) ? stop-1 : stop+shift-1 ; } + /** + * Creates an empty GATKSAMRecord with the read's header, read group and mate + * information, but empty (not-null) fields: + * - Cigar String + * - Read Bases + * - Base Qualities + * + * Use this method if you want to create a new empty GATKSAMRecord based on + * another GATKSAMRecord + * + * @param read + * @return + */ + public static GATKSAMRecord emptyRead(GATKSAMRecord read) { + GATKSAMRecord emptyRead = new GATKSAMRecord(read.getHeader(), + read.getReferenceIndex(), + 0, + (short) 0, + (short) 0, + 0, + 0, + read.getFlags(), + 0, + read.getMateReferenceIndex(), + read.getMateAlignmentStart(), + read.getInferredInsertSize(), + null); + emptyRead.setCigarString(""); + emptyRead.setReadBases(new byte[0]); + emptyRead.setBaseQualities(new byte[0]); + + SAMReadGroupRecord samRG = read.getReadGroup(); + emptyRead.clearAttributes(); + if (samRG != null) { + GATKSAMReadGroupRecord rg = new GATKSAMReadGroupRecord(samRG); + emptyRead.setReadGroup(rg); + } + + return emptyRead; + } } From e6e80e8d3f1c2bc0ef0f984cf3ab2854617aa64a Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Thu, 29 Dec 2011 14:35:02 -0500 Subject: [PATCH 377/380] Update Picard to fix a bug Mauricio found in Picard where Picard unnecessarily depends on Snappy during some usages of SortingCollection. --- .../picard-private-parts-2125.jar | Bin 327508 -> 0 bytes .../picard-private-parts-2164.jar | Bin 0 -> 40954 bytes ...2125.xml => picard-private-parts-2164.xml} | 2 +- .../repository/net.sf/picard-1.57.1030.xml | 3 --- ...ard-1.57.1030.jar => picard-1.58.1057.jar} | Bin 1194798 -> 1201269 bytes .../repository/net.sf/picard-1.58.1057.xml | 3 +++ settings/repository/net.sf/sam-1.57.1030.xml | 3 --- .../{sam-1.57.1030.jar => sam-1.58.1057.jar} | Bin 566967 -> 569648 bytes settings/repository/net.sf/sam-1.58.1057.xml | 3 +++ 9 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 settings/repository/edu.mit.broad/picard-private-parts-2125.jar create mode 100644 settings/repository/edu.mit.broad/picard-private-parts-2164.jar rename settings/repository/edu.mit.broad/{picard-private-parts-2125.xml => picard-private-parts-2164.xml} (58%) delete mode 100644 settings/repository/net.sf/picard-1.57.1030.xml rename settings/repository/net.sf/{picard-1.57.1030.jar => picard-1.58.1057.jar} (88%) create mode 100644 settings/repository/net.sf/picard-1.58.1057.xml delete mode 100644 settings/repository/net.sf/sam-1.57.1030.xml rename settings/repository/net.sf/{sam-1.57.1030.jar => sam-1.58.1057.jar} (91%) create mode 100644 settings/repository/net.sf/sam-1.58.1057.xml diff --git a/settings/repository/edu.mit.broad/picard-private-parts-2125.jar b/settings/repository/edu.mit.broad/picard-private-parts-2125.jar deleted file mode 100644 index 70c58957224875dd6ab582017fe4310d55c8388c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 327508 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

SrMbUL3o;F z7a>HYTYqq0IiId-%)0UrXyLmWDEJFvtG4uKs=iMnhnHW@t)i0%bVbchPA4FG%Kjl& zyGt$Zf;amAgMShEZ%D)TPUim|90vmW*B0N!u7Up1!qCCc*y8_>=!^8f8~$s-?SEwx zWBj}3MqZW#tHif_TJAcMHf)0lUqlL!`dfKL`e*NKWr?I~fUQ>3@=(U$~W&K4wiFp%X z>j>PchVB!*3A3nKSh_P!2FrCN@R5qA&-c~ z$_`9 z4#U9nya0o7=}p3(rV_w|5sYI+teB*5hXVgL*d7DI-s?NGIZ^AsoUOtDN9vnxZg6M& zW%pdu{{p^Pyd2g0@_SzNPiy8*^jNW?{Ee+yKTsB=3to^s!LhYt^>`tz+z}}KP;N(w zTldH6g0*7Xl>z4IE!(HRHR01&ue>X}@Qt+Sf%kJ&UO96shN;YNKaW!+&@&>P>)qNv z7uR(2_QB?VDsWo=Y#U#5p2mY|)x0`$;FQP(Ra^`{Sm=6AW36Uo#xTUTd=b*nRzMqg zm*29GRj*t5Hy4ab$De%Hq~<(2@{4tprq>to+K)%==bS$mG*Yd?68j7HhDj_wq_NBT z6hO~#u4F?5j`22R+&w&bG_y1?2=7D3LD1aAE>7!--Wy#29{w78^$7ZAIYDz

J5 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 z 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ˉM`ko~LH9NQ831eJnuqC#pk-w{h^58A5N6inZ% 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�gm&?u{)U5UL{J&Mr63V0j|eyaM-M7 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≈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
vwPB5_whIVfPK$+e#A#x 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
ZzYP-ByQ*PZoIe6u7_>P?1xmRWQ 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 diff --git a/settings/repository/edu.mit.broad/picard-private-parts-2164.jar b/settings/repository/edu.mit.broad/picard-private-parts-2164.jar new file mode 100644 index 0000000000000000000000000000000000000000..4465f91f578165fb7f49ea5c891b40da3ac8e70a GIT binary patch literal 40954 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 - + diff --git a/settings/repository/net.sf/picard-1.57.1030.xml b/settings/repository/net.sf/picard-1.57.1030.xml deleted file mode 100644 index 50e2db846..000000000 --- a/settings/repository/net.sf/picard-1.57.1030.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/settings/repository/net.sf/picard-1.57.1030.jar b/settings/repository/net.sf/picard-1.58.1057.jar similarity index 88% rename from settings/repository/net.sf/picard-1.57.1030.jar rename to settings/repository/net.sf/picard-1.58.1057.jar index 0ce2cb0dc7d1bba0989393673d3b17f5d0d24269..4a82a3058e5c8939773c8c4f3e78dae6105547a3 100644 GIT binary patch delta 73393 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 yw5(iW!-};5zN;n~j2Zd$Ju{n4tVKK};)Cwo-@ delta 67818 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

4LH`#JE&xWDqZVKk5!h|;?FV!#iCQ}2uDGM3v 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

!16 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 U`3)UcG<^|$M-;`~jm4n+1s-s;asU7T diff --git a/settings/repository/net.sf/picard-1.58.1057.xml b/settings/repository/net.sf/picard-1.58.1057.xml new file mode 100644 index 000000000..15c5b5620 --- /dev/null +++ b/settings/repository/net.sf/picard-1.58.1057.xml @@ -0,0 +1,3 @@ + + + diff --git a/settings/repository/net.sf/sam-1.57.1030.xml b/settings/repository/net.sf/sam-1.57.1030.xml deleted file mode 100644 index a5dfe0718..000000000 --- a/settings/repository/net.sf/sam-1.57.1030.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/settings/repository/net.sf/sam-1.57.1030.jar b/settings/repository/net.sf/sam-1.58.1057.jar similarity index 91% rename from settings/repository/net.sf/sam-1.57.1030.jar rename to settings/repository/net.sf/sam-1.58.1057.jar index 2404c9986f1b5e4f858617ad8d8968b76fec0dc0..804e21b6165869fa1dff4e622ddf3e7dac8f0f7c 100644 GIT binary patch delta 24576 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 z znD^y6DIuBdw2`G5?*qcRGwbr`Kx?e0-T$8^`lx*kg`A~!`B^^dz@Y;)Djmw&(QH_F zqOGoFom63Gyw~Xy7n=HHJJ!_(ee(Qo3gtmivISKln8Z(0PRiNF%6bqM4`ZM``B*y_ zVzGQDR=kz;8X(`mYWz(lub$AAK``QX!mqX=*VGj@3=gvmSyeb081fGXbOm{)uCRur zk-g_YjaFW#Cb&0Zov_vGgsBoN*HwO%cn(#uO7!;&s{QW@3QJ+=!2cpCP->_k|A1Nl z0HJ~p;g)fD^J4@bKE*8iSpx;>U#B4b>)O`8u5JAhq*w!*F7gBeXQ>_)(2?#ev!W!RgdI%uc8g1ZT z=n3j|k}M}E?!f;_?+U10$f%iXz9zZx5 zkz^M_$#DE_w3jm<;@dt=`To4;wo9jj{KOJq2p!_oBQCI{U>{}J8l4_vM+!QzvC|KgP&mZlV*I7olkVJu^Y&`W%KnMuCl z8i^`j<$B@0zpm38I=!jWTb%K>PVeaSCl?Mv0P76>IRwl9zw+&G{Q52v`MZ_018$Ap z)98JT{-Mza@_3XUBj=5>_7We;$3|IyYPitwJv7?7M@0VD8pC@fJ5H|4v*ybAW33}> zPik~Yo#pt?SnCmSN*)+zZ6Q9AFORc65IT!y8&IFo1{7qp9MxXLrDhkEWMeTiy9oB* zboULoD_hyYo@T(Knk1iZqc@XzU9dzrHs0DN{xkOMx6=-TKIhw5nqa{7njym`Si6M$ zlO|~Feiy^DA2Y$)GxXo|rGc#w(EEzMmc2*V;^gXTTZD@%`IiANYbKuzNtXYfU~QT* ziysztUCjsOK|#h72q#SzJ1@ z3} zBYx3Z`c_Y|-f8g+!H5#@#dtx1zEZ;$-KCz8wtAvp)D!Xn>=c#01H+r$XCCJ05cPz7 z@r(itx*qpJyp6!PWW$l1?jwNg&I+zwG@V!72>vGm~SHBn4V`BJ1RZ- zG)1#Xv?{W3%E}Li6EXsuDx+Z+$50!nFt^EV68EL}h-i z+Th7Rc5oHNvw_FbpauCstvAI*RBnn3z%x)O`!nodqfPLBUHMTlihV3y)IlSY^C(nG z=u8bA8HsL;QmibRAx93X?WG4o zX$seQ83=o>Z+dwR~P(H_k(suR{~KT{_vY z&#hs-WZ{X!->|b#8o{m9XkHZ?&D*jE&HOtRG{QX(}2I!a>)}tw= z>B2F|>oilt3NsRV9GDGuqm-M%tDBY=DT^Pq)NdG$BK8;1^jIVY2)aON!_&cSr<$Ap zGC>By-VUl~&UAmzk>pO*;d{S1BQU=Q#x{d9|K?7KeO+TN<%;UisQA5X`@ z)AVq4zE0*9TO(x2I^7|=@3iW^vU%1_b2s6@QtOa7Glsv}y35>aSii%1m$_qM?6gkb zq)LlZZM)5QeMnQ=7n-W%M8V<_jt;PYq%naKQ6MA{3t)EHjLbh#& zu4pywWb18iKFsZ5>(EN&?JKk`(be8aLb2^=xZ2Hlb+v7Kyegfx)AozG50PJOTWL~x z_kgW~xl0lLgzZ(Ag8SgKZK|2q{k(0e*_sFcXxnSF}DMP=iI_Dg0RcOI}8n1>VJ zsY`11u!!(m0 z55+OXoJ)4YxAimo&}ItdL(S14j#F|@dq=8#Ylp7M9!(s&Sw|Dc*Ji3yOGk;SS3aKQ zXr~H4%5tnVvw)cs$wwkEF6*|UOj*NW6kr4S`2dTY{%3Fse1Z8_y<6LyRt(M6D zJ{RqMc6=N8=jX8O;v>@$4UbIosci4t<=vImyxx1wGe00=_k%7AUe=OAgXzCBF;;$8 zsypVsdgGbrz*}3I>4Gun;KZ$?3vOa37j>pCA=H(+ajLrufvldq%;%ql&p73AVQ((% zqromxKTh`NNH?MfgET^TX^0?4#k`{OW;ychgO(V@amuY67nXGcSofmhqKck^vSigJ zOZ+X|dS>Q%y%XK?jaRf=i{6=-Ap3o4OO$D^YVnCdjZ}x!%es#n*R5x^_wN%Mo5$tl zflKyyZ{j3u;Efw6BVQ8Hq0Bs@su*H|@^4eWES+pkP|T{@rqOhR5V#i9Nq(|L#NHxKbiVgmetNxv#oVr0_2=?$ z(`7iuRxoQ;VR?D8+?*Ua`fY2hl234(3ksWfE9ZxLU!L4wOuUU;y#<9C-icE#Mg@Pp zz=Dxg-)ISukZYitb~%vm?K%fUI(eH(oc}< zX$rB?Fu2p~gjg_r8#Omy99DDl`5KBGW+QB0NR2?y?7bQjS>rXUHRS~&Y8K*9|=2>jGQbyF((IY z^Y@B-|HYSQfHPbc%2pONSi5DfemDtz#tF_B@vV$+eN*E>7Lex3w;G*gDR;hg0kMwt*?%tIcG%El}%E-skKyRcpO z!<}I1c-daEsciYXrqZ-a0V>`P3p_Eu_X*MO4cu%DTt_PyUbc+EgFEQcB)`>{m-jh3 z?P#jWO?g_p`!Qf~vR>^%_7uY|nulhPa5eYx># zEhYs05%`+O`^~I8-hjW&2S_k1rukYrJNEa@*WYHxi{CLzeFVN6yNc+~`L8vHjOeZz zGWkQZ}34)Tdgr-KIun zGo7altWrkc^oD^AA8@bctZ9N;3@(P!mMtztSkA3F86U{G`NKB%oRTs{0U3(KF)XKV z)8p=f1^lVNa(|@&JnSQd>6jlOJhoJf_8gczxJfK^ zq!O`%)e+_rW)H@#3+6MZf-;KG2ITit`Mr>j9#Q$|)4KeA$cO&ay${5E#M|U&jG_av()G-5)dl!o+p4=88W@1B(#h#DtTE}U8>2{X1d>n$*Pa{frmYn%X zK606%!jSxQ6d0!|6a`@%ltJJs9HE2=tO+A=r11nr=i6qS8ZBQHoiQcTvp6`XbD4d=LVB zW0GR=O+?x}B*A5q@P;nJlHZ~tj?aKLCNhs&=2~j0Q8lxilplp}`Wl}{)0==P{`@Y5 zPm6~mJcY8+%kFpMU>xg?N#U@XREik=I z#uA*x+fgyhlQ)ugAdF^mY_^6AnPanz4?s*uS|@AFD2-->_*H;N|4@Q`9 zv7h;djcTeI1%Jd=i&&VFc5}39*jW!Zs|DtAG)sLT*is;>)56@GR~^W^-ce>*Yk~NJ z*?kZZ_CUENmV_JKl3pi?Y_yV(T-czWVbIV7Y+1XZku7N;nmPm)Xei>9BhZN>$+Hl> z5$MGXzjy?~Nvj}p*ufV4C1{`%BdlQVxQ+&gf!LnBKEe{xlJ&=Qpu$xhrrO#O1%XNfuLwRM z?_NLupe62q1?IB?3_LATk5dO`ewQ*7YGIx18Uu-cc34UkAUF36P2>m&)-)O%eP^>& zs{A!Rj+u+bFq0v8wkJ!ypT^7l>h_NLW;npn)lr+N*2m}PILgfhdWTZSU(8ZtiQ`-g zwakD0K}UXqf@^i!(bo)I6rXk6V=nW5e!<~10}|FZ923n@!qhh%QCb*k>-BP%e#0AK zb=t@-Q~QZHZ_ZAK%>J80_Z|4FBi*iuzWKi6oK>Z^Typ%Z7WlpmKRSlk6@g7zzde7# zAUa`~1ZKqAWzJ29(`UQp_=BO)UeUCFn1@MgVzeP<{Aa)u71k=$TV?0aOp+Y+lP$#8 zJVD!THty-hT1%7F&zfjE!xWj7S=vu#M53aLw!jg}?EmYvkhM3Uzc=Le9oBeRIS>co zE_1`hsmoq{OGgH0CYtNW*mm7uJbQ5F7JPqew zxkH95(3l4RE#=omI+f|{ zKx6N~#W8-qEpy^^xlSuw2xzauX8;^LSfi6ir%H`??cgj&(%l-Zbt33^k4EbeY-DOU zO4^n-y7(xmPMdYwqS02aY8&5%ZP)l9shdibo9t#cxiKFob@RS*TjeNYSJ}niCz&3y z&PPczXdeuEC6-R>XunPeu&0berM}X`TA9U&99Hk~$m>sPom>&+#Ra9M%lns=?K*5p zlf$0UCW>Zq_)}WEuKXet$gNLU;-eqd_*5zy!_0$E{K-j0dSB@{qV*IH%Kk^R-BEe$ zr}d^meCuOitn`vsj%Y~7xqmGuOj zlvPKy)=e6hl+DdvR#;Y4upryyF?(!))sixK;Xep0JbP4&bHI}k6eDwA5;3|)wFWlu zhsYm}Y6BxWQYZEw@oxvt7&tclq#Sxo>(wZ{UIb`dNtuDKW6m1*+~S;h-t!N~G6nP=d z(o}B#!qG)GKcyX+(iWY`hOH571%JHVL0Jwq*d5Z5zj`L zNci0j96nkpsISaEttAfZhfH&0u|52!4)A*dQ@)P*W+2`eVGtw0W_<<%8nPUiwyf#m zG}-uTPtnyT?>ntE@(hX!#bLIt_NZ`vIHDr>VN0~z&QWB(HJYNv;`rC_M2Fq~9y2!I zhCM0H9Z^Fre5OP8uJ-iI)9`1qPE#W0poELG1dqgvRC0zI?WS1FJRyh?L?t0CVC@Ku zs6b9Kb}0=TXLZNdNDl0D@h3VNXjUIR`2!`S{6Vq#VnDZV8 z9`uC!m4|`X8)*G8_6EQo9f+Jka2E%I&@hl3ip|Smm=H!{S8){fw#I<;1T17HVt`Jf z{g^+W#Qb>_b3Q(74?60Wk}w0OsIq%v|#PKt-5=<_kOKFYZYG5~(fp zW-wd^JF3k>MMI!XccN?nnMtf;;RQbz%4rcOb}hJhgms&M%8(D{DF z;M8bf2sHE>SnlM(R2GWk17m?^MXP_pEItI%V5gD~>X@udK+0rgB8Zx(UFtBq)nQHo zWCR zHJ|}eT_qi%CUya5ni`J%MROO5_rwyQx2^K68PLzx7qAfx4>lt|Dia=T)_H*Q=!%BZ zW>vk9tzPy-7a^+*9xO+g2)%AaIs`(Th3YvbW{xRNnP*P9g>VoMl$-;SbK$S3k%mo3 zlaH3H{1`8ak9=EH_8Dqp@)P6dC#Q}d4~BVCLxw6G!>xJTJeu>L8Ee`9A`*7LwYSaPnrQNp(Nli{U6?eT>uOVEev*K<~^-l zH|Ns1&uU+rdlM60)?DT;z^u2m@=!IuzW1Rv4FA0aGi|F+wRUFdv@f-TW}fw1?aMS( z`dy?x-1G_ZqV=CmxUR|iSEiTJDox*M?q*zQrQcBh48r$(PyJ4lN}Im=9<%g~!TN$E z#dUg=n>TR2Ex6YB90mbj*fXm89jb~L!{E{Kt*5P_vRx_QSyn;+HmDAo z#SlJT!F%`5j+F0Jpkk1hQx>7(C#T2*ks{W6Y^lwg(#9!wFY=cj<f zJ5=?D)%9pccDQAp$Isv=OCL^u0?hH16@bU{P}PFX^(cALCI40Km(OqIkk8B=^`#?f z9I{u1Zm3QjQGwEL-%_QrvI76O1RW&za&GeH0UVqBbA_Iu;4gE@U#@eBTW%`xQ|Y=G zxm#~=ZX;RQScJ>U#d;HkaDa;pyS%VO_ZNM!*soC6x#(G!S+qYM{|BY(5`WRiB`9iv z{SwA<`~Mk>schPp5kE#?9P-sHNCcDGFnn9plZ6%f3yxSAU|bwP3j7XL9nO)ZOZ8+q zeUONdJ(prcv7UGipSigP{lJ$y0F39Msv9zLsoq#_0;JN$153f$Gvm1``~#|M4*6c7 z0YKcR`7Yt^d#F8n*_;i2Suu^Tn44#k+(GlU0Z!Ck1c@%sAI+jNdaZzI3Ka4R&^42d$j{gH1!54231b&CAt{i4~^ zv^t=dtI@r$O_9&7*1M=de3diaoGllv!6Di2R$yzn*BXB(7QEiOC>I^hmpece&qGzQ z^B6u`sYd8oiBb)Ri@;=F25A%%k^I86!Q&aIKN=c=S~DFIMC} zkH6l(jD9xhEb8Pdc(!@Gis3se0!AeWJh_oGV!b(EIpyHJ{x;shC9iCjRaF=Rr4M84 zzuy)==~ru}ZG`)y{`W|*WqfXj3GW_Zi;y=e{qWlsCR+O9xEEhd11(b`a6Y8qy_LHh za%7bs`~j-94+43RkHP9eD4^^#8Hj%e!_<*tWZa+(@rUq+xAykflsfoGnYh+ZpHI_2iH7hMY!{|<@;7F!o~B6GAF;>B#6hf& z=%zq-jf9+ETk9YD@2`cN-yCBSaTj7Ee&k+%QO-IP*(aDqA3tje<)cYv7yP~a$JtsO zgo~?HP$lK?2G;2vmA1am1TL)PjCdJw%xag>_rQT;lim6r6y;ae6-CR^d;GMA&b+^0 z^G&cBxKe^>U%E@ddq-8;fpRz>DH5>fx^zQhf=o-uyUl?h1ze80&HZ#fRbp>PZ0$z#i>kX*6OGaH$ zgv`FzPio8uXVW{QYQBQiZ_(KL>Q~;Y$Eo?X^_jk}6=62#E4bz_r!cr_B)_>A^*wke zXEgTCsCCG!jsEJ6Et}o#1T>MaVEmlB89Yv|skVj7wHy7$ea}Wv%2;m}y}40ORWcis zvbpRA80HKAs-f<8sOs^p3>qu19k4lN)_s1-cfSu*K6$__n*NkclZ)^3i)JGi9eG$4 z?W(nf$&~@5_qZhNDH91F_{PiX&3<~hn^07I+$_5AM_UVd?S6y?HNj?8kT`l1FBjO_z>A6(jF$8Ks8dilF~j&VJASHui*aQ-_$POnPN|n`%~-= z`J#lt*o)2Iq9<9sgH>5GdG3C+gR>8-?8(SpU{Aq6DXd@TaV`H@RpdJh@_Yr)xrKA= z^7vAimC9YhF0;0RE%pFTPEU64!0gXgZcy9r*yu<2_#yDN2?$D#2eyK(9$N!t58Gqp z7lE?tTz24)S;qfzO!nC3mvO`k-`)E@MiF1Zj@tF7WXU$Yy^^dUKKgo#lZdZiyVB?s zLnX;eOpqBb{d3Q}3ar@p3hso@&&i1GdY&Tq%vVpn_!C@IzM2I!@9_(=bi3c(_FWkk z7751q3WoaRHHN}RP#@V{vgalA59_94p#F^Et)5r=cj}OIiBpFTxo`!LxPQK1cVyhR zaO(LA9+pRcG?B6jM5MfLhhGWy?|{~R5wgpx$7kN!*J*w2|9AoeldoWrHZp)UzVg;KxwfnzzYG8)MbvK`^l zeSbqd8T~*eh4M0 zuPWNL6YB>asP3J}{7Z=m|J{5?6J-lks~&&x{rMH3%~$^*#)d36;oXZI>GJcP{xNrz zNu_#Jk@v#UM)LhV{*tM?Q1Zzmfs$~!e-~D+T>bN0(&M@Mk|t*y5pvHWJyNky`^#&; z^uZ|Ot91|ycfSz8vCA9JI-GKTz#@61o`nDOIqCsDL7wjni`eExheNK}OYQzr%OyC|rMup%T=7pL_vSRSVyH z0QG(LnLJ*NqOfW`N9FdeM()F3_;Zsqr}R|&+1T^ve7glA=PTIB-g|@LyD0+SGJ!9x za{EE3F>esc%-#MPd+$ckr3kZVTOxMvR&o)mz&0*=wy{~ndxY|>-Fl1?b^Q+Ft=i;F zZ2;dq(5GGZfYh5U%%YDPYZ0c1xl=cD(Jw9iSlz5)@~u67b^Df!wsov4Y9xC+=r@%o zfBdR*@tEWSA zuK}K~VA=K=7Ql%zmqOf-X?wx#hLK#7AWH^nI2h(YYmN@UF{&f%+%nKP2I@c24>VGX zl&2r^cjbq#pIlvrnV+xV>LyHUK-4`^bIECp#8bv&R}QA)e;5*9!8#vZQil|(DpB>n z`Vi`0yvQudDMFu?7HN&;(RpA}dEm@s_k94GzPJIUUvpmHRy>O!$;}_PiYJO$ndS@{-?DNnG~2z+*^Jbw<82|H?D@}8>aQ53BT`I{D*H9 zALM?Wxo=782@Ng?uHcB>xR-qO4I0%Se!=kR(*3y>t2`h@>Q^R}`L$V;cMxkprt-Brquo$AAAEHWqB8Rb z1|6bsFC7G~_A6(!Qg%`KRQK-t{$&L9c`NW1f&GxA%Xju8@RgmbhszzC^=O3OLz`UF+huW>zsTMY$;FrL0{v=QcluK_^_JzLZxgv%K%;0yBrQrxaO1V1P<^A>;< zS@|hM`xSz_3hF9Dz0kHEDy+U`$432-mr500jgtp(@fN3{9m3A|Mbz za)F3o<5`}H`aZ>H!`@N!sZT}t|IX~*6u$TU|KI<@?#|AfIdjgLGiT16nN7tfDZW=z zf(rXOT^=HXD6IeL%|RJKq5W5Pdi%RYhfWaDiOpRmhUI17o!cV2#T_lPo8=a?XxY5g zW?!agz4@y=r`dmXX|ibG492soNO1;%sl-7y9KlF*dNM0-!S|rxdnz5@Ag1)txG?4L@E@kbF>h->`_CL!D(8+ zq{$wNpq2r+wenCT<$0(z-4RG_s4b`3d8j>gaDxc`IRdC7b>hN&ojP-Qmq6-D-E``% zQxAUc>7icKTc&E2Zdgs+BA9cpvn%H?=;tPWZ%ZxQ-PlG6DAbRotxGo zH&^C<>W)&t&*cJc_OM{tqS6^B^LscASvu2^8{!9UnG1#oTf(@~TuYM|GqnJ@GgF)H zjX*~ViXssj%6y~eoa6xWvB-_XTT8q-@bqM6ohC=7kDRRN4vNQJ0}RnjO2FMzX%8h* z65a$QQ;Ln@dvYlZqcfi*#~E^+CAXj*6slgmq*ao>lbYFukz|}C&shp|(M2-*WnHr2 zC@AYJ1v{wx*7<(rx6YPRSU;;qn8>Q6@Zl%WL<01y2Zf;dK2$)VV1$XxBn(%YgVMRw zoaO;$0if=so_O}5(Mte&KlP`@=*FdrlT6UtoWdxT(!fDaRBlL(km^rCluj8)5skL_ zY>adW=@j3ni%ywN+_Hi~3F2$Y%**IA? z7SsUGaHJwIUh3o71WyVzYK#z%UG32qzkC6$@|EEqyQKq$hIpR}0KQp#o6Wa5d@J_g zHrK3*FSdnlNe=(N-WQO+~S%g<$up@dV*`L@CX znXKfzRh(MQw>6CN0i7N+Xv|sE6HxGs>^(*~V38nlTsHwS{dEe4e`^ut_lt3lfs zNE)=ApF0?0C)2e{r`Rn;~~Lhh?fga@))1pV|Qaff^?0|2{USx6p!1KN=mxMk%2u^)jggbu_6H<(t%* zx|mR1*rYx*Mt;;nYwGMn`(2Z_ppf;R3WYS?e z!gZc7sXO&B=_u_t=@^YM={Qx$(6w3v(Nwl-r6ncR!Pb=PoG@uDTgj6qouV?flBxMw zy=S7a;`y`Z6-`W19WZs~WUf1?Xu_=GiAj@Y6(`*Xf{?3lJgH2sphO$Je6^KU&p6Fg zR6-!%x6(45K2Q>ur)314p|d8zFf(!Ivx^Yz)T7!y;6E2B5hAh%5{vU_V->mU1z7~k>MTBz{C2sXnA6HsAHa4b3u z*!6rei4H%6!c&c3+!LuqGau!#aP zv%>VtK61l8dtoXHQ0BptV2TYGq&)9{8miUOhV4YcvS3}`k`(_!ni%qr4H zLYR=Q6X}4F!bhIdB&^AxiULO__{cm>E))ihtfJtNnScoKR#E6kABBa7S5m}DiX7=0 zcZwn_DXNm9eH3$=>cM!|b5&9-KPxHDnp2qMMj#4i3^uqP3`{)6d}A<>N!gSItJ{Po zB6SbUE3;!+(&^RgOt;wq|I;yFw*j}9_AqH%l6n+KnX^1J>a3*r9TZeW2_wT3eN_Jf zxNU%Z7p9pcOfwWxpvqF3_$XBso&Xm31egqxfrUwpgTV$iOrUq5qz%g2qSxC|EAqCl zrmqpYYbXs!gQHgEv=>y1YtULQEyBveIT&{Knws( zA!Ilhnm+`7(r~yFBdC~0RqJb(EkG8=Tw8!Fwg7U~8^~g7ih%-~V#{qa4+k>Z14` zN!gbu$4*_Orv0*>p`0CLWL>133rHte>4Xc+Ah^!Gz?xXQd&grC-3|X}A{Z%x?o9%* zQ>uxJLN2^L)l;o&h^q&8$a#a3b-Fw74>Cdxng9gQLRr|$9X*ki$Lhm~T7BpSehfBK# zEd86%UqaCrp+Y`$(H&iSxsl=9kRuu?N`1auF;F$?3+y4zD$0C+8!A3cU_yLHi-i%V zQfJnQ0YR!=&rWgBqc9gdI`uCXOo>aA1G8MwGXHU*%ix2;EWLU&#P@5NaBHgW&BG$X zt!n%+vHOcRqlj)sVYL@6H(YgVzIGKNAzHzWx+Ja`3U10Yn-AC^Qz!84mgWCr>edxj1{79!Q9=b{&>vT+~<2s$t=@Xqk^+2QFGw3sZe$G|D z;MA9V`-*+WuMPUfL*LSOjO=@zuIcoHPCx2&U8kRP`dO!c8T5+>bKtKG5%il{C*H!( zpnuDouZuq3-#zpP{mHi*eEZ7-ulOb}C{W`Mx`n!Mn&=N9IOX7!lT%oY{~_FP4y`=R zL-mBtc?KF10lM%QBG7}0^lOhWMNohU79pGprL+*$mlb#V>bkWrm-Dsl} zPiUQF@fDF6(_Qp1MNiSo6x~H{Q}hviO5}kC<+X8Dgd>W{KIR zn8TQg#d!I~F<11qm5#7TlU|{}GvRYhdYvwtw2yu2Wsbar?X=Yt^Td3U{z3m`ihIR< zrdYr<-76Nd57{tI7mG}>SS*2pLc~e;j-NiiD2c_fR4#c>WTmVT(5_{sC=tuK)(TUs zWPpcVS%WJ(i!uyI^?@jQ&lHU{T1~cKsL5m3Y0v`NOBChUR`P@OtaojPBgS_y_W(-t7wMDwW>H5~LI+z_iS2{%1VTY+<%9QuVw(WTX< zT>6Emm(C9gRD+Hl*D6G*xzAmC5x z>WO!NGYkk-@zXEGDpZ{Ml^9&B;yii;G6hMhVnDdZ{^9jedQ6GrtFJ^a7D=;j#H>-= zWJ`jH6|MbJL~0N11t{k+xmXz>1to`If<@sYKGO~7PPos z)!2&~%ThG6-63&L=EbvmWflZYiE zN8Cb$Mz?cx((qrP@-3`joPIcRAZ%#W^=JKus)kO+|Een5zrEQ2odW(t-CI~a)zIeA z$@3q85~0AS=`43MnBI&2;>g+7%=m;e)Vr-K)^(ElR8rsJqOCg?%Z7eCytI%L1$i1j z`se9UdT5u&D7K0QjEvHv^jKH+87lPAz_vy?<*BzpZ3AKho}|G$D7uP<0E*v+`e+z~ zL|7H=EYym1|BXDcMq76|nX&FzqmqUreQQ;$n|)!Ymv}sO zMX4g1GDSUVCSs|lh{w#GK+{AbEfPtzS|rm(kwRNUni|K8FhaYb|IbksgfO`}H2`vZ ziq1oiQsMsc0Mf9eDWa#9vU?X{VPI8(oah2Hn}1vBBD5WSkJK~Dym0DSdJbp}*tF;A z1!#LD%+Mt?=T>T!NBUnBsng$ddhsu6F2rA0PU`g1Eii{Zg(~UvvQ^B<7hypDO{T%7 z1Jf3FnLPNTxW_x90^!j9Th7o(PK;U=Ny*+W$&r-y@FNtE{J-X_Z&d}Y-XJ~@L!F7PE!2b-NxYSPe_dmnvmX+tNq@pu4 zsUqtPjewT8&(LJFHw9})tFftQBW_D2O+!OIy5|C9-%zQP@CylU^<}|(^WKkWUL!e zhdD}%75~LB6D00JXnYLD=2)yU##6qSKt04n>LZG1pqK>zb1IF3ff^(3p($cI&B60N zF%t@1Ov}Vv@~#x~XoHwf63<7(eY8j1PY1`2MH}$Ytlq=i<3Rs$w5O7ahvPQ4J@?}d(#miB&aWL3;wQQ$K4tXB%e>*4*_AYt z!TJw}(VAb=Gb$h@9zxHoL(i;-dD(zf$7V_vrPN3~Oijc#m>3CRVut3u0}cuC5NF4w z3B9dMK`iE)ca$kehRL|1ra+#|b#mMQ)&S7@E?ynu0J#<65Ae;){-*x4-rxNP=jFKXTOQL zk!t-p%5=PFs`X;SSjU%6l?q98%(B+0C9NGB)w0?b(cZDbO6|yZyko6w=N3Bh)WY2N z#85|LwK^@oa;HP>N&4QK=;-EDn~LNA-g#;>j3B;}urU!K6Q(*mzJe)^&W37l^K8ci zYi;}6y^cg(<;5&@>{is2SC-fq&#raESp>el-eCm-e6MVA^a@lJq91jXWU187mmE8+ zyuAN%yk(&?KX5FzNPXxN$5VLDEzxd%=cAnOqoLu^@|hAxZ0W_J9+}_RsmrYmoFU%dJP75MuqV5m zZ!7qQ4U$f@DgaLK8csdHw+B5K_78Dtod;9G1`kpjJ@7#{2S7PW)t*QQl^C?ugBf6( z2X3|G)OHX1n0n2gNIkVDV$dE2*^5n@7~1E-=-kh@GJ_5<=7Tzwdte41*XfW>hYdR7 zfkU*{1Gnrb!yV(>alRc5K%DV}2e*^_dWy+>l2fO3s^pCvAK%XK4V9YFX}+Om9Gx}j zoF^F*+Ed)(d4rzj=LKf_BHy0T=~)LxvVrTJb}$+?49{l=eYle?#X2@q*mj z!W}2`8aWdN_oaUJ9!Xc;DCx$x?$kx6OD6q|UwZHjA$3fnCcQ*2n}|qa6Z#bs6X~la zf?P#9y`~m{I=x}Se7&VMN93hO&Vndj0%B0VV`3D)qtm-4hU|N#Z(K`}ebSu;*?sAK zL8jVnnK~Wg}% z7Xvcn@(gEeljlwPm_9??g}y+@g}&11Ym>gAZ%z7+zBlO_^ZNr+K9cT|15Y@jWzv%_ z*S12p(Yi1F$QZAi^b@_ql-xajhE6Y-^fUd7IUUP+bH~pxF<)Zi`WKeWuhd1YidnOL z(CkMXKJ1NEiqgI(Bjpc+G*9UdXHw-qvYm;2t$ho4lh}pmqU;w$j;DvEuHGSd^@PmT zlXo)M&OzdMhQL;%yKpa%-I_X^d#z0hH;U3>NgHF8lm%nh1Yx6WL|6UmsAR-R2jVS& zH|4o9(k<}BASQR*DROa0(xF1D)(%KZAZt|%ftwWqFaj~>p*Dg}s3Cu8>I_pvSoPXq z{%Wi0S!+_=ZkP=>V-O%=*ncF!aXSe;Ktj(tBn-Dn7*U4=5V0!98PbX+VG%JHL=5>K zh+rJdH_jn28}g+bXM8RCC%}K1SgWe1ntl&@fcKPm%fTNY-+EsY0fvUoW)A(hpu``=j%Lh~jrq>;RYK=KGyG>5Qqc>(_0>j-*$h6u)&$o(1t2_C4U zAgzPN!Ms*X5CH2nre;BIBqzXgumI>FY>M4egCbDd@*54P6UsUyJ0cBXY! zlS;}+tfa=A$c%FBph&ECvQ83Sv!MfcX$nz#J(nmr3kgppZw)|`&HCOHNa2_+v!>+Yr7+fO7BC@!|*_pI3(CmeQ zwv}}_nqqYOK@BvnZ51^`-~N9noR6B9Qy^E%_E8I7*@MZ9gi313{9U4uSXVWptyE!N zrni=AajB(Ru8Ov<|AlIK5hF}PTyeR2T4J*{dQYP}&%%tKg9)p`a6FH$y#UCIfP5Bl zzvq$tvYNI_E*bvzh8Bb=O63)?Xk}Kl0xo|J#pIQ2Gz$sj#b1} zqav1u2+V``*&6aRT4FJGSAz0FX#YD&{SSfyrrP>)8P@#`1OeW}9`{?6j%7qyTT}5`CK;#z;vx&9D4N7MY!N(%M(ja z*ms&0X+HDLT_!o4tA9XlIj+4~Up5FO9mVbuK z0704PP7aYepgfGb}{{9k-CT08G^iZ$}{w$8a~!E|(C)QI=6qxN2u z+}lNu#xfvCKG@C~?&Te+9<)9HmTUvxHu7zgPMg(6nMUi?877U-Fm-2VU1Mk6O_IU3 z^KFMgJNd-LE)PPykLdKM2OiCOc(r_lay{Sna1Ls9XODaU?bF$LkE1fK?LDB|-g|v| zoIwtG5MMpQMNb4^NWgs`pmyrmp$FCq4|EAM!G+&VCj)TE2HyKA#(COc2VNte!R~uD zFDJUwS-zcPlvUg$T=zISuhY{89|XN<&@(vM8As0=^qfJ@^Px@DI!rGFB69UNzP-q= zFENsrouuzgf)}mR-*tKw9<+5l>qdKLC-J&$-@*B-uU|*!E+K>Son99Xa$V-*RG$SL zM36w8sFEeEjr!sZ`ABEy_HcL-?6|_SE#Mpcywc<&Y4Wk{j(YNR7iYHwc;zPMl?QoK z2Dj0?Q`Mi(M2(=4GPJ9+P4IuwK%L$;VQ1m_M#?w(x+CR1U7hJcSJ-P;TU&75<>sSU z8jm~fl6S3fg?JbfeEU&!Cm-odkl%K7rZyN&GfkKUoR53oMC{_KItR*5`6niHZIZkk z;c70&c60U*#O$flm)H}fujS_MdXtWUJ&R|}pFN;>Vo`AtFD1Sa6qYn~Zqm$I^OEMy zpFMk4F%CVM*fLpU(h|O53)MvQ&9?7%w=Tx-s|O@;_&iuBR=uS4n4c&JenYW^@MEIlRa+Ew;bnTDm@(U5W7l4 za(MELrJOR=;4twEu@=Z*OcjGNU*$Z~o z>N@$hhh2lA4NOVfS_s@W?E#opCZT;skf7+mRy&Az0`CI$(eFZb@#1wdd=d+E4bVAJ zn2X8Ib%~M@sl_a-!SmC@5r_^zs0gk=KqUp@1k?pS5EYb}aGHWG$AHmBtLdXJ#Muvn za{x?KA&z#GCV|)50BsJtsy-ejWWCr0>jV&(ZDedIo2{Lrkd;+ zC1c^ImAicgj(sQzid68=Xg0EN*k=erG#}j`3Q@5q6;#QtYB(esfx}jjBQeEBHGwA* z4bLm4KL_Oam{{-*%4A^F!+9~Du$+)`Ikm@;v$&C5;v-O>P+l8sCnaV^P+}F;=Tkim zpm#}tbe^H))09%frrJ7y$Ki&MO!CgaIony7k7py=KL?^NhTk@i+TgrGK8Ak*rrV*I zQ72>4nuo!<0W%HD#ESWCtd_|}dsWIi2D6mc*%D)dJx`~SQ!u$A{-Vx0&DCjMSR)8) zK3agt)Zrj+O?R$P=&h|ow~gMxcF2-1Vk*3dA<92VsVs*|N^5!;?^(Dw--!#yS>U&0 zB4sblM~x7TPscN(LLi0SZ_K@~U~P@{;$;n-wFfahKLq~Q!-w5ii;#Sq5Y|qOTxq(_ zi^zqLi#2RsMwhdWO{v{yo$=y=cYTu3y-jv9HQCjJsVNU8s~oPiH$2xrs+wU`RkEwf z2lJo?p_%B*7It1B@={RTvZgmJkMdzYON?;qHaOASVa9f#J9gq+*)9zAN6>kXDP}B1 zo>JXuQW8zB`CdMo zAB9wB0Vh1MAQR*^FTeGb1=9squWOCOEq@RjtaJi?;7JUHClL;WY{*wlB;0OAngh|E zcec(5+pKu`q-DOhs08uAjGxPiYR{*>iSYws9$_(;2rr8O1@C2OACub<&i=h&WVh5b+&*< z#JRdzs2)pjjWbkqy>!dJrv8Zq3SU_qyWZBw72(Tk=nAqz>OHbttuj=jO9#8|HB`|5 zw>w?OErJi-(VcUgJyM_qSW zEZ%>@^@tT+kN3GMLKWO=e{;RuT%~sX;j#|$`JVdIbuC@x9Zhxr*ifbR^mhMc)z~x2 z{d%C{>8Hh0eO0MMeAQ>3N`IJd_;yWlzmlrzOkL~#&}zBQM)!E@z}>Mj_XvxwfMf1> zi}&;=-7f|xjI-bBm_H8&iLd62Q5L>05_~yNyH{EQt-R=NWo;T9qse$a*T(=V`60#nz_hwbfRQ*w?fG>x|!k zn+H7Bxx~`FBD!?bnrPqXxAB>js_K153%8Z5O}BzLXiA)0s^g2B)@ZWuBhBzV_o0?) zwf*C#+IdS6D!$Zyv$Xl}O|6er`kPa~ZtWL+8>+u&rCv(Z`&c!ill5mUxc3_A=~mv-G3$%H^?E);a)K>{1N!M*t<=^5 z`ZWQW_aNE%)(hcn!~r^0KCpMXr70scZ0vM38e)F zE!5TbHTHJg8a@R%4R+7d2?=~plfb)1 ziM%S!p$!IY)cK-da*+^kmsMoIl;tA zy|qle?247kD)r&&^SMxKZ;9zqO+O%$eR_!~mHU1A&hQS@)&+>TCt>i%p{zVP)FxXK2Ri=pV6CW2h|58^2sxLhVu{|mLHwb8_SdrMYwExR*!Zb zp(kX;F)hZ>=_r3k(o+_n)q6&?pq3^UoGoz*)x=7EKYvfv=^Uv3{H&fT66MoR>h;{+ zi;HI!Te~wI&*}BUPgoz8;0uw{gd4t0lrzuiZM0qdX^2lY3)Sk$#hbzZXXo?`qa&SR zg3tOQtMow*YpL|{dHt9-2VKkVO`Q55OW-UMPa?b(0je;?v{LVRcn5*i?{{F5MT;m4 zXVKsphj=N!y8#1<;hvATJmBIv(11UL(-!tZrZaM^rBGfQOsM!?5*e2BXTX)|hBrpi z-9qBn0HhcS^fi22XQqIIEeb?`+2?{D=j|LGbcTZ4y23;Ffx|kk^Y|Xa89`x#5&A8N zb-V5Nh`}SV5{l&9{^jIp>nbOATUTb2(^w*8TVH9!Tv85Cy-PIu48`uGD10^%h)?vw z;}C^+=AlNoKLv{qP2rloac(B%Cxs{2JZ{*G07TG^LhP zq#r{X^4Yyl<+E5BSV4|O5SgD7vGQ1o*y`g@eHS*H2p&Rx-1*>OQ!EiXBGTUpj$dbt z3asNYu#nBcLN*W6eGlw(jK&IVEPTQ7cuvC9e-Bn-v*8ym#qP~Y_=cOXO z3&5P}i&QE2=?7JC0c{%c3IKQj)vY}AqTR!s>JK;p_;wZliNE+8-VGnHipK818|1*z zH5Ci31_0nl>j2UyJCg1%g5KhT27I0A|5ha*P4FQ<7#roM$GFaWvD9#8L8%&Dq_7Gs z?J}^=z;`s67f_1_M+BtkgEnFxfp{sC9V1=gIF8iBq9?+pC%cB8IJl*C>A4p<_d(MZ zRMV4Ci=OmqdeZICgiX(#OpifDeX2)#W5oc%A>mEKwKJrjA&wlJB4K%|ECKL=JMUhy zwm4{Vor4~}h0p0XEI(zzCH*^V4xRj_?y-E2dH>Ys2B{hLn=kc#mSfcBJH3TfI_qEh zK9`!$E^cgoVt)#PBd9vJ{0tBp3zOv^_A<_{~Dlw~_I!HL160 zVr;c$+BcdTH=`8Nio$=zHgOR(aVd{tTZIwn(|a3v78&^k#xASIhl7pj@v4o2SwV7#d-?`Ul#XhPGxzC7?35HboP)lK7;fQp4*y|^JHZMD)(2I|mtrIVk z>Xh@hu#jQ(FSJ(b`_4maDq+KW-mF>E=hmss$3f2L_1p3@YVp;ix;9Z+$%uMpOIdA%@rc|uQkGx7*k2%`&$rx?R#G6vLZeUtHQ3Rh-6I)9d}Y=F>MCb1G}&NyGJ0_v zaq{wI(Dd8}(8RL&Vf>$o=P{!AN~=@fGl=0MN;?gTkbM>zrUI||lLEIl7ckTU=f5%# z@u(50!0Y@4fjZbb4yyt8|3v|dqB>tL*QqmTY;_%c$*jpepo(=!9$y6h?wT!=?l<5H z;Kg9pD!V_`L>}8_L_0n%=Assgqyg6w-`NYFm#;hOpm)q;sHD=6@sV=-J|kSAe(HXp z{(Ygp#4O!Z6fGa!XE&U=7^T;i`b!NtYq1fl>OI6IbGKL}H z*>wm=+-HGrS{@+VFR=+2xCDqI%DJS0T#sC(NfW&8JrU#b?d0 ztmK`>)#Q|3Y+}eyfJn)*=KHJLwKt%fh5g4fB1nF;%NAzuWoY=$?pD#Bo+4JJt+P>i z51+ZU6z)A=Ni~gQv>hMiZ#_U>U1o!?poyms;dmxr4eEg3GtEMOb%Zcvc8Lu?r`McT zFCp;8SKV2>gr-plZoS^T;L{2V)GA!xD_4Zc`z zt7h(U6b(OX6^&bNq$#wo|NGd9_n>%uCDuW^?}7!IbVQi4*HL@C*L+lV`*=U|yam7U zs@pB`omr%eU*R7WE70`Kf7U7=yTV9N?bm#KcDoqf{j?fBNLH<|JM1!0IX@0?$ZIQ% zRw+EHYfdrpPGHTitmNxJoY(zBq{=pHY&~1D(ujBMO~Ub+p|2);okC7j(HX@Wn zUKx~o;T)zyzBtV5|CN%Rt=QWm1{_*$)zoLp{%D@=9H zX8O_KSynQ2F@VjfIEMGuZX-OI1y}HT%D!~0Huy@e1AJwrL++jJNNIYztQ&nW;rBNI z&R1@oDjgpJ{>=*-HR)$KJ6{Ce)_I=P6;_oAAewh zU!E5z>;K&mUNa9RJcP!Vd|gwV^tK~X&UnZcR@%^0DUD%#`Kmjs4Zg1G$lMR02Jb#( zkL#vqnlE08w)v_{@teQ;+wiFAMk#)qb!dHaw%ohHh$>ySz$53wY^q6NEtkBTYn3Ej zG33(;m||XDXUpteE*{_9D&|wMYVu+AsW~!xd&iZxr~y}pXlJaqx#Vs7C!h{|)gAlq z_fd7^j~$)C^4aya`%$xjf4dQVy?-tESU5w9&88bb?GF>JqH!CHWTj#?QQq6dNd9J;>xJgsSn?0A-quT2$8WH{cUYTTOY5t+uCS-3M`j!?xHQ#xw^br z0*)SA#zNib7b*kYdRPH%*{LaKP7TS9S-%zH4^a?bb>%ZY84mdaMx@-c%@HbJ+GG>4 zWE0HL%@n&Vf2}L7rtCv5`zq5e3*BrqP;Hc#4{7Uwv&dK7iD^1J{b*y`tBzoK*Aupx zSlFkf8G*sc7l%yzzp|23O)Yq4;lgI4p+a9LfbECHv#cb#wN1*^4z5tyc#CaG^S7{- z?NGan=X8Z^(EC-Hcc5Lq>T3CxBD=z#A&CC{atIaJnl^?0PuyeT>UF637lzF zC=LBrxNK5t%dc}O8b80pF51&9xTY^E7EEk+5_-y4T~q_N+HL>17K~gewHf(=arN6# zyG*Sdc(4p=u)g>vnBWTzNA<6)#UPd;AptJm5fYSQNh6R5!|&_g=GzEovJ)q0^pfewE?zEyX+{JJ$a{9h7-}6e62TbKS-N_ zo2q=eDvy%Qw%dMmpY5o&dI3V^JdQ42b-Cq%GF#D3jZH|}0+))fW_37PvfHjuvE5xy zUfph2cz*fNX0bS0#8+L1dCXqBLSbVV_;x#Nq6>GR^=B(<%0lE|w63P@(C2@A@So@+ zz8ckOJ@Fz=9qm2sj+GzouootO0FkTRC4WAvzCu!GPoxeK?J|J#-xp0JHb^wNeWzin z(RJ?Iqc8l5RXAU1b!xo)rdtkt!;J~{jMF2h`klGocY+7UhpH@5w%lcRX5U@N9{Q=u zPL=C++1;>e&Y$fubRTf@nZX54IyMZ7xoe{*y3%iX*4S0mrq8}&N{J&88zVmK*;!z_)eiW}6vcV%ZlBS?S zyjfk6C>Qxlm_FyomYTdi3x8JVyg&Q;qsU(8&wi!9J5c66W-G(N18;YmkI({NbvgLG zjlvUCy6IbwTW?+^(ndYpTU7yzUq#KZ=P2b zVr2enT8x}gVY@Fi`&+liWPbg-oW0Lzt5oIaK1ihSHO@$ov#)^a~b{f64Xl;?oUS@L1+va4UB zt(*I8F$I+Y;Qn81fXg?r=fF_}#s0eNzn;P0zT$0)+Xm8$f7lfgU3!Q-Qf4DN$H<=2 zbxX%KtwTdRjuq1Z2T-;%(k`pG0!8a}z#eI1x$MiB+GUR(uytged+A$Wf=|90`uWAP ztmKJAdHI0RLXp~e+MjEBARfh6%Q{eR*SFD*ONYZUDpL=X?>ugcvvA$MUk}3B<*Tm# z&CIT;6{jjFCSExRLMOMe%WfSs>Z|Oea%8{V$ZwVuw(ZGyE&x#SE3y z)rP7lH>{=a%X3C9p9uxytFDcn)I*LtgpkR&pWyCK8>j~>LX>f-*EN4C&o@R+S~n z+`i6GdDvfefy+icWtSDcggw*h8Mnz1l=XhGre2_Y>`mQ@l_^SAbjt0TfM^C^Ainxn zRx<2z?OGokF_M)m#S_5z_Ydr{itBoEjaND4$15Xns+3QbV&A0ZS5^}LNlk?;Ip(;{ z*7uBT;5Rj8rY!soJ>BJ~t)n&J@c(8~uGNs|k-Jerk$2)KFn$_lun=4PH**(u45ck1 z3^_j0=vosnEaB|z1XZbt>~tK9(5uI63eMg8*N#jLt07#4H7sES#;4R&Xr|QQ&7a3L zJO$r@uevMgQyFs8aigOm@r~n9xhFfy{0eCIYrPHp1)&OC+vX?!bYv(R;;XKEn$nfw z+b9H!D}dl)Z_Y@RNkcW43?70#@{6#;s{D17J9CN*%Mgv#jj)8K6Q+-!JGZc}8;B7e O!@nF@&|++c6a60``hB4Q diff --git a/settings/repository/net.sf/sam-1.58.1057.xml b/settings/repository/net.sf/sam-1.58.1057.xml new file mode 100644 index 000000000..4f0dfe44e --- /dev/null +++ b/settings/repository/net.sf/sam-1.58.1057.xml @@ -0,0 +1,3 @@ + + + From a259bfefd4458638812751c09616e09761479622 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Thu, 29 Dec 2011 16:22:14 -0500 Subject: [PATCH 378/380] First commit addressing problems running RTC in parallel. Turns out that because the RTC is the first walker to 'correctly' tree reduce according to functional programming standards, the RTC has revealed a few problems with the tree reducer holding on to too much data. This is the first and smaller of two commits to reduce memory consumption. The second commit will likely be pushed after GATK1.4 is released. --- .../executive/HierarchicalMicroScheduler.java | 32 +++++++------------ .../HierarchicalMicroSchedulerMBean.java | 12 ------- 2 files changed, 11 insertions(+), 33 deletions(-) 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 b0043e68c..39e1bdc72 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java @@ -15,6 +15,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.threading.ThreadPoolMonitor; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ExecutorService; @@ -41,7 +42,6 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar */ private ThreadLocalOutputTracker outputTracker = new ThreadLocalOutputTracker(); - private final Queue traverseTasks = new LinkedList(); private final Queue reduceTasks = new LinkedList(); /** @@ -49,6 +49,11 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar */ private Throwable error = null; + /** + * Queue of incoming shards. + */ + private Iterator traversalTasks; + /** * Keep a queue of shard traversals, and constantly monitor it to see what output * merge tasks remain. @@ -56,9 +61,6 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar */ private final Queue outputMergeTasks = new LinkedList(); - /** How many total tasks were in the queue at the start of run. */ - private int totalTraversals = 0; - /** How many shard traversals have run to date? */ private int totalCompletedTraversals = 0; @@ -92,13 +94,11 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar if (!( walker instanceof TreeReducible )) throw new IllegalArgumentException("The GATK can currently run in parallel only with TreeReducible walkers"); + this.traversalTasks = shardStrategy.iterator(); + ReduceTree reduceTree = new ReduceTree(this); initializeWalker(walker); - for (Shard shard : shardStrategy) - traverseTasks.add(shard); - totalTraversals = traverseTasks.size(); - while (isShardTraversePending() || isTreeReducePending()) { // Check for errors during execution. if(hasTraversalErrorOccurred()) @@ -190,7 +190,7 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar * @return true if a shard traversal is waiting; false otherwise. */ protected boolean isShardTraversePending() { - return traverseTasks.size() > 0; + return traversalTasks.hasNext(); } /** @@ -283,10 +283,10 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar * @param reduceTree Tree of reduces to which to add this shard traverse. */ protected void queueNextShardTraverse( Walker walker, ReduceTree reduceTree ) { - if (traverseTasks.size() == 0) + if (!traversalTasks.hasNext()) throw new IllegalStateException("Cannot traverse; no pending traversals exist."); - Shard shard = traverseTasks.remove(); + Shard shard = traversalTasks.next(); // todo -- add ownership claim here @@ -398,16 +398,6 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar } - /** {@inheritDoc} */ - public int getTotalNumberOfShards() { - return totalTraversals; - } - - /** {@inheritDoc} */ - public int getRemainingNumberOfShards() { - return traverseTasks.size(); - } - /** {@inheritDoc} */ public int getNumberOfTasksInReduceQueue() { return reduceTasks.size(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroSchedulerMBean.java b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroSchedulerMBean.java index 21a87963b..530285db0 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroSchedulerMBean.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroSchedulerMBean.java @@ -17,18 +17,6 @@ package org.broadinstitute.sting.gatk.executive; * microscheduler is behaving. */ public interface HierarchicalMicroSchedulerMBean extends MicroSchedulerMBean { - /** - * What is the total number of shards assigned to this microscheduler? - * @return Total number of shards to process. - */ - public int getTotalNumberOfShards(); - - /** - * How many shards are remaining for this microscheduler to process? - * @return Remaining number of shards to process. - */ - public int getRemainingNumberOfShards(); - /** * How many tree reduces are waiting in the tree reduce queue? * @return Total number of reduces waiting in the tree reduce queue? From c7d0a9ebeece1c6e2337ab4fbf6e33afb8a8d636 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 30 Dec 2011 00:19:24 -0500 Subject: [PATCH 379/380] Forgot to test for inter-chromosomal mates in the adaptor clipping * Fixing bug caught by Eric (and Kristian) --- .../sting/utils/sam/ReadUtils.java | 173 +++++++++--------- .../sting/utils/sam/ReadUtilsUnitTest.java | 41 +++-- 2 files changed, 113 insertions(+), 101 deletions(-) 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 24fd48387..f9a5045d4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -43,7 +43,8 @@ import java.util.*; * @version 0.1 */ public class ReadUtils { - private ReadUtils() { } + private ReadUtils() { + } private static int DEFAULT_ADAPTOR_SIZE = 100; @@ -57,10 +58,11 @@ public class ReadUtils { /** * A HashMap of the SAM spec read flag names - * + *

* Note: This is not being used right now, but can be useful in the future */ private static final Map readFlagNames = new HashMap(); + static { readFlagNames.put(0x1, "Paired"); readFlagNames.put(0x2, "Proper"); @@ -77,47 +79,49 @@ public class ReadUtils { /** * This enum represents all the different ways in which a read can overlap an interval. - * + *

* NO_OVERLAP_CONTIG: * read and interval are in different contigs. - * + *

* NO_OVERLAP_LEFT: * the read does not overlap the interval. - * - * |----------------| (interval) - * <----------------> (read) - * + *

+ * |----------------| (interval) + * <----------------> (read) + *

* NO_OVERLAP_RIGHT: * the read does not overlap the interval. - * - * |----------------| (interval) - * <----------------> (read) - * + *

+ * |----------------| (interval) + * <----------------> (read) + *

* OVERLAP_LEFT: * the read starts before the beginning of the interval but ends inside of it - * - * |----------------| (interval) - * <----------------> (read) - * + *

+ * |----------------| (interval) + * <----------------> (read) + *

* OVERLAP_RIGHT: * the read starts inside the interval but ends outside of it - * - * |----------------| (interval) - * <----------------> (read) - * + *

+ * |----------------| (interval) + * <----------------> (read) + *

* OVERLAP_LEFT_AND_RIGHT: * the read starts before the interval and ends after the interval - * - * |-----------| (interval) - * <-------------------> (read) - * + *

+ * |-----------| (interval) + * <-------------------> (read) + *

* OVERLAP_CONTAINED: * the read starts and ends inside the interval - * - * |----------------| (interval) - * <--------> (read) + *

+ * |----------------| (interval) + * <--------> (read) */ - public enum ReadAndIntervalOverlap {NO_OVERLAP_CONTIG, NO_OVERLAP_LEFT, NO_OVERLAP_RIGHT, NO_OVERLAP_HARDCLIPPED_LEFT, NO_OVERLAP_HARDCLIPPED_RIGHT, OVERLAP_LEFT, OVERLAP_RIGHT, OVERLAP_LEFT_AND_RIGHT, OVERLAP_CONTAINED} + public enum ReadAndIntervalOverlap { + NO_OVERLAP_CONTIG, NO_OVERLAP_LEFT, NO_OVERLAP_RIGHT, NO_OVERLAP_HARDCLIPPED_LEFT, NO_OVERLAP_HARDCLIPPED_RIGHT, OVERLAP_LEFT, OVERLAP_RIGHT, OVERLAP_LEFT_AND_RIGHT, OVERLAP_CONTAINED + } /** * Creates a SAMFileWriter with the given compression level if you request a bam file. Creates a regular @@ -137,15 +141,15 @@ public class ReadUtils { /** * is this base inside the adaptor of the read? - * + *

* There are two cases to treat here: - * + *

* 1) Read is in the negative strand => Adaptor boundary is on the left tail * 2) Read is in the positive strand => Adaptor boundary is on the right tail - * + *

* Note: We return false to all reads that are UNMAPPED or have an weird big insert size (probably due to mismapping or bigger event) * - * @param read the read to test + * @param read the read to test * @param basePos base position in REFERENCE coordinates (not read coordinates) * @return whether or not the base is in the adaptor */ @@ -162,39 +166,37 @@ public class ReadUtils { * the read boundary. If the read is in the positive strand, this is the first base after the end of the * fragment (Picard calls it 'insert'), if the read is in the negative strand, this is the first base before the * beginning of the fragment. - * + *

* There are two cases we need to treat here: - * + *

* 1) Our read is in the reverse strand : - * - * <----------------------| * - * |---------------------> - * - * in these cases, the adaptor boundary is at the mate start (minus one) - * + *

+ * <----------------------| * + * |---------------------> + *

+ * in these cases, the adaptor boundary is at the mate start (minus one) + *

* 2) Our read is in the forward strand : - * - * |----------------------> * - * <----------------------| - * - * in these cases the adaptor boundary is at the start of the read plus the inferred insert size (plus one) + *

+ * |----------------------> * + * <----------------------| + *

+ * in these cases the adaptor boundary is at the start of the read plus the inferred insert size (plus one) * * @param read the read being tested for the adaptor boundary * @return the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read. NULL if the read is unmapped or the insert size cannot be determined (and is necessary for the calculation). */ public static Integer getAdaptorBoundary(final SAMRecord read) { - if ( read.getReadUnmappedFlag() ) - return null; // don't worry about unmapped pairs + final int insertSize = Math.abs(read.getInferredInsertSize()); // the inferred insert size can be negative if the mate is mapped before the read (so we take the absolute value) - final int isize = Math.abs(read.getInferredInsertSize()); // the inferred insert size can be negative if the mate is mapped before the read (so we take the absolute value) - int adaptorBoundary; // the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read) + if (insertSize == 0 || read.getReadUnmappedFlag()) // no adaptors in reads with mates in another + return null; // chromosome or unmapped pairs - if ( read.getReadNegativeStrandFlag() ) - adaptorBoundary = read.getMateAlignmentStart() - 1; // case 1 (see header) - else if (isize > 0) - adaptorBoundary = read.getAlignmentStart() + isize + 1; // case 2 (see header) + int adaptorBoundary; // the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read) + if (read.getReadNegativeStrandFlag()) + adaptorBoundary = read.getMateAlignmentStart() - 1; // case 1 (see header) else - return null; // this is a case 2 where for some reason we cannot estimate the insert size + adaptorBoundary = read.getAlignmentStart() + insertSize + 1; // case 2 (see header) return adaptorBoundary; } @@ -262,14 +264,15 @@ public class ReadUtils { /** * If a read starts in INSERTION, returns the first element length. - * + *

* Warning: If the read has Hard or Soft clips before the insertion this function will return 0. + * * @param read * @return the length of the first insertion, or 0 if there is none (see warning). */ public final static int getFirstInsertionOffset(SAMRecord read) { CigarElement e = read.getCigar().getCigarElement(0); - if ( e.getOperator() == CigarOperator.I ) + if (e.getOperator() == CigarOperator.I) return e.getLength(); else return 0; @@ -277,14 +280,15 @@ public class ReadUtils { /** * If a read ends in INSERTION, returns the last element length. - * + *

* Warning: If the read has Hard or Soft clips after the insertion this function will return 0. + * * @param read * @return the length of the last insertion, or 0 if there is none (see warning). */ public final static int getLastInsertionOffset(SAMRecord read) { CigarElement e = read.getCigar().getCigarElement(read.getCigarLength() - 1); - if ( e.getOperator() == CigarOperator.I ) + if (e.getOperator() == CigarOperator.I) return e.getLength(); else return 0; @@ -293,7 +297,8 @@ public class ReadUtils { /** * Determines what is the position of the read in relation to the interval. * Note: This function uses the UNCLIPPED ENDS of the reads for the comparison. - * @param read the read + * + * @param read the read * @param interval the interval * @return the overlap type as described by ReadAndIntervalOverlap enum (see above) */ @@ -304,30 +309,30 @@ public class ReadUtils { int uStart = read.getUnclippedStart(); int uStop = read.getUnclippedEnd(); - if ( !read.getReferenceName().equals(interval.getContig()) ) + if (!read.getReferenceName().equals(interval.getContig())) return ReadAndIntervalOverlap.NO_OVERLAP_CONTIG; - else if ( uStop < interval.getStart() ) + else if (uStop < interval.getStart()) return ReadAndIntervalOverlap.NO_OVERLAP_LEFT; - else if ( uStart > interval.getStop() ) + else if (uStart > interval.getStop()) return ReadAndIntervalOverlap.NO_OVERLAP_RIGHT; - else if ( sStop < interval.getStart() ) + else if (sStop < interval.getStart()) return ReadAndIntervalOverlap.NO_OVERLAP_HARDCLIPPED_LEFT; - else if ( sStart > interval.getStop() ) + else if (sStart > interval.getStop()) return ReadAndIntervalOverlap.NO_OVERLAP_HARDCLIPPED_RIGHT; - else if ( (sStart >= interval.getStart()) && - (sStop <= interval.getStop()) ) + else if ((sStart >= interval.getStart()) && + (sStop <= interval.getStop())) return ReadAndIntervalOverlap.OVERLAP_CONTAINED; - else if ( (sStart < interval.getStart()) && - (sStop > interval.getStop()) ) + else if ((sStart < interval.getStart()) && + (sStop > interval.getStop())) return ReadAndIntervalOverlap.OVERLAP_LEFT_AND_RIGHT; - else if ( (sStart < interval.getStart()) ) + else if ((sStart < interval.getStart())) return ReadAndIntervalOverlap.OVERLAP_LEFT; else @@ -359,12 +364,12 @@ public class ReadUtils { /** * Returns the read coordinate corresponding to the requested reference coordinate. - * + *

* WARNING: if the requested reference coordinate happens to fall inside a deletion in the read, this function * will return the last read base before the deletion. This function returns a * Pair(int readCoord, boolean fallsInsideDeletion) so you can choose which readCoordinate to use when faced with * a deletion. - * + *

* SUGGESTION: Use getReadCoordinateForReferenceCoordinate(GATKSAMRecord, int, ClippingTail) instead to get a * pre-processed result according to normal clipping needs. Or you can use this function and tailor the * behavior to your needs. @@ -403,7 +408,7 @@ public class ReadUtils { if (goalReached) { // Is this base's reference position within this cigar element? Or did we use it all? - boolean endsWithinCigar = shift < cigarElement.getLength(); + boolean endsWithinCigar = shift < cigarElement.getLength(); // If it isn't, we need to check the next one. There should *ALWAYS* be a next one // since we checked if the goal coordinate is within the read length, so this is just a sanity check. @@ -416,7 +421,7 @@ public class ReadUtils { if (endsWithinCigar) fallsInsideDeletion = cigarElement.getOperator() == CigarOperator.DELETION; - // if we end outside the current cigar element, we need to check if the next element is an insertion or deletion. + // if we end outside the current cigar element, we need to check if the next element is an insertion or deletion. else { nextCigarElement = cigarElementIterator.next(); @@ -437,21 +442,21 @@ public class ReadUtils { if (!fallsInsideDeletion && cigarElement.getOperator().consumesReadBases()) readBases += shift; - // If we reached our goal inside a deletion, but the deletion is the next cigar element then we need - // to add the shift of the current cigar element but go back to it's last element to return the last - // base before the deletion (see warning in function contracts) + // If we reached our goal inside a deletion, but the deletion is the next cigar element then we need + // to add the shift of the current cigar element but go back to it's last element to return the last + // base before the deletion (see warning in function contracts) else if (fallsInsideDeletion && !endsWithinCigar) readBases += shift - 1; - // If we reached our goal inside a deletion then we must backtrack to the last base before the deletion + // If we reached our goal inside a deletion then we must backtrack to the last base before the deletion else if (fallsInsideDeletion && endsWithinCigar) readBases--; - } } + } + + if (!goalReached) + throw new ReviewedStingException("Somehow the requested coordinate is not covered by the read. Too many deletions?"); - if (!goalReached) - throw new ReviewedStingException("Somehow the requested coordinate is not covered by the read. Too many deletions?"); - return new Pair(readBases, fallsInsideDeletion); } @@ -460,7 +465,7 @@ public class ReadUtils { * Compares two SAMRecords only the basis on alignment start. Note that * comparisons are performed ONLY on the basis of alignment start; any * two SAM records with the same alignment start will be considered equal. - * + *

* Unmapped alignments will all be considered equal. */ @@ -474,7 +479,7 @@ public class ReadUtils { /** * Is a base inside a read? * - * @param read the read to evaluate + * @param read the read to evaluate * @param referenceCoordinate the reference coordinate of the base to test * @return true if it is inside the read, false otherwise. */ @@ -497,6 +502,4 @@ public class ReadUtils { } - - } diff --git a/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java index e9269ff48..b9f831028 100755 --- a/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/sam/ReadUtilsUnitTest.java @@ -1,12 +1,8 @@ package org.broadinstitute.sting.utils.sam; import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.pileup.PileupElement; -import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; -import org.broadinstitute.sting.utils.sam.GATKSAMRecord; -import org.broadinstitute.sting.utils.sam.ReadUtils; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -16,12 +12,12 @@ 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, 1}; + 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() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1,1,1000); + 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()); @@ -39,15 +35,15 @@ public class ReadUtilsUnitTest extends BaseTest { Assert.assertEquals(read.getReducedReadCounts(), null, "No reduced read tag in normal read"); Assert.assertTrue(reducedRead.isReducedRead(), "isReducedRead is true for reduced read"); - for ( int i = 0; i < reducedRead.getReadLength(); i++) { + for (int i = 0; i < reducedRead.getReadLength(); i++) { Assert.assertEquals(reducedRead.getReducedCount(i), REDUCED_READ_COUNTS[i], "Reduced read count not set to the expected value at " + i); } } @Test public void testReducedReadPileupElement() { - PileupElement readp = new PileupElement(read,0); - PileupElement reducedreadp = new PileupElement(reducedRead,0); + PileupElement readp = new PileupElement(read, 0); + PileupElement reducedreadp = new PileupElement(reducedRead, 0); Assert.assertFalse(readp.isReducedRead()); @@ -58,14 +54,14 @@ public class ReadUtilsUnitTest extends BaseTest { @Test public void testGetAdaptorBoundary() { - final byte [] bases = {'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T'}; - final byte [] quals = {30, 30, 30, 30, 30, 30, 30, 30}; + final byte[] bases = {'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T'}; + final byte[] quals = {30, 30, 30, 30, 30, 30, 30, 30}; final String cigar = "8M"; final int fragmentSize = 10; final int mateStart = 1000; final int BEFORE = mateStart - 2; final int AFTER = mateStart + 2; - int myStart, boundary; + Integer myStart, boundary; GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(bases, quals, cigar); read.setMateAlignmentStart(mateStart); @@ -76,28 +72,41 @@ public class ReadUtilsUnitTest extends BaseTest { read.setAlignmentStart(myStart); read.setReadNegativeStrandFlag(false); boundary = ReadUtils.getAdaptorBoundary(read); - Assert.assertEquals(boundary, myStart + fragmentSize + 1); + Assert.assertEquals(boundary.intValue(), myStart + fragmentSize + 1); // Test case 2: positive strand, second read myStart = AFTER; read.setAlignmentStart(myStart); read.setReadNegativeStrandFlag(false); boundary = ReadUtils.getAdaptorBoundary(read); - Assert.assertEquals(boundary, myStart + fragmentSize + 1); + Assert.assertEquals(boundary.intValue(), myStart + fragmentSize + 1); // Test case 3: negative strand, second read myStart = AFTER; read.setAlignmentStart(myStart); read.setReadNegativeStrandFlag(true); boundary = ReadUtils.getAdaptorBoundary(read); - Assert.assertEquals(boundary, mateStart - 1); + Assert.assertEquals(boundary.intValue(), mateStart - 1); // Test case 4: negative strand, first read myStart = BEFORE; read.setAlignmentStart(myStart); read.setReadNegativeStrandFlag(true); boundary = ReadUtils.getAdaptorBoundary(read); - Assert.assertEquals(boundary, mateStart - 1); + Assert.assertEquals(boundary.intValue(), mateStart - 1); + // Test case 5: mate is mapped to another chromosome (test both strands) + read.setInferredInsertSize(0); + read.setReadNegativeStrandFlag(true); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertNull(boundary); + read.setReadNegativeStrandFlag(false); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertNull(boundary); + + // Test case 6: read is unmapped + read.setReadUnmappedFlag(true); + boundary = ReadUtils.getAdaptorBoundary(read); + Assert.assertNull(boundary); } } From 55cfa76cf3db53fded6183b4108932f685db41b8 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 30 Dec 2011 12:50:40 -0500 Subject: [PATCH 380/380] Updated integration tests for the new adaptor clipping fix. --- .../sting/utils/sam/ReadUtils.java | 4 +- .../walkers/PileupWalkerIntegrationTest.java | 4 +- .../VariantAnnotatorIntegrationTest.java | 12 +++--- .../CallableLociWalkerIntegrationTest.java | 8 ++-- .../DepthOfCoverageB36IntegrationTest.java | 14 +++---- .../DepthOfCoverageIntegrationTest.java | 38 +++++++++---------- .../UnifiedGenotyperIntegrationTest.java | 8 ++-- .../RecalibrationWalkersIntegrationTest.java | 28 +++++++------- 8 files changed, 57 insertions(+), 59 deletions(-) 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 f9a5045d4..f2e54713f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -184,12 +184,12 @@ public class ReadUtils { * in these cases the adaptor boundary is at the start of the read plus the inferred insert size (plus one) * * @param read the read being tested for the adaptor boundary - * @return the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read. NULL if the read is unmapped or the insert size cannot be determined (and is necessary for the calculation). + * @return the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read. NULL if the read is unmapped or the mate is mapped to another contig. */ public static Integer getAdaptorBoundary(final SAMRecord read) { final int insertSize = Math.abs(read.getInferredInsertSize()); // the inferred insert size can be negative if the mate is mapped before the read (so we take the absolute value) - if (insertSize == 0 || read.getReadUnmappedFlag()) // no adaptors in reads with mates in another + if (insertSize == 0 || read.getReadUnmappedFlag()) // no adaptors in reads with mates in another return null; // chromosome or unmapped pairs int adaptorBoundary; // the reference coordinate for the adaptor boundary (effectively the first base IN the adaptor, closest to the read) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java index 8bea5385a..e26d6174b 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/PileupWalkerIntegrationTest.java @@ -29,12 +29,10 @@ public class PileupWalkerIntegrationTest extends WalkerTest { String gatk_args = "-T Pileup -I " + validationDataLocation + "OV-0930.normal.chunk.bam " + "-R " + hg18Reference + " -show_indels -o %s"; - String expected_md5="da2a02d02abac9de14cc4b187d8595a1"; + String expected_md5="06eedc2e7927650961d99d703f4301a4"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args,1,Arrays.asList(expected_md5)); executeTest("Testing the extended pileup with indel records included on a small chunk of Ovarian dataset with 20 indels (1 D, 19 I)", spec); - // before Adaptor clipping - // String expected_md5="06eedc2e7927650961d99d703f4301a4"; } } 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 fa0b62cfd..8b101d1d5 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("ac5f409856a1b79316469733e62abb91")); + Arrays.asList("e70eb5f80c93e366dcbe3cf684c154e4")); 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("f9aa7bee5a61ac1a9187d0cf1e8af471")); + Arrays.asList("2977bb30c8b84a5f4094fe6090658561")); executeTest("test file has annotations, asking for annotations, #2", spec); } @@ -66,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("6f27fd863b6718d59d2a2d8e2a20bcae")); + Arrays.asList("1e52761fdff73a5361b5eb0a6e5d9dad")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -74,7 +74,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("40bbd3d5a2397a007c0e74211fb33433")); + Arrays.asList("0948cd1dba7d61f283cc4cf2a7757d92")); executeTest("test file doesn't have annotations, asking for annotations, #2", spec); } @@ -82,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("40622d39072b298440a77ecc794116e7")); + Arrays.asList("bb4eebfaffc230cb8a31e62e7b53a300")); executeTest("test exclude annotations", spec); } @@ -90,7 +90,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("31faae1bc588d195ff553cf6c47fabfa")); + Arrays.asList("062155edec46a8c52243475fbf3a2943")); executeTest("test overwriting header", spec); } 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 186e5c3b7..02332b64e 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 @@ -32,13 +32,13 @@ import java.util.Arrays; public class CallableLociWalkerIntegrationTest extends WalkerTest { final static String commonArgs = "-R " + b36KGReference + " -T CallableLoci -I " + validationDataLocation + "/NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s"; - final static String SUMMARY_MD5 = "cd597a8dae35c226a2cb110b1c9f32d5"; + final static String SUMMARY_MD5 = "ffdbd9cdcb4169ebed5ae4bec797260f"; @Test public void testCallableLociWalkerBed() { String gatk_args = commonArgs + " -format BED -L 1:10,000,000-11,000,000 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, - Arrays.asList("c86ac1ef404c11d5e5452e020c8f7ce9", SUMMARY_MD5)); + Arrays.asList("9e4ec9c23f21a8162d27a39ab057398c", SUMMARY_MD5)); executeTest("formatBed", spec); } @@ -46,7 +46,7 @@ public class CallableLociWalkerIntegrationTest extends WalkerTest { public void testCallableLociWalkerPerBase() { String gatk_args = commonArgs + " -format STATE_PER_BASE -L 1:10,000,000-11,000,000 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, - Arrays.asList("d8536a55fe5f6fdb1ee6c9511082fdfd", SUMMARY_MD5)); + Arrays.asList("e6044b4495ef24f542403e6a94437068", SUMMARY_MD5)); executeTest("format_state_per_base", spec); } @@ -62,7 +62,7 @@ public class CallableLociWalkerIntegrationTest extends WalkerTest { public void testCallableLociWalker3() { String gatk_args = commonArgs + " -format BED -L 1:10,000,000-11,000,000 -minDepth 10 -maxDepth 100 --minBaseQuality 10 --minMappingQuality 20 -summary %s"; WalkerTestSpec spec = new WalkerTestSpec(gatk_args, 2, - Arrays.asList("bc966060184bf4605a31da7fe383464e", "d624eda8f6ed14b9251ebeec73e37867")); + Arrays.asList("4496551d4493857e5153d8172965e527", "b0667e31af9aec02eaf73ca73ec16937")); executeTest("formatBed lots of arguments", spec); } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java index f4cd4968b..84603f066 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageB36IntegrationTest.java @@ -61,13 +61,13 @@ public class DepthOfCoverageB36IntegrationTest extends WalkerTest { File baseOutputFile = this.createTempFile("depthofcoveragemapq0",".tmp"); spec.setOutputFileLocation(baseOutputFile); - spec.addAuxFile("5b6c16a1c667c844882e9dce71454fc4",baseOutputFile); - spec.addAuxFile("fc161ec1b61dc67bc6a5ce36cb2d02c9", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); - spec.addAuxFile("89321bbfb76a4e1edc0905d50503ba1f", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); + spec.addAuxFile("f39af6ad99520fd4fb27b409ab0344a0",baseOutputFile); + spec.addAuxFile("6b15f5330414b6d4e2f6caea42139fa1", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); + spec.addAuxFile("cc6640d82077991dde8a2b523935cdff", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); spec.addAuxFile("0fb627234599c258a3fee1b2703e164a", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); - spec.addAuxFile("4dd16b659065e331ed4bd3ab0dae6c1b", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); - spec.addAuxFile("2be0c18b501f4a3d8c5e5f99738b4713", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); - spec.addAuxFile("5a26ef61f586f58310812580ce842462", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); + spec.addAuxFile("cb73a0fa0cee50f1fb8f249315d38128", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); + spec.addAuxFile("347b47ef73fbd4e277704ddbd7834f69", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); + spec.addAuxFile("4ec920335d4b9573f695c39d62748089", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); execute("testMapQ0Only",spec); @@ -84,7 +84,7 @@ public class DepthOfCoverageB36IntegrationTest extends WalkerTest { File baseOutputFile = this.createTempFile("testManySamples",".tmp"); spec.setOutputFileLocation(baseOutputFile); - spec.addAuxFile("d73fa1fc492f7dcc1d75056f8c12c92a",baseOutputFile); + spec.addAuxFile("c9561b52344536d2b06ab97b0bb1a234",baseOutputFile); execute("testLotsOfSamples",spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java index 9d6638d53..f2f72978f 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java @@ -55,25 +55,25 @@ public class DepthOfCoverageIntegrationTest extends WalkerTest { spec.setOutputFileLocation(baseOutputFile); // now add the expected files that get generated - spec.addAuxFile("19e862f7ed3de97f2569803f766b7433", baseOutputFile); - spec.addAuxFile("c64cc5636d4880b80b71169ed1832cd7", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_counts")); - spec.addAuxFile("1a8ba07a60e55f9fdadc89d00b1f3394", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_proportions")); - spec.addAuxFile("0075cead73a901e3a9d07c5d9c2b75f4", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_statistics")); - spec.addAuxFile("d757be2f953f893e66eff1ef1f0fff4e", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); - spec.addAuxFile("de08996729c774590d6a4954c906fe84", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_statistics")); - spec.addAuxFile("58ad39b100d1f2af7d119f28ba626bfd", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_summary")); - spec.addAuxFile("0b4ce6059e6587ae5a986afbbcc7d783", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_counts")); - spec.addAuxFile("adc2b2babcdd72a843878acf2d510ca7", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_proportions")); - spec.addAuxFile("884281c139241c5db3c9f90e8684d084", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_statistics")); - spec.addAuxFile("b90636cad74ff4f6b9ff9a596e145bd6", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_summary")); - spec.addAuxFile("ad540b355ef90c566bebaeabd70026d2", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_statistics")); - spec.addAuxFile("27fe09a02a5b381e0ed633587c0f4b23", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_summary")); - spec.addAuxFile("5fcd53b4bd167b5e6d5f92329cf8678e", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); - spec.addAuxFile("7a2a19e54f73a8e07de2f020f1f913dd", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); - spec.addAuxFile("852a079c5e9e93e7daad31fd6a9f4a49", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); - spec.addAuxFile("0828762842103edfaf115ef4e50809c6", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); - spec.addAuxFile("5c5aeb28419bba1decb17f8a166777f2", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); - spec.addAuxFile("e5fd6216b3d6a751f3a90677b4e5bf3c", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); + spec.addAuxFile("2f072fd8b41b5ac1108797f89376c797", baseOutputFile); + spec.addAuxFile("d17ac7cc0b58ba801d2b0727a363d615", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_counts")); + spec.addAuxFile("c05190c9e6239cdb1cd486edcbc23505", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_proportions")); + spec.addAuxFile("9cd395f47b329b9dd00ad024fcac9929", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_statistics")); + spec.addAuxFile("c94a52b4e73a7995319e0b570c80d2f7", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); + spec.addAuxFile("1970a44efb7ace4e51a37f0bd2dc84d1", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_statistics")); + spec.addAuxFile("c321c542be25359d2e26d45cbeb6d7ab", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_summary")); + spec.addAuxFile("9023cc8939777d515cd2895919a99688", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_counts")); + spec.addAuxFile("3597b69e90742c5dd7c83fbc74d079f3", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_cumulative_coverage_proportions")); + spec.addAuxFile("7b9d0e93bf5b5313995be7010ef1f528", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_statistics")); + spec.addAuxFile("1a6ea3aa759fb154ccc4e171ebca9d02", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_interval_summary")); + spec.addAuxFile("b492644ff06b4ffb044d5075cd168abf", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_statistics")); + spec.addAuxFile("77cef87dc4083a7b60b7a7b38b4c0bd8", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".read_group_summary")); + spec.addAuxFile("8e1adbe37b98bb2271ba13932d5c947f", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_counts")); + spec.addAuxFile("761d2f9daf2ebaf43abf65c8fd2fcd05", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); + spec.addAuxFile("df0ba76e0e6082c0d29fcfd68efc6b77", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); + spec.addAuxFile("0582b4681dbc02ece2dfe2752dcfd228", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); + spec.addAuxFile("0685214965bf1863f7ce8de2e38af060", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); + spec.addAuxFile("7a0cd8a5ebaaa82621fd3b5aed9c32fe", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); execute("testBaseOutputNoFiltering",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 3c6131d6c..f7d6af3a7 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 @@ -227,7 +227,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("d87ce4b405d4f7926d1c36aee7053975")); + Arrays.asList("b11df6587e4e16cb819d76a900446946")); executeTest(String.format("test indel caller in SLX"), spec); } @@ -255,7 +255,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("c5989e5d67d9e5fe8c5c956f12a975da")); + Arrays.asList("59068bc8888ad5f08790946066d76602")); executeTest(String.format("test indel calling, multiple technologies"), spec); } @@ -265,7 +265,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("daca0741278de32e507ad367e67753b6")); + Arrays.asList("fa4f3ee67d98b64102a8a3ec81a3bc81")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -275,7 +275,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("0ccc4e876809566510429c64adece2c7")); + Arrays.asList("df90890e43d735573a3b3e4f289ca46b")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java index b4b1f7b8e..d45e663b0 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationWalkersIntegrationTest.java @@ -5,11 +5,11 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.HashMap; -import java.util.Map; -import java.util.Arrays; -import java.util.List; import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class RecalibrationWalkersIntegrationTest extends WalkerTest { static HashMap paramsFiles = new HashMap(); @@ -32,10 +32,10 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @DataProvider(name = "cctestdata") public Object[][] createCCTestData() { - new CCTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "9469d6b65880abe4e5babc1c1a69889d" ); + new CCTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "ab4940a16ab990181bd8368c76b23853" ); new CCTest( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "17d4b8001c982a70185e344929cf3941"); new CCTest( validationDataLocation + "NA12873.454.SRP000031.2009_06.chr1.10_20mb.bam", "36c0c467b6245c2c6c4e9c956443a154" ); - new CCTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "ed15f8bf03bb2ea9b7c26844be829c0d" ); + new CCTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "955a8fa2ddb2b04c406766ccd9ac45cc" ); return CCTest.getTests(CCTest.class); } @@ -89,10 +89,10 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @DataProvider(name = "trtestdata") public Object[][] createTRTestData() { - new TRTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "f020725d9f75ad8f1c14bfae056e250f" ); + new TRTest( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "0b7123ae9f4155484b68e4a4f96c5504" ); new TRTest( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "d04cf1f6df486e45226ebfbf93a188a5"); new TRTest( validationDataLocation + "NA12873.454.SRP000031.2009_06.chr1.10_20mb.bam", "b2f4757bc47cf23bd9a09f756c250787" ); - new TRTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "313a21a8a88e3460b6e71ec5ffc50f0f" ); + new TRTest( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.bam", "502c7df4d4923c4d078b014bf78bed34" ); return TRTest.getTests(TRTest.class); } @@ -123,7 +123,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesUseOriginalQuals() { HashMap e = new HashMap(); - e.put( validationDataLocation + "originalQuals.1kg.chr1.1-1K.bam", "bd8288b1fc7629e2e8c2cf7f65fefa8f"); + e.put( validationDataLocation + "originalQuals.1kg.chr1.1-1K.bam", "0b88d0e8c97e83bdeee2064b6730abff"); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -147,7 +147,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testTableRecalibratorMaxQ70() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "f020725d9f75ad8f1c14bfae056e250f" ); + e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "0b7123ae9f4155484b68e4a4f96c5504" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -176,7 +176,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesSolidIndelsRemoveRefBias() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "1f643bca090478ba68aac88db835a629" ); + e.put( validationDataLocation + "NA19240.chr1.BFAST.SOLID.bam", "8379f24cf5312587a1f92c162ecc220f" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -230,7 +230,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesBED() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "b00e99219aeafe2516c6232b7d6a0a00"); + e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "7e973328751d233653530245d404a64d"); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -254,7 +254,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesVCFPlusDBsnp() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "7b92788ce92f49415af3a75a2e4a2b33"); + e.put( validationDataLocation + "NA12892.SLX.SRP000031.2009_06.selected.bam", "fd9e37879069aa6d84436c25e472b9e9"); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey(); @@ -282,7 +282,7 @@ public class RecalibrationWalkersIntegrationTest extends WalkerTest { @Test public void testCountCovariatesNoIndex() { HashMap e = new HashMap(); - e.put( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.noindex.bam", "f34f7141351a5dbf9664c67260f94e96" ); + e.put( validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.allTechs.noindex.bam", "828d247c6e8ef5ebdf3603dc0ce79f61" ); for ( Map.Entry entry : e.entrySet() ) { String bam = entry.getKey();