From c3ea96d85616a3ce0b4b90d40e2d141b1efd16cf Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 08:42:01 -0400 Subject: [PATCH 001/363] Removing many unused functions of unquestionable purpose --- .../sting/utils/QualityUtils.java | 101 ++---------------- 1 file changed, 10 insertions(+), 91 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java b/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java index fad2320fcf..093da7dd69 100755 --- a/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java @@ -9,14 +9,17 @@ * @author Kiran Garimella */ public class QualityUtils { - public final static byte MAX_QUAL_SCORE = SAMUtils.MAX_PHRED_SCORE; public final static double MIN_REASONABLE_ERROR = 0.0001; public final static byte MAX_REASONABLE_Q_SCORE = 40; public final static byte MIN_USABLE_Q_SCORE = 6; - public final static int MAPPING_QUALITY_UNAVAILABLE = 255; + private static double qualToErrorProbCache[] = new double[256]; + static { + for (byte i = 0; i < 256; i++) qualToErrorProbCache[i] = qualToErrorProbRaw(i); + } + /** * Private constructor. No instantiating this class! */ @@ -33,10 +36,6 @@ static public double qualToProb(byte qual) { return 1.0 - qualToErrorProb(qual); } - static public double qualToProb(int qual) { - return qualToProb( (double)qual ); - } - static public double qualToProb(double qual) { return 1.0 - Math.pow(10.0, qual/(-10.0)); } @@ -48,10 +47,14 @@ static public double qualToProb(double qual) { * @param qual a quality score (0-40) * @return a probability (0.0-1.0) */ - static public double qualToErrorProb(byte qual) { + static public double qualToErrorProbRaw(byte qual) { return Math.pow(10.0, ((double) qual)/-10.0); } + static public double qualToErrorProb(byte qual) { + return qualToErrorProbCache[qual]; + } + /** * Convert a probability to a quality score. Note, this is capped at Q40. * @@ -110,88 +113,4 @@ static public byte boundQual(int qual, byte maxQual) { //return (byte) Math.min(qual, maxQual); return (byte) Math.max(Math.min(qual, maxQual), 1); } - - /** - * Compress a base and a probability into a single byte so that it can be output in a SAMRecord's SQ field. - * Note: the highest probability this function can encode is 64%, so this function should only never be used on the best base hypothesis. - * Another note: the probability encoded here gets rounded to the nearest 1%. - * - * @param baseIndex the base index - * @param prob the base probability - * @return a byte containing the index and the probability - */ - static public byte baseAndProbToCompressedQuality(int baseIndex, double prob) { - byte compressedQual = 0; - - compressedQual = (byte) baseIndex; - - byte cprob = (byte) (100.0*prob); - byte qualmask = (byte) 252; - compressedQual += ((cprob << 2) & qualmask); - - return compressedQual; - } - - /** - * From a compressed base, extract the base index (0:A, 1:C, 2:G, 3:T) - * - * @param compressedQual the compressed quality score, as returned by baseAndProbToCompressedQuality - * @return base index - */ - static public int compressedQualityToBaseIndex(byte compressedQual) { - return (int) (compressedQual & 0x3); - } - - /** - * From a compressed base, extract the base probability - * - * @param compressedQual the compressed quality score, as returned by baseAndProbToCompressedQuality - * @return the probability - */ - static public double compressedQualityToProb(byte compressedQual) { - // Because java natives are signed, extra care must be taken to avoid - // shifting a 1 into the sign bit in the implicit promotion of 2 to an int. - int x2 = ((int) compressedQual) & 0xff; - x2 = (x2 >>> 2); - - return ((double) x2)/100.0; - } - - /** - * Return the complement of a compressed quality - * - * @param compressedQual the compressed quality score (as returned by baseAndProbToCompressedQuality) - * @return the complementary compressed quality - */ - static public byte complementCompressedQuality(byte compressedQual) { - int baseIndex = compressedQualityToBaseIndex(compressedQual); - double prob = compressedQualityToProb(compressedQual); - - return baseAndProbToCompressedQuality(BaseUtils.complementIndex(baseIndex), prob); - } - - /** - * Return the reverse complement of a byte array of compressed qualities - * - * @param compressedQuals a byte array of compressed quality scores - * @return the reverse complement of the byte array - */ - static public byte[] reverseComplementCompressedQualityArray(byte[] compressedQuals) { - byte[] rcCompressedQuals = new byte[compressedQuals.length]; - - for (int pos = 0; pos < compressedQuals.length; pos++) { - rcCompressedQuals[compressedQuals.length - pos - 1] = complementCompressedQuality(compressedQuals[pos]); - } - - return rcCompressedQuals; - } - - /** - * Return the reverse of a byte array of qualities (compressed or otherwise) - * @param quals the array of bytes to be reversed - * @return the reverse of the quality array - */ - static public byte[] reverseQualityArray( byte[] quals ) { - return Utils.reverse(quals); // no sense in duplicating functionality - } } From c57198a1b998ba25b7facac526cafa04f9b8f77a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 08:46:17 -0400 Subject: [PATCH 002/363] Optimizations in VCFCodec -- Don't create an empty LinkedHashSet() for PASS fields. Just return Collections.emptySet() instead. -- For filter fields with actual values, returns an unmodifiableSet instead of one that can be changed --- .../broadinstitute/sting/utils/codecs/vcf/VCFCodec.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 fa030ef5f4..cd320b3325 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 @@ -110,11 +110,8 @@ protected Set parseFilters(String filterString) { if ( filterString.equals(VCFConstants.UNFILTERED) ) return null; - // empty set for passes filters - LinkedHashSet fFields = new LinkedHashSet(); - if ( filterString.equals(VCFConstants.PASSES_FILTERS_v4) ) - return fFields; + return Collections.emptySet(); if ( filterString.equals(VCFConstants.PASSES_FILTERS_v3) ) generateException(VCFConstants.PASSES_FILTERS_v3 + " is an invalid filter name in vcf4"); if ( filterString.length() == 0 ) @@ -124,6 +121,8 @@ protected Set parseFilters(String filterString) { if ( filterHash.containsKey(filterString) ) return filterHash.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); @@ -132,7 +131,7 @@ protected Set parseFilters(String filterString) { filterHash.put(filterString, fFields); - return fFields; + return Collections.unmodifiableSet(fFields); } From 82f213177730123def312b6a5b64878f7f665769 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 12:27:11 -0400 Subject: [PATCH 003/363] Simplied getAttributeAsX interfaces -- Removed versions getAttribriteAsX(key) that except on not having the value. -- Removed version that getAttributeAsXNoException(key) -- The only available assessors are now getAttributeAsX(key, default). -- This single accessors properly handle their argument types, so if the value is a double it is returned directly for getAttributeAsDouble(), or if it's a string it's converted to a double. If the key isn't found, default is returned. --- .../gatk/walkers/annotator/SBByDepth.java | 2 +- .../indels/HaplotypeIndelErrorModel.java | 2 +- .../gatk/walkers/phasing/PhasingRead.java | 2 +- .../walkers/phasing/RefSeqDataParser.java | 10 ++-- .../varianteval/evaluators/CountVariants.java | 6 +- .../evaluators/GenotypePhasingEvaluator.java | 3 +- .../evaluators/SimpleMetricsByAC.java | 2 +- .../evaluators/TiTvVariantEvaluator.java | 2 +- .../evaluators/ValidationReport.java | 2 +- .../stratifications/AlleleCount.java | 2 +- .../stratifications/AlleleFrequency.java | 2 +- .../stratifications/Degeneracy.java | 10 ++-- .../stratifications/FunctionalClass.java | 4 +- .../VQSRCalibrationCurve.java | 4 +- .../walkers/variantutils/SelectVariants.java | 2 +- .../walkers/variantutils/VariantsToTable.java | 2 +- .../sting/utils/codecs/vcf/VCFCodec.java | 14 +++-- .../sting/utils/variantcontext/Genotype.java | 9 --- .../InferredGeneticContext.java | 59 +++++++++++-------- .../utils/variantcontext/VariantContext.java | 10 ---- .../variantcontext/VariantContextUtils.java | 16 ++--- 21 files changed, 82 insertions(+), 83 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SBByDepth.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SBByDepth.java index 180bed24dc..d2c4d24ab8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SBByDepth.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SBByDepth.java @@ -26,7 +26,7 @@ public Map annotate(RefMetaDataTracker tracker, AnnotatorCompati if (!vc.hasAttribute(VCFConstants.STRAND_BIAS_KEY)) return null; - double sBias = Double.valueOf(vc.getAttributeAsString(VCFConstants.STRAND_BIAS_KEY)); + double sBias = vc.getAttributeAsDouble(VCFConstants.STRAND_BIAS_KEY, -1); final Map genotypes = vc.getGenotypes(); if ( genotypes == null || genotypes.size() == 0 ) 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 e68aa31e06..232e468f96 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 @@ -73,7 +73,7 @@ public class HaplotypeIndelErrorModel { baseMatchArray = new double[MAX_CACHED_QUAL+1]; baseMismatchArray = new double[MAX_CACHED_QUAL+1]; for (int k=1; k <= MAX_CACHED_QUAL; k++) { - double baseProb = QualityUtils.qualToProb(k); + double baseProb = QualityUtils.qualToProb((byte)k); baseMatchArray[k] = probToQual(baseProb); 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 a56c9e21e4..63fb332950 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 @@ -37,7 +37,7 @@ public class PhasingRead extends BaseArray { public PhasingRead(int length, int mappingQual) { super(length); - this.mappingProb = new PreciseNonNegativeDouble(QualityUtils.qualToProb(mappingQual)); + this.mappingProb = new PreciseNonNegativeDouble(QualityUtils.qualToProb((byte)mappingQual)); this.baseProbs = new PreciseNonNegativeDouble[length]; Arrays.fill(this.baseProbs, null); 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 index 55da1c1528..f941408146 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/RefSeqDataParser.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/RefSeqDataParser.java @@ -44,12 +44,12 @@ private static Map getRefSeqEntriesToNames(VariantContext vc, bo String nameKeyToUseMultiplePrefix = nameKeyToUse + "_"; Map entriesToNames = new HashMap(); - Integer numRecords = vc.getAttributeAsIntegerNoException(NUM_RECORDS_KEY); - if (numRecords != null) { + 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.getAttributeAsStringNoException(nameKeyToUse); + String name = vc.getAttributeAsString(nameKeyToUse, null); if (name != null) { entriesToNames.put(nameKeyToUse, name); done = true; @@ -59,14 +59,14 @@ private static Map getRefSeqEntriesToNames(VariantContext vc, bo if (!done) { for (int i = 1; i <= numRecords; i++) { String key = nameKeyToUseMultiplePrefix + i; - String name = vc.getAttributeAsStringNoException(key); + String name = vc.getAttributeAsString(key, null); if (name != null) entriesToNames.put(key, name); } } } else { // no entry with the # of records: - String name = vc.getAttributeAsStringNoException(nameKeyToUse); + String name = vc.getAttributeAsString(nameKeyToUse, null); if (name != null) { entriesToNames.put(nameKeyToUse, name); } 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 59ef3d9922..fd379dfda9 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 @@ -109,12 +109,12 @@ public String update1(VariantContext vc1, RefMetaDataTracker tracker, ReferenceC case SNP: nVariantLoci++; nSNPs++; - if (vc1.getAttributeAsBoolean("ISSINGLETON")) nSingletons++; + if (vc1.getAttributeAsBoolean("ISSINGLETON", false)) nSingletons++; break; case MNP: nVariantLoci++; nMNPs++; - if (vc1.getAttributeAsBoolean("ISSINGLETON")) nSingletons++; + if (vc1.getAttributeAsBoolean("ISSINGLETON", false)) nSingletons++; break; case INDEL: nVariantLoci++; @@ -136,7 +136,7 @@ else if (vc1.isSimpleDeletion()) String refStr = vc1.getReference().getBaseString().toUpperCase(); - String aaStr = vc1.hasAttribute("ANCESTRALALLELE") ? vc1.getAttributeAsString("ANCESTRALALLELE").toUpperCase() : null; + String aaStr = vc1.hasAttribute("ANCESTRALALLELE") ? vc1.getAttributeAsString("ANCESTRALALLELE", null).toUpperCase() : null; // if (aaStr.equals(".")) { // aaStr = refStr; // } 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 a476a2680a..e69dbfb28e 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 @@ -219,7 +219,8 @@ public boolean genotypesArePhasedAboveThreshold(Genotype gt) { } public static Double getPQ(Genotype gt) { - return gt.getAttributeAsDoubleNoException(ReadBackedPhasingWalker.PQ_KEY); + Double d = gt.getAttributeAsDouble(ReadBackedPhasingWalker.PQ_KEY, -1); + return d == -1 ? null : d; } public static boolean topMatchesTop(AllelePair b1, AllelePair b2) { 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 d466645ea4..38cbf1c450 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 @@ -120,7 +120,7 @@ public void incrValue( VariantContext eval ) { if ( eval.hasGenotypes() ) ac = eval.getChromosomeCount(eval.getAlternateAllele(0)); else if ( eval.hasAttribute("AC") ) { - ac = Integer.valueOf(eval.getAttributeAsString("AC")); + ac = eval.getAttributeAsInt("AC", -1); } if ( ac != -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 be957abd78..ee58012a07 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 @@ -50,7 +50,7 @@ public void updateTiTv(VariantContext vc, boolean updateStandard) { } String refStr = vc.getReference().getBaseString().toUpperCase(); - String aaStr = vc.getAttributeAsString("ANCESTRALALLELE").toUpperCase(); + String aaStr = vc.getAttributeAsString("ANCESTRALALLELE", null).toUpperCase(); if (aaStr != null && !aaStr.equalsIgnoreCase("null") && !aaStr.equals(".")) { BaseUtils.BaseSubstitutionType aaSubType = BaseUtils.SNPSubstitutionType(aaStr.getBytes()[0], vc.getAlternateAllele(0).getBases()[0]); 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 9c331b5771..7fa56785b6 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 @@ -130,7 +130,7 @@ public SiteStatus calcSiteStatus(VariantContext vc) { //// System.out.printf(" ac = %d%n", ac); } else - ac = vc.getAttributeAsInt(VCFConstants.ALLELE_COUNT_KEY); + ac = vc.getAttributeAsInt(VCFConstants.ALLELE_COUNT_KEY, 0); return ac > 0 ? SiteStatus.POLY : SiteStatus.MONO; } else if ( vc.hasGenotypes() ) { return vc.isPolymorphic() ? SiteStatus.POLY : SiteStatus.MONO; 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 5cdea4e00a..56b06d032c 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 @@ -45,7 +45,7 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac if (eval != null) { int AC = -1; if ( eval.hasAttribute("AC") && eval.getAttribute("AC") instanceof Integer ) { - AC = eval.getAttributeAsInt("AC"); + AC = eval.getAttributeAsInt("AC", 0); } else if ( eval.isVariant() ) { for (Allele allele : eval.getAlternateAlleles()) AC = Math.max(AC, eval.getChromosomeCount(allele)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleFrequency.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleFrequency.java index 96d9f30eca..ac1ee9e0ea 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleFrequency.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/AlleleFrequency.java @@ -28,7 +28,7 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac if (eval != null) { try { - relevantStates.add(String.format("%.3f", (5.0 * MathUtils.round(eval.getAttributeAsDouble("AF") / 5.0, 3)))); + relevantStates.add(String.format("%.3f", (5.0 * MathUtils.round(eval.getAttributeAsDouble("AF", 0.0) / 5.0, 3)))); } catch (Exception e) { return relevantStates; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/Degeneracy.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/Degeneracy.java index cc878e9754..06ac05ec8b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/Degeneracy.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/Degeneracy.java @@ -92,8 +92,8 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac Integer frame = null; if (eval.hasAttribute("refseq.functionalClass")) { - aa = eval.getAttributeAsString("refseq.variantAA"); - frame = eval.getAttributeAsInt("refseq.frame"); + aa = eval.getAttributeAsString("refseq.variantAA", null); + frame = eval.getAttributeAsInt("refseq.frame", 0); } else if (eval.hasAttribute("refseq.functionalClass_1")) { int annotationId = 1; String key; @@ -101,7 +101,7 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac do { key = String.format("refseq.functionalClass_%d", annotationId); - String newtype = eval.getAttributeAsString(key); + String newtype = eval.getAttributeAsString(key, null); if ( newtype != null && ( type == null || @@ -111,13 +111,13 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac type = newtype; String aakey = String.format("refseq.variantAA_%d", annotationId); - aa = eval.getAttributeAsString(aakey); + aa = eval.getAttributeAsString(aakey, null); if (aa != null) { String framekey = String.format("refseq.frame_%d", annotationId); if (eval.hasAttribute(framekey)) { - frame = eval.getAttributeAsInt(framekey); + frame = eval.getAttributeAsInt(framekey, 0); } } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/FunctionalClass.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/FunctionalClass.java index 0de871fe63..4af12fbd13 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/FunctionalClass.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/stratifications/FunctionalClass.java @@ -32,7 +32,7 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac String type = null; if (eval.hasAttribute("refseq.functionalClass")) { - type = eval.getAttributeAsString("refseq.functionalClass"); + type = eval.getAttributeAsString("refseq.functionalClass", null); } else if (eval.hasAttribute("refseq.functionalClass_1")) { int annotationId = 1; String key; @@ -40,7 +40,7 @@ public ArrayList getRelevantStates(ReferenceContext ref, RefMetaDataTrac do { key = String.format("refseq.functionalClass_%d", annotationId); - String newtype = eval.getAttributeAsString(key); + String newtype = eval.getAttributeAsString(key, null); if ( newtype != null && !newtype.equalsIgnoreCase("null") && ( type == null || diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VQSRCalibrationCurve.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VQSRCalibrationCurve.java index bc7252ec20..04ba3ff14e 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VQSRCalibrationCurve.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantrecalibration/VQSRCalibrationCurve.java @@ -115,7 +115,7 @@ public double probTrueVariant(String VQSRQualKey, VariantContext vc) { if ( vc.isFiltered() ) return 0.0; else if ( vc.hasAttribute(VQSRQualKey) ) { - double qual = vc.getAttributeAsDouble(VQSRQualKey); + double qual = vc.getAttributeAsDouble(VQSRQualKey, 0.0); return probTrueVariant(qual); } else { throw new UserException.VariantContextMissingRequiredField(VQSRQualKey, vc); @@ -143,7 +143,7 @@ public double[] includeErrorRateInLikelihoods(String VQSRQualKey, VariantContext for ( int i = 0; i < log10Likelihoods.length; i++) { double p = Math.pow(10, log10Likelihoods[i]); double q = alpha * p + (1-alpha) * noInfoPr; - if ( DEBUG ) System.out.printf(" vqslod = %.2f, p = %.2e, alpha = %.2e, q = %.2e%n", vc.getAttributeAsDouble(VQSRQualKey), p, alpha, q); + if ( DEBUG ) System.out.printf(" vqslod = %.2f, p = %.2e, alpha = %.2e, q = %.2e%n", vc.getAttributeAsDouble(VQSRQualKey, 0.0), p, alpha, q); updated[i] = Math.log10(q); } 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 bb3cd82a1e..ceafb0cf5c 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 @@ -575,7 +575,7 @@ else if (!SELECT_RANDOM_FRACTION || (!KEEP_AF_SPECTRUM && GenomeAnalysisEngine.g // ok we have a comp VC and we need to match the AF spectrum of inputAFRodName. // We then pick a variant with probablity AF*desiredFraction if ( sub.hasAttribute(VCFConstants.ALLELE_FREQUENCY_KEY) ) { - String afo = sub.getAttributeAsString(VCFConstants.ALLELE_FREQUENCY_KEY); + String afo = sub.getAttributeAsString(VCFConstants.ALLELE_FREQUENCY_KEY, null); double af; double afBoost = 1.0; 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 2a877fb093..aafbe4db4e 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 @@ -192,7 +192,7 @@ private static List extractFields(VariantContext vc, List fields if ( getters.containsKey(field) ) { val = getters.get(field).get(vc); } else if ( vc.hasAttribute(field) ) { - val = vc.getAttributeAsString(field); + val = vc.getAttributeAsString(field, null); } else if ( isWildCard(field) ) { Set wildVals = new HashSet(); for ( Map.Entry elt : vc.getAttributes().entrySet()) { 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 cd320b3325..94e40fc98e 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 @@ -105,7 +105,10 @@ else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { * @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; @@ -113,13 +116,13 @@ protected Set parseFilters(String filterString) { 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"); + 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"); + generateException("The VCF specification requires a valid filter status", lineNo); // do we have the filter string cached? - if ( filterHash.containsKey(filterString) ) - return filterHash.get(filterString); + if ( cache != null && cache.containsKey(filterString) ) + return Collections.unmodifiableSet(cache.get(filterString)); // empty set for passes filters LinkedHashSet fFields = new LinkedHashSet(); @@ -129,7 +132,8 @@ protected Set parseFilters(String filterString) { else fFields.addAll(Arrays.asList(filterString.split(VCFConstants.FILTER_CODE_SEPARATOR))); - filterHash.put(filterString, fFields); + fFields = fFields; + if ( cache != null ) cache.put(filterString, fFields); return Collections.unmodifiableSet(fFields); } 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 fdf3d97db8..85d7520035 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -293,17 +293,8 @@ public Object getAttribute(String key, Object defaultValue) { return commonInfo.getAttribute(key, defaultValue); } - public String getAttributeAsString(String key) { return commonInfo.getAttributeAsString(key); } public String getAttributeAsString(String key, String defaultValue) { return commonInfo.getAttributeAsString(key, defaultValue); } - public int getAttributeAsInt(String key) { return commonInfo.getAttributeAsInt(key); } public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } - public double getAttributeAsDouble(String key) { return commonInfo.getAttributeAsDouble(key); } public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } - public boolean getAttributeAsBoolean(String key) { return commonInfo.getAttributeAsBoolean(key); } public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } - - public Integer getAttributeAsIntegerNoException(String key) { return commonInfo.getAttributeAsIntegerNoException(key); } - public Double getAttributeAsDoubleNoException(String key) { return commonInfo.getAttributeAsDoubleNoException(key); } - public String getAttributeAsStringNoException(String key) { return commonInfo.getAttributeAsStringNoException(key); } - public Boolean getAttributeAsBooleanNoException(String key) { return commonInfo.getAttributeAsBooleanNoException(key); } } \ No newline at end of file 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 3d162adb0e..4266fb4b5e 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java @@ -204,27 +204,40 @@ public Object getAttribute(String key, Object defaultValue) { return defaultValue; } -// public AttributedObject getAttributes(Collection keys) { -// AttributedObject selected = new AttributedObject(); -// -// for ( Object key : keys ) -// selected.putAttribute(key, this.getAttribute(key)); -// -// return selected; -// } - - 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 String getAttributeAsString(String key, String defaultValue) { return (String)getAttribute(key, defaultValue); } - public int getAttributeAsInt(String key, int defaultValue) { return (Integer)getAttribute(key, defaultValue); } - public double getAttributeAsDouble(String key, double defaultValue) { return (Double)getAttribute(key, defaultValue); } - public boolean getAttributeAsBoolean(String key, boolean defaultValue){ return (Boolean)getAttribute(key, defaultValue); } - - 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;} } + 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 ) 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/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java index 673fe45295..e6637a5d96 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -666,21 +666,11 @@ public Object getAttribute(String key, Object defaultValue) { return commonInfo.getAttribute(key, defaultValue); } - public String getAttributeAsString(String key) { return commonInfo.getAttributeAsString(key); } public String getAttributeAsString(String key, String defaultValue) { return commonInfo.getAttributeAsString(key, defaultValue); } - public int getAttributeAsInt(String key) { return commonInfo.getAttributeAsInt(key); } public int getAttributeAsInt(String key, int defaultValue) { return commonInfo.getAttributeAsInt(key, defaultValue); } - public double getAttributeAsDouble(String key) { return commonInfo.getAttributeAsDouble(key); } public double getAttributeAsDouble(String key, double defaultValue) { return commonInfo.getAttributeAsDouble(key, defaultValue); } - public boolean getAttributeAsBoolean(String key) { return commonInfo.getAttributeAsBoolean(key); } public boolean getAttributeAsBoolean(String key, boolean defaultValue) { return commonInfo.getAttributeAsBoolean(key, defaultValue); } - public Integer getAttributeAsIntegerNoException(String key) { return commonInfo.getAttributeAsIntegerNoException(key); } - public Double getAttributeAsDoubleNoException(String key) { return commonInfo.getAttributeAsDoubleNoException(key); } - public String getAttributeAsStringNoException(String key) { return commonInfo.getAttributeAsStringNoException(key); } - public Boolean getAttributeAsBooleanNoException(String key) { return commonInfo.getAttributeAsBooleanNoException(key); } - - // --------------------------------------------------------------------------------------------------------- // // Working with alleles 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 986d6305c4..d5c541b197 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -565,11 +565,11 @@ public static VariantContext simpleMerge(GenomeLocParser genomeLocParser, Collec // special case DP (add it up) and ID (just preserve it) // if (vc.hasAttribute(VCFConstants.DEPTH_KEY)) - depth += Integer.valueOf(vc.getAttributeAsString(VCFConstants.DEPTH_KEY)); + depth += vc.getAttributeAsInt(VCFConstants.DEPTH_KEY, 0); if (rsID == null && vc.hasID()) rsID = vc.getID(); if (mergeInfoWithMaxAC && vc.hasAttribute(VCFConstants.ALLELE_COUNT_KEY)) { - String rawAlleleCounts = vc.getAttributeAsString(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)); @@ -1147,9 +1147,7 @@ private static Map mergeVariantContextAttributes(VariantContext for (String orAttrib : MERGE_OR_ATTRIBS) { boolean attribVal = false; for (VariantContext vc : vcList) { - Boolean val = vc.getAttributeAsBooleanNoException(orAttrib); - if (val != null) - attribVal = (attribVal || val); + attribVal = vc.getAttributeAsBoolean(orAttrib, false); if (attribVal) // already true, so no reason to continue: break; } @@ -1159,7 +1157,7 @@ private static Map mergeVariantContextAttributes(VariantContext // Merge ID fields: String iDVal = null; for (VariantContext vc : vcList) { - String val = vc.getAttributeAsStringNoException(VariantContext.ID_KEY); + String val = vc.getAttributeAsString(VariantContext.ID_KEY, null); if (val != null && !val.equals(VCFConstants.EMPTY_ID_FIELD)) { if (iDVal == null) iDVal = val; @@ -1239,8 +1237,10 @@ private static class PhaseAndQuality { public PhaseAndQuality(Genotype gt) { this.isPhased = gt.isPhased(); - if (this.isPhased) - this.PQ = gt.getAttributeAsDoubleNoException(ReadBackedPhasingWalker.PQ_KEY); + if (this.isPhased) { + this.PQ = gt.getAttributeAsDouble(ReadBackedPhasingWalker.PQ_KEY, -1); + if ( this.PQ == -1 ) this.PQ = null; + } } } From 124ef6c4834d8a948629291762b7f9d7d9696d50 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 21:12:28 -0400 Subject: [PATCH 004/363] MISSING_VALUE now gets defaultValue in getAttribute functions --- .../sting/utils/variantcontext/InferredGeneticContext.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 4266fb4b5e..bf16cd1cf4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/InferredGeneticContext.java @@ -1,6 +1,8 @@ package org.broadinstitute.sting.utils.variantcontext; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; + import java.util.*; @@ -213,7 +215,7 @@ public String getAttributeAsString(String key, String defaultValue) { public int getAttributeAsInt(String key, int defaultValue) { Object x = getAttribute(key); - if ( x == null ) return defaultValue; + 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 } From 03aa04e37c8a2cf31d650cba6a8c703fbb033978 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 21:13:08 -0400 Subject: [PATCH 005/363] Simple refactoring to make formating functions public --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 2 +- .../sting/utils/codecs/vcf/StandardVCFWriter.java | 11 +++++++++-- .../sting/utils/codecs/vcf/VCFCodec.java | 2 +- 3 files changed, 11 insertions(+), 4 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 bb212e1285..624d06a710 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 @@ -227,7 +227,7 @@ protected void generateException(String message) { throw new UserException.MalformedVCF(message, lineNo); } - private static void generateException(String message, int lineNo) { + protected static void generateException(String message, int lineNo) { throw new UserException.MalformedVCF(message, lineNo); } 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 d3705813ce..e28cd7598e 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 @@ -275,7 +275,7 @@ public void add(VariantContext vc, boolean refBaseShouldBeAppliedToEndOfAlleles) mWriter.write(VCFConstants.FIELD_SEPARATOR); // FILTER - String filters = vc.isFiltered() ? ParsingUtils.join(";", ParsingUtils.sortList(vc.getFilters())) : (filtersWereAppliedToContext || vc.filtersWereApplied() ? VCFConstants.PASSES_FILTERS_v4 : VCFConstants.UNFILTERED); + String filters = getFilterString(vc, filtersWereAppliedToContext); mWriter.write(filters); mWriter.write(VCFConstants.FIELD_SEPARATOR); @@ -319,7 +319,14 @@ public void add(VariantContext vc, boolean refBaseShouldBeAppliedToEndOfAlleles) } catch (IOException e) { throw new RuntimeException("Unable to write the VCF object to " + locationString()); } + } + + public static final String getFilterString(final VariantContext vc) { + return getFilterString(vc, false); + } + public static final String getFilterString(final VariantContext vc, boolean forcePASS) { + return vc.isFiltered() ? ParsingUtils.join(";", ParsingUtils.sortList(vc.getFilters())) : (forcePASS || vc.filtersWereApplied() ? VCFConstants.PASSES_FILTERS_v4 : VCFConstants.UNFILTERED); } private String getQualValue(double qual) { @@ -462,7 +469,7 @@ private void writeAllele(Allele allele, Map alleleMap) throws IO mWriter.write(encoding); } - private static String formatVCFField(Object val) { + public static String formatVCFField(Object val) { String result; if ( val == null ) result = VCFConstants.MISSING_VALUE_v4; 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 94e40fc98e..42ea05355b 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 @@ -118,7 +118,7 @@ public static Set parseFilters(final Map> 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", lineNo); + 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) ) From 048202d18e42444e47e6237246b31d24c57dec9a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 21:13:28 -0400 Subject: [PATCH 006/363] Bugfix for cached quals --- .../java/src/org/broadinstitute/sting/utils/QualityUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java b/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java index 093da7dd69..19e03a19d6 100755 --- a/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/QualityUtils.java @@ -17,7 +17,7 @@ public class QualityUtils { private static double qualToErrorProbCache[] = new double[256]; static { - for (byte i = 0; i < 256; i++) qualToErrorProbCache[i] = qualToErrorProbRaw(i); + for (int i = 0; i < 256; i++) qualToErrorProbCache[i] = qualToErrorProbRaw((byte)i); } /** From d471617c65f6d1f3885ea6628b7676ed6bbc6f8d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 2 Sep 2011 21:15:19 -0400 Subject: [PATCH 007/363] GATK binary VCF (gvcf) prototype format for efficiency testing -- Very minimal working version that can read / write binary VCFs with genotypes -- Already 10x faster for sites, 5x for fully parsed genotypes, and 1000x for skipping genotypes when reading --- .../broadinstitute/sting/utils/gvcf/GVCF.java | 252 ++++++++++++++++++ .../sting/utils/gvcf/GVCFGenotype.java | 147 ++++++++++ .../sting/utils/gvcf/GVCFHeader.java | 180 +++++++++++++ .../sting/utils/gvcf/GVCFHeaderBuilder.java | 80 ++++++ 4 files changed, 659 insertions(+) create mode 100644 public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java new file mode 100644 index 0000000000..8568c1aab1 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java @@ -0,0 +1,252 @@ +/* + * 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.gvcf; + +import org.broadinstitute.sting.utils.QualityUtils; +import org.broadinstitute.sting.utils.Utils; +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.VariantContext; + +import java.io.*; +import java.util.*; + +/** + * GATK binary VCF record + * + * @author Your Name + * @since Date created + */ +public class GVCF { + private final static int RECORD_TERMINATOR = 123456789; + private int chromOffset; + private int start, stop; + private String id; + private List alleleMap; + private int alleleOffsets[]; + private float qual; + private byte refPad; + private String info; + private int filterOffset; + + private List genotypes = Collections.emptyList(); + + public GVCF(final GVCFHeaderBuilder gvcfHeaderBuilder, final VariantContext vc, boolean skipGenotypes) { + chromOffset = gvcfHeaderBuilder.encodeString(vc.getChr()); + start = vc.getStart(); + stop = vc.getEnd(); + refPad = vc.hasReferenceBaseForIndel() ? vc.getReferenceBaseForIndel() : 0; + id = vc.getID(); + + // encode alleles + alleleMap = new ArrayList(vc.getNAlleles()); + alleleOffsets = new int[vc.getNAlleles()]; + alleleMap.add(vc.getReference()); + alleleOffsets[0] = gvcfHeaderBuilder.encodeAllele(vc.getReference()); + for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { + alleleMap.add(vc.getAlternateAllele(i)); + alleleOffsets[i+1] = gvcfHeaderBuilder.encodeAllele(vc.getAlternateAllele(i)); + } + + qual = (float)vc.getNegLog10PError(); //qualToByte(vc.getPhredScaledQual()); + info = infoFieldString(vc, gvcfHeaderBuilder); + filterOffset = gvcfHeaderBuilder.encodeString(StandardVCFWriter.getFilterString(vc)); + + if ( ! skipGenotypes ) { + genotypes = encodeGenotypes(gvcfHeaderBuilder, vc); + } + } + + public GVCF(DataInputStream inputStream, boolean skipGenotypes) throws IOException { + chromOffset = inputStream.readInt(); + start = inputStream.readInt(); + stop = inputStream.readInt(); + id = inputStream.readUTF(); + refPad = inputStream.readByte(); + alleleOffsets = readIntArray(inputStream); + qual = inputStream.readFloat(); + info = inputStream.readUTF(); + filterOffset = inputStream.readInt(); + + int nGenotypes = inputStream.readInt(); + int sizeOfGenotypes = inputStream.readInt(); + if ( skipGenotypes ) { + genotypes = Collections.emptyList(); + inputStream.skipBytes(sizeOfGenotypes); + } else { + genotypes = new ArrayList(nGenotypes); + for ( int i = 0; i < nGenotypes; i++ ) + genotypes.add(new GVCFGenotype(this, inputStream)); + } + + int recordDone = inputStream.readInt(); + if ( recordDone != RECORD_TERMINATOR ) + throw new UserException.MalformedFile("Record not terminated by RECORD_TERMINATOR key"); + } + + public VariantContext decode(final String source, final GVCFHeader 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; + Map genotypes = decodeGenotypes(header); + + return new VariantContext(source, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); + } + + private Map decodeGenotypes(final GVCFHeader header) { + if ( genotypes.isEmpty() ) + return VariantContext.NO_GENOTYPES; + else { + Map map = new TreeMap(); + + 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); + } + + return map; + } + } + + private List encodeGenotypes(final GVCFHeaderBuilder gvcfHeaderBuilder, final VariantContext vc) { + int nGenotypes = vc.getNSamples(); + if ( nGenotypes > 0 ) { + List genotypes = new ArrayList(nGenotypes); + for ( int i = 0; i < nGenotypes; i++ ) genotypes.add(null); + + for ( Genotype g : vc.getGenotypes().values() ) { + int i = gvcfHeaderBuilder.encodeSample(g.getSampleName()); + genotypes.set(i, new GVCFGenotype(gvcfHeaderBuilder, alleleMap, g)); + } + + return genotypes; + } else { + return Collections.emptyList(); + } + } + + public int getNAlleles() { return alleleOffsets.length; } + + public int write(DataOutputStream outputStream) throws IOException { + int startSize = outputStream.size(); + outputStream.writeInt(chromOffset); + outputStream.writeInt(start); + outputStream.writeInt(stop); + outputStream.writeUTF(id); + outputStream.writeByte(refPad); + writeIntArray(alleleOffsets, outputStream, true); + outputStream.writeFloat(qual); + outputStream.writeUTF(info); + outputStream.writeInt(filterOffset); + + int nGenotypes = genotypes.size(); + int expectedSizeOfGenotypes = nGenotypes == 0 ? 0 : genotypes.get(0).sizeInBytes() * nGenotypes; + outputStream.writeInt(nGenotypes); + outputStream.writeInt(expectedSizeOfGenotypes); + int obsSizeOfGenotypes = 0; + for ( GVCFGenotype g : genotypes ) + obsSizeOfGenotypes += g.write(outputStream); + if ( obsSizeOfGenotypes != expectedSizeOfGenotypes ) + throw new RuntimeException("Expect and observed genotype sizes disagree! expect = " + expectedSizeOfGenotypes + " obs =" + obsSizeOfGenotypes); + + outputStream.writeInt(RECORD_TERMINATOR); + return outputStream.size() - startSize; + } + + private final String infoFieldString(VariantContext vc, final GVCFHeaderBuilder gvcfHeaderBuilder) { + StringBuilder s = new StringBuilder(); + + 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) ) + continue; + int stringIndex = gvcfHeaderBuilder.encodeString(key); + String outputValue = StandardVCFWriter.formatVCFField(field.getValue()); + if ( outputValue != null ) { + if ( ! first ) s.append(";"); + s.append(stringIndex).append("=").append(outputValue); + first = false; + } + } + + return s.toString(); + } + + private final static int BUFFER_SIZE = 1048576; // 2**20 + public static DataOutputStream createOutputStream(final File file) throws FileNotFoundException { + return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE)); + } + + public static DataInputStream createInputStream(final File file) throws FileNotFoundException { + return new DataInputStream(new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE)); + } + + protected final static int[] readIntArray(final DataInputStream inputStream) throws IOException { + return readIntArray(inputStream, inputStream.readInt()); + } + + protected final static int[] readIntArray(final DataInputStream inputStream, int size) throws IOException { + int[] array = new int[size]; + for ( int i = 0; i < array.length; i++ ) + array[i] = inputStream.readInt(); + return array; + } + + protected final static void writeIntArray(int[] array, final DataOutputStream outputStream, boolean writeSize) throws IOException { + if ( writeSize ) outputStream.writeInt(array.length); + for ( int i : array ) + outputStream.writeInt(i); + } + + protected final static byte[] readByteArray(final DataInputStream inputStream) throws IOException { + return readByteArray(inputStream, inputStream.readInt()); + } + + protected final static byte[] readByteArray(final DataInputStream inputStream, int size) throws IOException { + byte[] array = new byte[size]; + for ( int i = 0; i < array.length; i++ ) + array[i] = inputStream.readByte(); + return array; + } + + protected final static void writeByteArray(byte[] array, final DataOutputStream outputStream, boolean writeSize) throws IOException { + if ( writeSize ) outputStream.writeInt(array.length); + for ( byte i : array ) + outputStream.writeByte(i); + } + + protected final static byte qualToByte(double phredScaledQual) { + return (byte)Math.round(Math.min(phredScaledQual, 255)); + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.java b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.java new file mode 100644 index 0000000000..2ef6d9b3a7 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.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.utils.gvcf; + +import org.broadinstitute.sting.utils.variantcontext.Allele; +import org.broadinstitute.sting.utils.variantcontext.Genotype; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; + +/** + * GATK binary VCF record + * + * @author Your Name + * @since Date created + */ +public class GVCFGenotype { + private byte gq; + private int gt; + private int dp; + private int ad[]; + private byte[] pl; + + // todo -- what to do about phasing? Perhaps we shouldn't support it + // todo -- is the FL field generic or just a flag? Should we even support per sample filtering? + + public GVCFGenotype(final GVCFHeaderBuilder gvcfHeaderBuilder, final List allAlleles, Genotype genotype) { + gq = GVCF.qualToByte(genotype.getPhredScaledQual()); + gt = encodeAlleles(genotype.getAlleles(), allAlleles); + + dp = genotype.getAttributeAsInt("DP", 0); + + int nAlleles = allAlleles.size(); + ad = new int[nAlleles]; + + int npls = nAllelesToNPls(nAlleles); + pl = new byte[npls]; + } + + private int nAllelesToNPls( int nAlleles ) { + return nAlleles*(nAlleles+1) / 2; + } + + public GVCFGenotype(GVCF gvcf, DataInputStream inputStream) throws IOException { + int gqInt = inputStream.readUnsignedByte(); + gq = (byte)gqInt; + gt = inputStream.readInt(); + dp = inputStream.readInt(); + ad = GVCF.readIntArray(inputStream, gvcf.getNAlleles()); + pl = GVCF.readByteArray(inputStream, nAllelesToNPls(gvcf.getNAlleles())); + } + + // 2 alleles => 1 + 8 + 8 + 3 => 20 + protected int sizeInBytes() { + return 1 // gq + + 4 * 2 // gt + dp + + 4 * ad.length // ad + + 1 * pl.length; // pl + } + + public Genotype decode(final String sampleName, final GVCFHeader header, GVCF gvcf, List alleleIndex) { + final List alleles = decodeAlleles(gt, alleleIndex); + final double negLog10PError = 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); + } + + private static int encodeAlleles(List gtList, List allAlleles) { + final int nAlleles = gtList.size(); + if ( nAlleles > 4 ) + throw new IllegalArgumentException("encodeAlleles doesn't support more than 4 alt alleles, but I saw " + gtList); + + int gtInt = 0; + for ( int i = 0; i < nAlleles ; i++ ) { + final int bitOffset = i * 8; + final int allelei = getAlleleIndex(gtList.get(i), allAlleles); + final int gti = (allelei + 1) << bitOffset; + gtInt = gtInt | gti; + } + + return gtInt; + } + + private static int getAlleleIndex(Allele q, List allAlleles) { + if ( q.isNoCall() ) + return 254; + for ( int i = 0; i < allAlleles.size(); i++ ) + if ( q.equals(allAlleles.get(i)) ) + return i; + throw new IllegalStateException("getAlleleIndex passed allele not in map! allele " + q + " allAlleles " + allAlleles); + } + + private static List decodeAlleles(int gtInt, List alleleIndex) { + List alleles = new ArrayList(4); + + for ( int i = 0; i < 32; i += 8 ) { + final int gi = (gtInt & (0x000000FF << i)) >> i; + if ( gi != 0 ) { + final int allelei = gi - 1; + alleles.add( allelei == 254 ? Allele.NO_CALL : alleleIndex.get(allelei) ); + } else { + break; + } + } + + return alleles; + } + + public int write(DataOutputStream outputStream) throws IOException { + int startSize = outputStream.size(); + outputStream.writeByte(gq); + outputStream.writeInt(gt); + outputStream.writeInt(dp); + GVCF.writeIntArray(ad, outputStream, false); + GVCF.writeByteArray(pl, outputStream, false); + return outputStream.size() - startSize; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java new file mode 100644 index 0000000000..c52c975bde --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java @@ -0,0 +1,180 @@ +/* + * 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.gvcf; + +import org.apache.log4j.Logger; +import org.broadinstitute.sting.utils.QualityUtils; +import org.broadinstitute.sting.utils.codecs.vcf.AbstractVCFCodec; +import org.broadinstitute.sting.utils.codecs.vcf.VCFCodec; +import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.variantcontext.Allele; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +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 + */ +public class GVCFHeader { + final protected static Logger logger = Logger.getLogger(GVCFHeader.class); + + private static byte[] MAGIC_HEADER = "GVCF0.1\1".getBytes(); + final List alleles; + final List strings; + final List samples; + final List> filters; + + public GVCFHeader(final Map allelesIn, final Map stringIn, final Map samplesIn) { + this.alleles = linearize(allelesIn); + this.strings = linearize(stringIn); + this.samples = linearize(samplesIn); + this.filters = null; // not used with this constructor + } + + public GVCFHeader(DataInputStream inputStream) throws IOException { + byte[] headerTest = new byte[MAGIC_HEADER.length]; + inputStream.read(headerTest); + if ( ! Arrays.equals(headerTest, MAGIC_HEADER) ) { + throw new UserException("Could not read GVCF file. MAGIC_HEADER missing. Saw " + headerTest); + } else { + alleles = stringsToAlleles(readStrings(inputStream)); + strings = readStrings(inputStream); + samples = readStrings(inputStream); + logger.info(String.format("Allele map of %d elements", alleles.size())); + logger.info(String.format("String map of %d elements", strings.size())); + logger.info(String.format("Sample map of %d elements", samples.size())); + filters = initializeFilterCache(); + } + } + + public int write(final DataOutputStream outputStream) throws IOException { + int startBytes = outputStream.size(); + outputStream.write(MAGIC_HEADER); + write(outputStream, allelesToStrings(alleles)); + write(outputStream, strings); + write(outputStream, samples); + return outputStream.size() - startBytes; + } + + public void write(DataOutputStream outputStream, List l) throws IOException { + outputStream.writeInt(l.size()); + for ( String elt : l ) outputStream.writeUTF(elt); + } + + private List allelesToStrings(List alleles) { + List strings = new ArrayList(alleles.size()); + for ( Allele allele : alleles ) strings.add(allele.toString()); + return strings; + } + + private List> initializeFilterCache() { + // required to allow offset -> set lookup + List> l = new ArrayList>(strings.size()); + for ( int i = 0; i < strings.size(); i++ ) l.add(null); + return l; + } + + private static List stringsToAlleles(final List strings) { + final List alleles = new ArrayList(strings.size()); + for ( String string : strings ) { + boolean isRef = string.endsWith("*"); + if ( isRef ) string = string.substring(0, string.length() - 1); + alleles.add(Allele.create(string, isRef)); + } + return alleles; + } + + private static List readStrings(final DataInputStream inputStream) throws IOException { + final int nStrings = inputStream.readInt(); + + final List strings = new ArrayList(nStrings); + for ( int i = 0; i < nStrings; i++ ) { + strings.add(inputStream.readUTF()); + } + + return strings; + } + + private static List linearize(final Map map) { + final ArrayList l = new ArrayList(map.size()); + for ( int i = 0; i < map.size(); i++ ) l.add(null); + for ( final Map.Entry elt : map.entrySet() ) + l.set(elt.getValue(), elt.getKey()); + return l; + } + + public String getSample(final int offset) { return samples.get(offset); } + public String getString(final int offset) { return strings.get(offset); } + public Allele getAllele(final int offset) { return alleles.get(offset); } + public List getAlleles(final int[] offsets) { + final List alleles = new ArrayList(offsets.length); + for ( int i : offsets ) alleles.add(getAllele(i)); + return alleles; + } + + public Set getFilters(final int offset) { + Set cached = filters.get(offset); + + if ( cached != null ) + return cached; + else { + final String filterString = getString(offset); + if ( filterString.equals(VCFConstants.UNFILTERED) ) + return null; // UNFILTERED records are represented by null + else { + Set set = VCFCodec.parseFilters(null, -1, filterString); + filters.set(offset, set); // remember the result + return set; + } + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java new file mode 100644 index 0000000000..2d045b8ead --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java @@ -0,0 +1,80 @@ +/* + * 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.gvcf; + +import org.broadinstitute.sting.utils.variantcontext.Allele; + +import java.util.HashMap; +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 GVCFHeaderBuilder { + Map alleles = new HashMap(); + Map strings = new HashMap(); + Map samples = new HashMap(); + + public GVCFHeader createHeader() { + return new GVCFHeader(alleles, strings, samples); + } + + public int encodeString(final String chr) { return encode(strings, chr); } + public int encodeAllele(final Allele allele) { return encode(alleles, allele); } + public int encodeSample(final String sampleName) { return encode(samples, sampleName); } + + private int encode(Map map, T key) { + Integer v = map.get(key); + if ( v == null ) { + v = map.size(); + map.put(key, v); + } + return v; + } +} From 01b6177ce15c2d4270ac863da1a2e4e43e020411 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Sep 2011 17:10:56 -0400 Subject: [PATCH 008/363] Renaming GVCF -> GCF --- .../utils/{gvcf/GVCF.java => gcf/GCF.java} | 47 +++++++++---------- .../GCFGenotype.java} | 20 ++++---- .../GVCFHeader.java => gcf/GCFHeader.java} | 12 ++--- .../GCFHeaderBuilder.java} | 8 ++-- 4 files changed, 41 insertions(+), 46 deletions(-) rename public/java/src/org/broadinstitute/sting/utils/{gvcf/GVCF.java => gcf/GCF.java} (83%) rename public/java/src/org/broadinstitute/sting/utils/{gvcf/GVCFGenotype.java => gcf/GCFGenotype.java} (86%) rename public/java/src/org/broadinstitute/sting/utils/{gvcf/GVCFHeader.java => gcf/GCFHeader.java} (92%) rename public/java/src/org/broadinstitute/sting/utils/{gvcf/GVCFHeaderBuilder.java => gcf/GCFHeaderBuilder.java} (93%) diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java similarity index 83% rename from public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java rename to public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java index 8568c1aab1..5ab241ebf2 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -22,12 +22,9 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.gvcf; +package org.broadinstitute.sting.utils.gcf; -import org.broadinstitute.sting.utils.QualityUtils; -import org.broadinstitute.sting.utils.Utils; 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; @@ -42,7 +39,7 @@ * @author Your Name * @since Date created */ -public class GVCF { +public class GCF { private final static int RECORD_TERMINATOR = 123456789; private int chromOffset; private int start, stop; @@ -54,10 +51,10 @@ public class GVCF { private String info; private int filterOffset; - private List genotypes = Collections.emptyList(); + private List genotypes = Collections.emptyList(); - public GVCF(final GVCFHeaderBuilder gvcfHeaderBuilder, final VariantContext vc, boolean skipGenotypes) { - chromOffset = gvcfHeaderBuilder.encodeString(vc.getChr()); + public GCF(final GCFHeaderBuilder GCFHeaderBuilder, final VariantContext vc, boolean skipGenotypes) { + chromOffset = GCFHeaderBuilder.encodeString(vc.getChr()); start = vc.getStart(); stop = vc.getEnd(); refPad = vc.hasReferenceBaseForIndel() ? vc.getReferenceBaseForIndel() : 0; @@ -67,22 +64,22 @@ public GVCF(final GVCFHeaderBuilder gvcfHeaderBuilder, final VariantContext vc, alleleMap = new ArrayList(vc.getNAlleles()); alleleOffsets = new int[vc.getNAlleles()]; alleleMap.add(vc.getReference()); - alleleOffsets[0] = gvcfHeaderBuilder.encodeAllele(vc.getReference()); + alleleOffsets[0] = GCFHeaderBuilder.encodeAllele(vc.getReference()); for ( int i = 0; i < vc.getAlternateAlleles().size(); i++ ) { alleleMap.add(vc.getAlternateAllele(i)); - alleleOffsets[i+1] = gvcfHeaderBuilder.encodeAllele(vc.getAlternateAllele(i)); + alleleOffsets[i+1] = GCFHeaderBuilder.encodeAllele(vc.getAlternateAllele(i)); } qual = (float)vc.getNegLog10PError(); //qualToByte(vc.getPhredScaledQual()); - info = infoFieldString(vc, gvcfHeaderBuilder); - filterOffset = gvcfHeaderBuilder.encodeString(StandardVCFWriter.getFilterString(vc)); + info = infoFieldString(vc, GCFHeaderBuilder); + filterOffset = GCFHeaderBuilder.encodeString(StandardVCFWriter.getFilterString(vc)); if ( ! skipGenotypes ) { - genotypes = encodeGenotypes(gvcfHeaderBuilder, vc); + genotypes = encodeGenotypes(GCFHeaderBuilder, vc); } } - public GVCF(DataInputStream inputStream, boolean skipGenotypes) throws IOException { + public GCF(DataInputStream inputStream, boolean skipGenotypes) throws IOException { chromOffset = inputStream.readInt(); start = inputStream.readInt(); stop = inputStream.readInt(); @@ -99,9 +96,9 @@ public GVCF(DataInputStream inputStream, boolean skipGenotypes) throws IOExcepti genotypes = Collections.emptyList(); inputStream.skipBytes(sizeOfGenotypes); } else { - genotypes = new ArrayList(nGenotypes); + genotypes = new ArrayList(nGenotypes); for ( int i = 0; i < nGenotypes; i++ ) - genotypes.add(new GVCFGenotype(this, inputStream)); + genotypes.add(new GCFGenotype(this, inputStream)); } int recordDone = inputStream.readInt(); @@ -109,7 +106,7 @@ public GVCF(DataInputStream inputStream, boolean skipGenotypes) throws IOExcepti throw new UserException.MalformedFile("Record not terminated by RECORD_TERMINATOR key"); } - public VariantContext decode(final String source, final GVCFHeader header) { + 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); @@ -122,7 +119,7 @@ public VariantContext decode(final String source, final GVCFHeader header) { return new VariantContext(source, contig, start, stop, alleleMap, genotypes, negLog10PError, filters, attributes, refPadByte); } - private Map decodeGenotypes(final GVCFHeader header) { + private Map decodeGenotypes(final GCFHeader header) { if ( genotypes.isEmpty() ) return VariantContext.NO_GENOTYPES; else { @@ -138,15 +135,15 @@ private Map decodeGenotypes(final GVCFHeader header) { } } - private List encodeGenotypes(final GVCFHeaderBuilder gvcfHeaderBuilder, final VariantContext vc) { + private List encodeGenotypes(final GCFHeaderBuilder GCFHeaderBuilder, final VariantContext vc) { int nGenotypes = vc.getNSamples(); if ( nGenotypes > 0 ) { - List genotypes = new ArrayList(nGenotypes); + List genotypes = new ArrayList(nGenotypes); for ( int i = 0; i < nGenotypes; i++ ) genotypes.add(null); for ( Genotype g : vc.getGenotypes().values() ) { - int i = gvcfHeaderBuilder.encodeSample(g.getSampleName()); - genotypes.set(i, new GVCFGenotype(gvcfHeaderBuilder, alleleMap, g)); + int i = GCFHeaderBuilder.encodeSample(g.getSampleName()); + genotypes.set(i, new GCFGenotype(GCFHeaderBuilder, alleleMap, g)); } return genotypes; @@ -174,7 +171,7 @@ public int write(DataOutputStream outputStream) throws IOException { outputStream.writeInt(nGenotypes); outputStream.writeInt(expectedSizeOfGenotypes); int obsSizeOfGenotypes = 0; - for ( GVCFGenotype g : genotypes ) + for ( GCFGenotype g : genotypes ) obsSizeOfGenotypes += g.write(outputStream); if ( obsSizeOfGenotypes != expectedSizeOfGenotypes ) throw new RuntimeException("Expect and observed genotype sizes disagree! expect = " + expectedSizeOfGenotypes + " obs =" + obsSizeOfGenotypes); @@ -183,7 +180,7 @@ public int write(DataOutputStream outputStream) throws IOException { return outputStream.size() - startSize; } - private final String infoFieldString(VariantContext vc, final GVCFHeaderBuilder gvcfHeaderBuilder) { + private final String infoFieldString(VariantContext vc, final GCFHeaderBuilder GCFHeaderBuilder) { StringBuilder s = new StringBuilder(); boolean first = true; @@ -191,7 +188,7 @@ private final String infoFieldString(VariantContext vc, final GVCFHeaderBuilder String key = field.getKey(); if ( key.equals(VariantContext.ID_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_MAP_KEY) || key.equals(VariantContext.UNPARSED_GENOTYPE_PARSER_KEY) ) continue; - int stringIndex = gvcfHeaderBuilder.encodeString(key); + int stringIndex = GCFHeaderBuilder.encodeString(key); String outputValue = StandardVCFWriter.formatVCFField(field.getValue()); if ( outputValue != null ) { if ( ! first ) s.append(";"); diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java similarity index 86% rename from public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.java rename to public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java index 2ef6d9b3a7..dd1fb091cb 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFGenotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFGenotype.java @@ -22,7 +22,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.gvcf; +package org.broadinstitute.sting.utils.gcf; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; @@ -38,7 +38,7 @@ * @author Your Name * @since Date created */ -public class GVCFGenotype { +public class GCFGenotype { private byte gq; private int gt; private int dp; @@ -48,8 +48,8 @@ public class GVCFGenotype { // todo -- what to do about phasing? Perhaps we shouldn't support it // todo -- is the FL field generic or just a flag? Should we even support per sample filtering? - public GVCFGenotype(final GVCFHeaderBuilder gvcfHeaderBuilder, final List allAlleles, Genotype genotype) { - gq = GVCF.qualToByte(genotype.getPhredScaledQual()); + public GCFGenotype(final GCFHeaderBuilder GCFHeaderBuilder, final List allAlleles, Genotype genotype) { + gq = GCF.qualToByte(genotype.getPhredScaledQual()); gt = encodeAlleles(genotype.getAlleles(), allAlleles); dp = genotype.getAttributeAsInt("DP", 0); @@ -65,13 +65,13 @@ private int nAllelesToNPls( int nAlleles ) { return nAlleles*(nAlleles+1) / 2; } - public GVCFGenotype(GVCF gvcf, DataInputStream inputStream) throws IOException { + public GCFGenotype(GCF GCF, DataInputStream inputStream) throws IOException { int gqInt = inputStream.readUnsignedByte(); gq = (byte)gqInt; gt = inputStream.readInt(); dp = inputStream.readInt(); - ad = GVCF.readIntArray(inputStream, gvcf.getNAlleles()); - pl = GVCF.readByteArray(inputStream, nAllelesToNPls(gvcf.getNAlleles())); + ad = GCF.readIntArray(inputStream, GCF.getNAlleles()); + pl = GCF.readByteArray(inputStream, nAllelesToNPls(GCF.getNAlleles())); } // 2 alleles => 1 + 8 + 8 + 3 => 20 @@ -82,7 +82,7 @@ protected int sizeInBytes() { + 1 * pl.length; // pl } - public Genotype decode(final String sampleName, final GVCFHeader header, GVCF gvcf, List alleleIndex) { + 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 Set filters = Collections.emptySet(); @@ -140,8 +140,8 @@ public int write(DataOutputStream outputStream) throws IOException { outputStream.writeByte(gq); outputStream.writeInt(gt); outputStream.writeInt(dp); - GVCF.writeIntArray(ad, outputStream, false); - GVCF.writeByteArray(pl, outputStream, false); + GCF.writeIntArray(ad, outputStream, false); + GCF.writeByteArray(pl, outputStream, false); return outputStream.size() - startSize; } } diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java similarity index 92% rename from public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java rename to public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java index c52c975bde..d0c765cc46 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeader.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java @@ -22,11 +22,9 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.gvcf; +package org.broadinstitute.sting.utils.gcf; import org.apache.log4j.Logger; -import org.broadinstitute.sting.utils.QualityUtils; -import org.broadinstitute.sting.utils.codecs.vcf.AbstractVCFCodec; import org.broadinstitute.sting.utils.codecs.vcf.VCFCodec; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -64,8 +62,8 @@ * @author Your Name * @since Date created */ -public class GVCFHeader { - final protected static Logger logger = Logger.getLogger(GVCFHeader.class); +public class GCFHeader { + final protected static Logger logger = Logger.getLogger(GCFHeader.class); private static byte[] MAGIC_HEADER = "GVCF0.1\1".getBytes(); final List alleles; @@ -73,14 +71,14 @@ public class GVCFHeader { final List samples; final List> filters; - public GVCFHeader(final Map allelesIn, final Map stringIn, final Map samplesIn) { + public GCFHeader(final Map allelesIn, final Map stringIn, final Map samplesIn) { this.alleles = linearize(allelesIn); this.strings = linearize(stringIn); this.samples = linearize(samplesIn); this.filters = null; // not used with this constructor } - public GVCFHeader(DataInputStream inputStream) throws IOException { + public GCFHeader(DataInputStream inputStream) throws IOException { byte[] headerTest = new byte[MAGIC_HEADER.length]; inputStream.read(headerTest); if ( ! Arrays.equals(headerTest, MAGIC_HEADER) ) { diff --git a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeaderBuilder.java similarity index 93% rename from public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java rename to public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeaderBuilder.java index 2d045b8ead..40e01ec72d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gvcf/GVCFHeaderBuilder.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeaderBuilder.java @@ -22,7 +22,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.gvcf; +package org.broadinstitute.sting.utils.gcf; import org.broadinstitute.sting.utils.variantcontext.Allele; @@ -56,13 +56,13 @@ * @author Your Name * @since Date created */ -public class GVCFHeaderBuilder { +public class GCFHeaderBuilder { Map alleles = new HashMap(); Map strings = new HashMap(); Map samples = new HashMap(); - public GVCFHeader createHeader() { - return new GVCFHeader(alleles, strings, samples); + public GCFHeader createHeader() { + return new GCFHeader(alleles, strings, samples); } public int encodeString(final String chr) { return encode(strings, chr); } From fe5724b6ea7c77f3f38f35b2d29d118860b6fc2a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Sep 2011 23:27:08 -0400 Subject: [PATCH 009/363] Refactored indexing part of StandardVCFWriter into superclass -- Now other implementations of the VCFWriter can easily share common functions, such as writing an index on the fly --- .../utils/codecs/vcf/IndexingVCFWriter.java | 116 ++++++++++++++++++ .../utils/codecs/vcf/StandardVCFWriter.java | 107 +++++----------- 2 files changed, 148 insertions(+), 75 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java new file mode 100644 index 0000000000..632bf8ed31 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java @@ -0,0 +1,116 @@ +/* + * 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.codecs.vcf; + +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.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.io.*; + +/** + * this class writes VCF files + */ +public abstract class IndexingVCFWriter implements VCFWriter { + final private File indexFile; + final private String name; + + private PositionalStream positionalStream; + private DynamicIndexCreator indexer; + private LittleEndianOutputStream idxStream; + + protected IndexingVCFWriter(String name, File location, OutputStream output, boolean enableOnTheFlyIndexing) { + this.name = name; + + if ( enableOnTheFlyIndexing ) { + indexFile = Tribble.indexFile(location); + try { + idxStream = new LittleEndianOutputStream(new FileOutputStream(indexFile)); + //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); + } catch ( IOException ex ) { + // No matter what we keep going, since we don't care if we can't create the index file + } + } else { + idxStream = null; + indexer = null; + positionalStream = null; + indexFile = 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()); + 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()); + } + + protected static final String writerName(File location, OutputStream stream) { + return location == null ? stream.toString() : location.getAbsolutePath(); + } + + protected static OutputStream openOutputStream(File location) { + try { + return new FileOutputStream(location); + } catch (FileNotFoundException e) { + throw new ReviewedStingException("Unable to create VCF file at location: " + location, e); + } + } +} 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 e28cd7598e..ebcba9635a 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 @@ -44,26 +44,19 @@ /** * this class writes VCF files */ -public class StandardVCFWriter implements VCFWriter { +public class StandardVCFWriter extends IndexingVCFWriter { + // the print stream we're writing to + final protected BufferedWriter mWriter; + + // should we write genotypes or just sites? + final protected boolean doNotWriteGenotypes; // the VCF header we're storing protected VCFHeader mHeader = null; - // the print stream we're writing to - protected BufferedWriter mWriter; - protected PositionalStream positionalStream = null; - // were filters applied? protected boolean filtersWereAppliedToContext = false; - // should we write genotypes or just sites? - protected boolean doNotWriteGenotypes = false; - - protected DynamicIndexCreator indexer = null; - protected File indexFile = null; - LittleEndianOutputStream idxStream = null; - File location = null; - /** * create a VCF writer, given a file to write to * @@ -93,32 +86,22 @@ public StandardVCFWriter(OutputStream output) { * @param doNotWriteGenotypes do not write genotypes */ public StandardVCFWriter(OutputStream output, boolean doNotWriteGenotypes) { - mWriter = new BufferedWriter(new OutputStreamWriter(output)); - this.doNotWriteGenotypes = doNotWriteGenotypes; + this(null, output, false, doNotWriteGenotypes); } public StandardVCFWriter(File location, OutputStream output, boolean enableOnTheFlyIndexing, boolean doNotWriteGenotypes) { - this.location = location; - - if ( enableOnTheFlyIndexing ) { - indexFile = Tribble.indexFile(location); - try { - idxStream = new LittleEndianOutputStream(new FileOutputStream(indexFile)); - //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); - output = positionalStream; - } catch ( IOException ex ) { - // No matter what we keep going, since we don't care if we can't create the index file - } - } - - //mWriter = new BufferedWriter(new OutputStreamWriter(new PositionalStream(output))); - mWriter = new BufferedWriter(new OutputStreamWriter(output)); + super(writerName(location, output), location, output, enableOnTheFlyIndexing); + mWriter = new BufferedWriter(new OutputStreamWriter(output)); // todo -- fix buffer size this.doNotWriteGenotypes = doNotWriteGenotypes; } + // -------------------------------------------------------------------------------- + // + // VCFWriter interface functions + // + // -------------------------------------------------------------------------------- + + @Override public void writeHeader(VCFHeader header) { mHeader = doNotWriteGenotypes ? new VCFHeader(header.getMetaData()) : header; @@ -158,44 +141,24 @@ public void writeHeader(VCFHeader header) { mWriter.flush(); // necessary so that writing to an output stream will work } catch (IOException e) { - throw new TribbleException("IOException writing the VCF header to " + locationString(), e); + throw new ReviewedStingException("IOException writing the VCF header to " + getStreamName(), e); } } - private String locationString() { - return location == null ? mWriter.toString() : location.getAbsolutePath(); - } - /** * attempt to close the VCF file */ + @Override public void close() { // try to close the vcf stream try { mWriter.flush(); mWriter.close(); } catch (IOException e) { - throw new TribbleException("Unable to close " + locationString() + " because of " + e.getMessage()); + throw new ReviewedStingException("Unable to close " + getStreamName(), e); } - // try to close the index stream (keep it separate to help debugging efforts) - if ( indexer != null ) { - try { - Index index = indexer.finalizeIndex(positionalStream.getPosition()); - index.write(idxStream); - idxStream.close(); - } catch (IOException e) { - throw new TribbleException("Unable to close index for " + locationString() + " because of " + e.getMessage()); - } - } - } - - protected static OutputStream openOutputStream(File location) { - try { - return new FileOutputStream(location); - } catch (FileNotFoundException e) { - throw new TribbleException("Unable to create VCF file at location: " + location); - } + super.close(); } /** @@ -203,28 +166,17 @@ protected static OutputStream openOutputStream(File location) { * * @param vc the Variant Context object */ + @Override public void add(VariantContext vc) { - add(vc, false); - } - - /** - * add a record to the file - * - * @param vc the Variant Context object - * @param refBaseShouldBeAppliedToEndOfAlleles *** THIS SHOULD BE FALSE EXCEPT FOR AN INDEL AT THE EXTREME BEGINNING OF A CONTIG (WHERE THERE IS NO PREVIOUS BASE, SO WE USE THE BASE AFTER THE EVENT INSTEAD) - */ - public void add(VariantContext vc, boolean refBaseShouldBeAppliedToEndOfAlleles) { if ( mHeader == null ) - throw new IllegalStateException("The VCF Header must be written before records can be added: " + locationString()); + throw new IllegalStateException("The VCF Header must be written before records can be added: " + getStreamName()); if ( doNotWriteGenotypes ) vc = VariantContext.modifyGenotypes(vc, null); try { - vc = VariantContext.createVariantContextWithPaddedAlleles(vc, refBaseShouldBeAppliedToEndOfAlleles); - - // if we are doing on the fly indexing, add the record ***before*** we write any bytes - if ( indexer != null ) indexer.addFeature(vc, positionalStream.getPosition()); + vc = VariantContext.createVariantContextWithPaddedAlleles(vc, false); + super.add(vc); Map alleleMap = new HashMap(vc.getAlleles().size()); alleleMap.put(Allele.NO_CALL, VCFConstants.EMPTY_ALLELE); // convenience for lookup @@ -317,10 +269,16 @@ public void add(VariantContext vc, boolean refBaseShouldBeAppliedToEndOfAlleles) mWriter.write("\n"); mWriter.flush(); // necessary so that writing to an output stream will work } catch (IOException e) { - throw new RuntimeException("Unable to write the VCF object to " + locationString()); + throw new RuntimeException("Unable to write the VCF object to " + getStreamName()); } } + // -------------------------------------------------------------------------------- + // + // implementation functions + // + // -------------------------------------------------------------------------------- + public static final String getFilterString(final VariantContext vc) { return getFilterString(vc, false); } @@ -531,12 +489,11 @@ private static List calcVCFGenotypeKeys(VariantContext vc) { } - public static int countOccurrences(char c, String s) { + private static int countOccurrences(char c, String s) { int count = 0; for (int i = 0; i < s.length(); i++) { count += s.charAt(i) == c ? 1 : 0; } return count; } - } From cd2c511c4ae8a7d13ca6fe3604308ca5fdea5c00 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 7 Sep 2011 23:28:46 -0400 Subject: [PATCH 010/363] GCF improvements -- Support for streaming VCF writing via the VCFWriter interface -- GCF now has a header and a footer. The header is minimal, and contains a forward pointer to the position of the footer in the file. -- Readers now read the header, and then jump to the footer to get the rest of the "header" information -- Version now a field in GCF --- .../broadinstitute/sting/utils/gcf/GCF.java | 69 +++++----- .../sting/utils/gcf/GCFHeader.java | 49 +++++-- .../sting/utils/gcf/GCFWriter.java | 122 ++++++++++++++++++ 3 files changed, 198 insertions(+), 42 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java 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 5ab241ebf2..ef0d9ca42b 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCF.java @@ -79,8 +79,13 @@ public GCF(final GCFHeaderBuilder GCFHeaderBuilder, final VariantContext vc, boo } } - public GCF(DataInputStream inputStream, boolean skipGenotypes) throws IOException { + public GCF(DataInputStream inputStream, boolean skipGenotypes) throws IOException, EOFException { chromOffset = inputStream.readInt(); + + // have we reached the footer? + if ( chromOffset == GCFHeader.FOOTER_START_MARKER ) + throw new EOFException(); + start = inputStream.readInt(); stop = inputStream.readInt(); id = inputStream.readUTF(); @@ -106,6 +111,32 @@ public GCF(DataInputStream inputStream, boolean skipGenotypes) throws IOExceptio throw new UserException.MalformedFile("Record not terminated by RECORD_TERMINATOR key"); } + public int write(DataOutputStream outputStream) throws IOException { + int startSize = outputStream.size(); + outputStream.writeInt(chromOffset); + outputStream.writeInt(start); + outputStream.writeInt(stop); + outputStream.writeUTF(id); + outputStream.writeByte(refPad); + writeIntArray(alleleOffsets, outputStream, true); + outputStream.writeFloat(qual); + outputStream.writeUTF(info); + outputStream.writeInt(filterOffset); + + int nGenotypes = genotypes.size(); + int expectedSizeOfGenotypes = nGenotypes == 0 ? 0 : genotypes.get(0).sizeInBytes() * nGenotypes; + outputStream.writeInt(nGenotypes); + outputStream.writeInt(expectedSizeOfGenotypes); + int obsSizeOfGenotypes = 0; + for ( GCFGenotype g : genotypes ) + obsSizeOfGenotypes += g.write(outputStream); + if ( obsSizeOfGenotypes != expectedSizeOfGenotypes ) + throw new RuntimeException("Expect and observed genotype sizes disagree! expect = " + expectedSizeOfGenotypes + " obs =" + obsSizeOfGenotypes); + + outputStream.writeInt(RECORD_TERMINATOR); + return outputStream.size() - startSize; + } + public VariantContext decode(final String source, final GCFHeader header) { final String contig = header.getString(chromOffset); alleleMap = header.getAlleles(alleleOffsets); @@ -154,31 +185,6 @@ private List encodeGenotypes(final GCFHeaderBuilder GCFHeaderBuilde public int getNAlleles() { return alleleOffsets.length; } - public int write(DataOutputStream outputStream) throws IOException { - int startSize = outputStream.size(); - outputStream.writeInt(chromOffset); - outputStream.writeInt(start); - outputStream.writeInt(stop); - outputStream.writeUTF(id); - outputStream.writeByte(refPad); - writeIntArray(alleleOffsets, outputStream, true); - outputStream.writeFloat(qual); - outputStream.writeUTF(info); - outputStream.writeInt(filterOffset); - - int nGenotypes = genotypes.size(); - int expectedSizeOfGenotypes = nGenotypes == 0 ? 0 : genotypes.get(0).sizeInBytes() * nGenotypes; - outputStream.writeInt(nGenotypes); - outputStream.writeInt(expectedSizeOfGenotypes); - int obsSizeOfGenotypes = 0; - for ( GCFGenotype g : genotypes ) - obsSizeOfGenotypes += g.write(outputStream); - if ( obsSizeOfGenotypes != expectedSizeOfGenotypes ) - throw new RuntimeException("Expect and observed genotype sizes disagree! expect = " + expectedSizeOfGenotypes + " obs =" + obsSizeOfGenotypes); - - outputStream.writeInt(RECORD_TERMINATOR); - return outputStream.size() - startSize; - } private final String infoFieldString(VariantContext vc, final GCFHeaderBuilder GCFHeaderBuilder) { StringBuilder s = new StringBuilder(); @@ -200,13 +206,14 @@ private final String infoFieldString(VariantContext vc, final GCFHeaderBuilder G return s.toString(); } - private final static int BUFFER_SIZE = 1048576; // 2**20 - public static DataOutputStream createOutputStream(final File file) throws FileNotFoundException { - return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE)); + protected final static int BUFFER_SIZE = 1048576; // 2**20 + + public static DataInputStream createDataInputStream(final InputStream stream) { + return new DataInputStream(new BufferedInputStream(stream, BUFFER_SIZE)); } - public static DataInputStream createInputStream(final File file) throws FileNotFoundException { - return new DataInputStream(new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE)); + public static FileInputStream createFileInputStream(final File file) throws FileNotFoundException { + return new FileInputStream(file); } protected final static int[] readIntArray(final DataInputStream inputStream) throws IOException { diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java index d0c765cc46..6d96eda563 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFHeader.java @@ -30,9 +30,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import java.io.*; import java.util.*; /** @@ -65,25 +63,45 @@ public class GCFHeader { final protected static Logger logger = Logger.getLogger(GCFHeader.class); - private static byte[] MAGIC_HEADER = "GVCF0.1\1".getBytes(); + public final static int GCF_VERSION = 1; + public final static byte[] GCF_FILE_START_MARKER = "GCF\1".getBytes(); + public final static int FOOTER_START_MARKER = -1; + public final static long HEADER_FORWARD_REFERENCE_OFFSET = GCF_FILE_START_MARKER.length + 4; // for the version + + final int version; + long footerPosition; final List alleles; final List strings; final List samples; final List> filters; public GCFHeader(final Map allelesIn, final Map stringIn, final Map samplesIn) { + version = GCF_VERSION; + footerPosition = 0; this.alleles = linearize(allelesIn); this.strings = linearize(stringIn); this.samples = linearize(samplesIn); this.filters = null; // not used with this constructor } - public GCFHeader(DataInputStream inputStream) throws IOException { - byte[] headerTest = new byte[MAGIC_HEADER.length]; + public GCFHeader(FileInputStream fileInputStream) throws IOException { + DataInputStream inputStream = new DataInputStream(fileInputStream); + byte[] headerTest = new byte[GCF_FILE_START_MARKER.length]; inputStream.read(headerTest); - if ( ! Arrays.equals(headerTest, MAGIC_HEADER) ) { - throw new UserException("Could not read GVCF file. MAGIC_HEADER missing. Saw " + headerTest); + if ( ! Arrays.equals(headerTest, GCF_FILE_START_MARKER) ) { + throw new UserException("Could not read GVCF file. GCF_FILE_START_MARKER missing. Saw " + new String(headerTest)); } else { + version = inputStream.readInt(); + logger.info("Read GCF version " + version); + footerPosition = inputStream.readLong(); + logger.info("Read footer position of " + footerPosition); + long lastPos = fileInputStream.getChannel().position(); + logger.info(" Last position is " + lastPos); + + // seek to the footer + fileInputStream.getChannel().position(footerPosition); + if ( inputStream.readInt() != FOOTER_START_MARKER ) + throw new UserException.MalformedFile("Malformed GCF file: couldn't find the footer marker"); alleles = stringsToAlleles(readStrings(inputStream)); strings = readStrings(inputStream); samples = readStrings(inputStream); @@ -91,19 +109,28 @@ public GCFHeader(DataInputStream inputStream) throws IOException { logger.info(String.format("String map of %d elements", strings.size())); logger.info(String.format("Sample map of %d elements", samples.size())); filters = initializeFilterCache(); + fileInputStream.getChannel().position(lastPos); } } - public int write(final DataOutputStream outputStream) throws IOException { + public static int writeHeader(final DataOutputStream outputStream) throws IOException { + int startBytes = outputStream.size(); + outputStream.write(GCF_FILE_START_MARKER); + outputStream.writeInt(GCF_VERSION); + outputStream.writeLong(0); + return outputStream.size() - startBytes; + } + + public int writeFooter(final DataOutputStream outputStream) throws IOException { int startBytes = outputStream.size(); - outputStream.write(MAGIC_HEADER); + outputStream.writeInt(FOOTER_START_MARKER); // has to be the same as chrom encoding write(outputStream, allelesToStrings(alleles)); write(outputStream, strings); write(outputStream, samples); return outputStream.size() - startBytes; } - public void write(DataOutputStream outputStream, List l) throws IOException { + private void write(DataOutputStream outputStream, List l) throws IOException { outputStream.writeInt(l.size()); for ( String elt : l ) outputStream.writeUTF(elt); } diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java new file mode 100644 index 0000000000..7ff6e27a26 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java @@ -0,0 +1,122 @@ +/* + * 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.gcf; + +import org.broadinstitute.sting.utils.codecs.vcf.IndexingVCFWriter; +import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.variantcontext.VariantContext; + +import java.io.*; + +/** + * GCFWriter implementing the VCFWriter interface + * @author Your Name + * @since Date created + */ +public class GCFWriter extends IndexingVCFWriter { + final boolean skipGenotypes; + final FileOutputStream fileOutputStream; + final DataOutputStream dataOutputStream; + final GCFHeaderBuilder gcfHeaderBuilder; + int nbytes = 0; + VCFHeader header = null; + File location; + + // -------------------------------------------------------------------------------- + // + // Constructors + // + // -------------------------------------------------------------------------------- + + public GCFWriter(File location, boolean enableOnTheFlyIndexing, boolean doNotWriteGenotypes) { + super(writerName(location, null), location, null, enableOnTheFlyIndexing); + this.location = location; + this.skipGenotypes = doNotWriteGenotypes; + + // write the output + try { + fileOutputStream = new FileOutputStream(location); + dataOutputStream = createDataOutputStream(fileOutputStream); + gcfHeaderBuilder = new GCFHeaderBuilder(); + } catch ( FileNotFoundException e ) { + throw new UserException.CouldNotCreateOutputFile(location, e); + } + } + + // -------------------------------------------------------------------------------- + // + // VCFWriter interface functions + // + // -------------------------------------------------------------------------------- + + @Override + public void writeHeader(VCFHeader header) { + this.header = header; + try { + nbytes += GCFHeader.writeHeader(dataOutputStream); + } catch ( IOException e ) { + throw new UserException.CouldNotCreateOutputFile(getStreamName(), "Couldn't write header", e); + } + } + + @Override + public void add(VariantContext vc) { + super.add(vc); + GCF gcf = new GCF(gcfHeaderBuilder, vc, skipGenotypes); + try { + nbytes += gcf.write(dataOutputStream); + } catch ( IOException e ) { + throw new UserException.CouldNotCreateOutputFile(getStreamName(), "Failed to add gcf record " + gcf + " to stream " + getStreamName(), e); + } + } + + @Override + public void close() { + // todo -- write out VCF header lines + GCFHeader gcfHeader = gcfHeaderBuilder.createHeader(); + try { + long headerPosition = nbytes; + nbytes += gcfHeader.writeFooter(dataOutputStream); + dataOutputStream.close(); + //System.out.println("Writing forward reference to " + headerPosition); + + RandomAccessFile raFile = new RandomAccessFile(location, "rw"); + raFile.seek(GCFHeader.HEADER_FORWARD_REFERENCE_OFFSET); + raFile.writeLong(headerPosition); + raFile.close(); + } catch ( IOException e ) { + throw new ReviewedStingException("Failed to close GCFWriter " + getStreamName(), e); + } + + super.close(); + } + + private static final DataOutputStream createDataOutputStream(final OutputStream stream) { + return new DataOutputStream(new BufferedOutputStream(stream, GCF.BUFFER_SIZE)); + } + +} From 48461b34afc6af2a545f961ac3563b7b0a602725 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 8 Sep 2011 15:01:13 -0400 Subject: [PATCH 011/363] Added TYPE argument to print out VariantType --- .../sting/gatk/walkers/variantutils/VariantsToTable.java | 1 + 1 file changed, 1 insertion(+) 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 2a877fb093..bf9ff35de0 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 @@ -309,6 +309,7 @@ public String get(VariantContext vc) { 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()); } }); getters.put("NO-CALL", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getNoCallCount()); } }); + getters.put("TYPE", new Getter() { public String get(VariantContext vc) { return vc.getType().toString(); } }); getters.put("VAR", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getHetCount() + vc.getHomVarCount()); } }); getters.put("NSAMPLES", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getNSamples()); } }); getters.put("NCALLED", new Getter() { public String get(VariantContext vc) { return Integer.toString(vc.getNSamples() - vc.getNoCallCount()); } }); From 61bccb71c6a31e62649813875b8b9de73f71c654 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 8 Sep 2011 16:09:33 -0400 Subject: [PATCH 012/363] Improvements to AssignSomaticStatus -- Added SOMATIC_AC_TAG_NAME, SOMATIC_NONREF_TAG_NAME tags -- Added minimalVCF option for testing output From 7fefc224baf1b6c983f5506394b63d583d77ff2c Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Thu, 8 Sep 2011 16:31:04 -0400 Subject: [PATCH 013/363] Unify outputs from PostCallingQC script to produce results compatible with the QC database building process. From 06cb20f2a5fd2681a95613ae0b8b8a53c6002f4b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 9 Sep 2011 12:56:45 -0400 Subject: [PATCH 014/363] Intermediate commit cleaning up scatter intervals -- Adding unit tests to ensure uniformity of intervals --- .../sting/utils/interval/IntervalUtils.java | 53 +- .../utils/interval/IntervalUtilsUnitTest.java | 1034 +++++++++-------- .../queue/extensions/gatk/GATKIntervals.scala | 130 +-- .../gatk/IntervalScatterFunction.scala | 4 +- .../gatk/GATKIntervalsUnitTest.scala | 10 +- 5 files changed, 657 insertions(+), 574 deletions(-) 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 f551e1368f..41cbbe59fa 100644 --- a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java @@ -334,24 +334,44 @@ public static void scatterContigIntervals(SAMFileHeader fileHeader, List> splitIntervalsToSubLists(List locs, List splits) { + int locIndex = 1; + int start = 0; + List> sublists = new ArrayList>(splits.size()); + for (Integer stop: splits) { + List curList = new ArrayList(); + for (int i = start; i < stop; i++) + curList.add(locs.get(i)); + start = stop; + sublists.add(curList); + } + + return sublists; + } + + + /** + * Splits an interval list into multiple files. + * @param fileHeader The sam file header. + * @param splits Pre-divided genome locs returned by splitFixedIntervals. * @param scatterParts The output interval lists to write to. */ - public static void scatterFixedIntervals(SAMFileHeader fileHeader, List locs, List splits, List scatterParts) { + public static void scatterFixedIntervals(SAMFileHeader fileHeader, List> splits, List scatterParts) { if (splits.size() != scatterParts.size()) throw new UserException.BadArgumentValue("splits", String.format("Split points %d does not equal the number of scatter parts %d.", splits.size(), scatterParts.size())); + int fileIndex = 0; int locIndex = 1; - int start = 0; - for (Integer stop: splits) { + for (final List split : splits) { IntervalList intervalList = new IntervalList(fileHeader); - for (int i = start; i < stop; i++) - intervalList.add(toInterval(locs.get(i), locIndex++)); + for (final GenomeLoc loc : split) + intervalList.add(toInterval(loc, locIndex++)); intervalList.write(scatterParts.get(fileIndex++)); - start = stop; } } @@ -361,17 +381,15 @@ public static void scatterFixedIntervals(SAMFileHeader fileHeader, List splitFixedIntervals(List locs, int numParts) { + public static List> splitFixedIntervals(List locs, int numParts) { if (locs.size() < numParts) throw new UserException.BadArgumentValue("scatterParts", String.format("Cannot scatter %d locs into %d parts.", locs.size(), numParts)); - long locsSize = 0; - for (GenomeLoc loc: locs) - locsSize += loc.size(); - List splitPoints = new ArrayList(); + final long locsSize = intervalSize(locs); + final List splitPoints = new ArrayList(); addFixedSplit(splitPoints, locs, locsSize, 0, locs.size(), numParts); Collections.sort(splitPoints); splitPoints.add(locs.size()); - return splitPoints; + return splitIntervalsToSubLists(locs, splitPoints); } private static void addFixedSplit(List splitPoints, List locs, long locsSize, int startIndex, int stopIndex, int numParts) { @@ -441,4 +459,11 @@ public static List mergeIntervalLocations(final List raw, return merged; } } + + public static final long intervalSize(final List locs) { + long size = 0; + for ( final GenomeLoc loc : locs ) + size += loc.size(); + return size; + } } 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 bb892eec81..bd6bf9591a 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java @@ -30,6 +30,20 @@ public class IntervalUtilsUnitTest extends BaseTest { private SAMFileHeader hg19Header; private GenomeLocParser hg19GenomeLocParser; private List hg19ReferenceLocs; + private List hg19exomeIntervals; + + private List getLocs(String... intervals) { + return getLocs(Arrays.asList(intervals)); + } + + private List getLocs(List intervals) { + if (intervals.size() == 0) + return hg18ReferenceLocs; + List locs = new ArrayList(); + for (String interval: intervals) + locs.add(hg18GenomeLocParser.parseGenomeLoc(interval)); + return locs; + } @BeforeClass public void init() { @@ -54,511 +68,555 @@ public void init() { ReferenceSequenceFile seq = new CachingIndexedFastaSequenceFile(hg19Ref); hg19GenomeLocParser = new GenomeLocParser(seq); hg19ReferenceLocs = Collections.unmodifiableList(GenomeLocSortedSet.createSetFromSequenceDictionary(referenceDataSource.getReference().getSequenceDictionary()).toList()) ; + + hg19exomeIntervals = Collections.unmodifiableList(IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(hg19Intervals), false)); } catch(FileNotFoundException ex) { throw new UserException.CouldNotReadInputFile(hg19Ref,ex); } } - @Test(expectedExceptions=UserException.class) - public void testMergeListsBySetOperatorNoOverlap() { - // a couple of lists we'll use for the testing - List listEveryTwoFromOne = new ArrayList(); - List listEveryTwoFromTwo = new ArrayList(); - - // create the two lists we'll use - for (int x = 1; x < 101; x++) { - if (x % 2 == 0) - listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); - else - listEveryTwoFromOne.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); - } - - List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.UNION); - Assert.assertEquals(ret.size(), 100); - ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.INTERSECTION); - Assert.assertEquals(ret.size(), 0); - } - - @Test - public void testMergeListsBySetOperatorAllOverlap() { - // a couple of lists we'll use for the testing - List allSites = new ArrayList(); - List listEveryTwoFromTwo = new ArrayList(); - - // create the two lists we'll use - for (int x = 1; x < 101; x++) { - if (x % 2 == 0) - listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); - allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); - } - - List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); - Assert.assertEquals(ret.size(), 150); - ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); - Assert.assertEquals(ret.size(), 50); - } - - @Test - public void testMergeListsBySetOperator() { - // a couple of lists we'll use for the testing - List allSites = new ArrayList(); - List listEveryTwoFromTwo = new ArrayList(); - - // create the two lists we'll use - for (int x = 1; x < 101; x++) { - if (x % 5 == 0) { - listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); - allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); - } - } - - List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); - Assert.assertEquals(ret.size(), 40); - ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); - Assert.assertEquals(ret.size(), 20); - } - - @Test - public void testGetContigLengths() { - Map lengths = IntervalUtils.getContigSizes(new File(BaseTest.hg18Reference)); - Assert.assertEquals((long)lengths.get("chr1"), 247249719); - Assert.assertEquals((long)lengths.get("chr2"), 242951149); - Assert.assertEquals((long)lengths.get("chr3"), 199501827); - Assert.assertEquals((long)lengths.get("chr20"), 62435964); - Assert.assertEquals((long)lengths.get("chrX"), 154913754); - } - - private List getLocs(String... intervals) { - return getLocs(Arrays.asList(intervals)); - } - - private List getLocs(List intervals) { - if (intervals.size() == 0) - return hg18ReferenceLocs; - List locs = new ArrayList(); - for (String interval: intervals) - locs.add(hg18GenomeLocParser.parseGenomeLoc(interval)); - return locs; - } - - @Test - public void testParseIntervalArguments() { - Assert.assertEquals(getLocs().size(), 45); - Assert.assertEquals(getLocs("chr1", "chr2", "chr3").size(), 3); - Assert.assertEquals(getLocs("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2").size(), 4); - } - - @Test - public void testIsIntervalFile() { - Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list")); - Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list", true)); - - List extensions = Arrays.asList("bed", "interval_list", "intervals", "list", "picard"); - for (String extension: extensions) { - Assert.assertTrue(IntervalUtils.isIntervalFile("test_intervals." + extension, false), "Tested interval file extension: " + extension); - } - } - - @Test(expectedExceptions = UserException.CouldNotReadInputFile.class) - public void testMissingIntervalFile() { - IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "no_such_intervals.list"); - } - - @Test - public void testFixedScatterIntervalsBasic() { - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); - - List files = testFiles("basic.", 3, ".intervals"); - - List locs = getLocs("chr1", "chr2", "chr3"); - List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 1); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2); - Assert.assertEquals(locs3.get(0), chr3); - } - - @Test - public void testScatterFixedIntervalsLessFiles() { - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); - GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); - - List files = testFiles("less.", 3, ".intervals"); - - List locs = getLocs("chr1", "chr2", "chr3", "chr4"); - List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 2); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2); - Assert.assertEquals(locs3.get(0), chr3); - Assert.assertEquals(locs3.get(1), chr4); - } - - @Test(expectedExceptions=UserException.BadArgumentValue.class) - public void testSplitFixedIntervalsMoreFiles() { - List files = testFiles("more.", 3, ".intervals"); - List locs = getLocs("chr1", "chr2"); - IntervalUtils.splitFixedIntervals(locs, files.size()); - } - - @Test(expectedExceptions=UserException.BadArgumentValue.class) - public void testScatterFixedIntervalsMoreFiles() { - List files = testFiles("more.", 3, ".intervals"); - List locs = getLocs("chr1", "chr2"); - List splits = IntervalUtils.splitFixedIntervals(locs, locs.size()); // locs.size() instead of files.size() - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - } - @Test - public void testScatterFixedIntervalsStart() { - List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); - GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); - GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); - - List files = testFiles("split.", 3, ".intervals"); - - List locs = getLocs(intervals); - List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 2); - - Assert.assertEquals(locs1.get(0), chr1a); - Assert.assertEquals(locs2.get(0), chr1b); - Assert.assertEquals(locs3.get(0), chr2); - Assert.assertEquals(locs3.get(1), chr3); - } - - @Test - public void testScatterFixedIntervalsMiddle() { - List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); - GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); - GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); - - List files = testFiles("split.", 3, ".intervals"); - - List locs = getLocs(intervals); - List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 2); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2a); - Assert.assertEquals(locs3.get(0), chr2b); - Assert.assertEquals(locs3.get(1), chr3); - } - - @Test - public void testScatterFixedIntervalsEnd() { - List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); - GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); - GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); - - List files = testFiles("split.", 3, ".intervals"); - - List locs = getLocs(intervals); - List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 2); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 1); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs1.get(1), chr2); - Assert.assertEquals(locs2.get(0), chr3a); - Assert.assertEquals(locs3.get(0), chr3b); - } - - @Test - public void testScatterFixedIntervalsFile() { - List files = testFiles("sg.", 20, ".intervals"); - List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(BaseTest.GATKDataLocation + "whole_exome_agilent_designed_120.targets.hg18.chr20.interval_list"), false); - List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); + // ------------------------------------------------------------------------------------- + // + // tests to ensure the quality of the interval cuts of the interval cutting functions + // + // ------------------------------------------------------------------------------------- - int[] counts = { - 125, 138, 287, 291, 312, 105, 155, 324, - 295, 298, 141, 121, 285, 302, 282, 88, - 116, 274, 282, 248 -// 5169, 5573, 10017, 10567, 10551, -// 5087, 4908, 10120, 10435, 10399, -// 5391, 4735, 10621, 10352, 10654, -// 5227, 5256, 10151, 9649, 9825 - }; + private class IntervalSlicingTest extends TestDataProvider { + public int parts; + public double maxAllowableVariance; - //String splitCounts = ""; - for (int lastIndex = 0, i = 0; i < splits.size(); i++) { - int splitIndex = splits.get(i); - int splitCount = (splitIndex - lastIndex); - //splitCounts += ", " + splitCount; - lastIndex = splitIndex; - Assert.assertEquals(splitCount, counts[i], "Num intervals in split " + i); + private IntervalSlicingTest(final int parts, final double maxAllowableVariance) { + super(IntervalSlicingTest.class); + this.parts = parts; + this.maxAllowableVariance = maxAllowableVariance; } - //System.out.println(splitCounts.substring(2)); - IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); - - int locIndex = 0; - for (int i = 0; i < files.size(); i++) { - String file = files.get(i).toString(); - List parsedLocs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(file), false); - Assert.assertEquals(parsedLocs.size(), counts[i], "Intervals in " + file); - for (GenomeLoc parsedLoc: parsedLocs) - Assert.assertEquals(parsedLoc, locs.get(locIndex), String.format("Genome loc %d from file %d", locIndex++, i)); - } - Assert.assertEquals(locIndex, locs.size(), "Total number of GenomeLocs"); - } - - @Test - public void testScatterFixedIntervalsMax() { - List files = testFiles("sg.", 85, ".intervals"); - List splits = IntervalUtils.splitFixedIntervals(hg19ReferenceLocs, files.size()); - IntervalUtils.scatterFixedIntervals(hg19Header, hg19ReferenceLocs, splits, files); - - for (int i = 0; i < files.size(); i++) { - String file = files.get(i).toString(); - List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); - Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); - Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); + public String toString() { + return String.format("IntervalSlicingTest parts=%d maxVar=%.2f", parts, maxAllowableVariance); } } - @Test - public void testScatterContigIntervalsOrder() { - List intervals = Arrays.asList("chr2:1-1", "chr1:1-1", "chr3:2-2"); - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); - - List files = testFiles("split.", 3, ".intervals"); - - IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 1); - - Assert.assertEquals(locs1.get(0), chr2); - Assert.assertEquals(locs2.get(0), chr1); - Assert.assertEquals(locs3.get(0), chr3); - } - - @Test - public void testScatterContigIntervalsBasic() { - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); - - List files = testFiles("contig_basic.", 3, ".intervals"); - - IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3"), files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 1); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2); - Assert.assertEquals(locs3.get(0), chr3); - } - - @Test - public void testScatterContigIntervalsLessFiles() { - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); - GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); - - List files = testFiles("contig_less.", 3, ".intervals"); - - IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3", "chr4"), files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 2); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2); - Assert.assertEquals(locs3.get(0), chr3); - Assert.assertEquals(locs3.get(1), chr4); - } - - @Test(expectedExceptions=UserException.BadArgumentValue.class) - public void testScatterContigIntervalsMoreFiles() { - List files = testFiles("contig_more.", 3, ".intervals"); - IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2"), files); - } - - @Test - public void testScatterContigIntervalsStart() { - List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); - GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); - GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); - - List files = testFiles("contig_split_start.", 3, ".intervals"); - - IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 2); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 1); - - Assert.assertEquals(locs1.get(0), chr1a); - Assert.assertEquals(locs1.get(1), chr1b); - Assert.assertEquals(locs2.get(0), chr2); - Assert.assertEquals(locs3.get(0), chr3); - } - - @Test - public void testScatterContigIntervalsMiddle() { - List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); - GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); - GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); - GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); - - List files = testFiles("contig_split_middle.", 3, ".intervals"); - - IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 2); - Assert.assertEquals(locs3.size(), 1); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2a); - Assert.assertEquals(locs2.get(1), chr2b); - Assert.assertEquals(locs3.get(0), chr3); - } - - @Test - public void testScatterContigIntervalsEnd() { - List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); - GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); - GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); - GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); - GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); - - List files = testFiles("contig_split_end.", 3 ,".intervals"); - - IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); - - List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); - List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); - List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - - Assert.assertEquals(locs1.size(), 1); - Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 2); - - Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs2.get(0), chr2); - Assert.assertEquals(locs3.get(0), chr3a); - Assert.assertEquals(locs3.get(1), chr3b); - } - - @Test - public void testScatterContigIntervalsMax() { - List files = testFiles("sg.", 85, ".intervals"); - IntervalUtils.scatterContigIntervals(hg19Header, hg19ReferenceLocs, files); - - for (int i = 0; i < files.size(); i++) { - String file = files.get(i).toString(); - List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); - Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); - Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); - } + @DataProvider(name = "intervalslicingdata") + public Object[][] createTrees() { +// new IntervalSlicingTest(1, 0); +// new IntervalSlicingTest(2, 0.1); + new IntervalSlicingTest(5, 0.1); +// new IntervalSlicingTest(10, 0.1); +// new IntervalSlicingTest(67, 0.1); +// new IntervalSlicingTest(100, 0.1); +// new IntervalSlicingTest(500, 0.1); +// new IntervalSlicingTest(1000, 0.1); + return IntervalSlicingTest.getTests(IntervalSlicingTest.class); } - private List testFiles(String prefix, int count, String suffix) { - ArrayList files = new ArrayList(); - for (int i = 1; i <= count; i++) { - files.add(createTempFile(prefix + i, suffix)); + @Test(dataProvider = "intervalslicingdata") + public void testFixedScatterIntervalsAlgorithm(IntervalSlicingTest test) { + List> splits = IntervalUtils.splitFixedIntervals(hg19exomeIntervals, test.parts); + + long totalSize = IntervalUtils.intervalSize(hg19exomeIntervals); + long idealSplitSize = totalSize / test.parts; + + long sumOfSplitSizes = 0; + int counter = 0; + for ( final List split : splits ) { + long splitSize = IntervalUtils.intervalSize(split); + double sigma = (splitSize - idealSplitSize) / (1.0 * idealSplitSize); + logger.warn(String.format("Split %d size %d ideal %d sigma %.2f", counter, splitSize, idealSplitSize, sigma)); + counter++; + sumOfSplitSizes += splitSize; + Assert.assertTrue(Math.abs(sigma) <= test.maxAllowableVariance, String.format("Interval %d (size %d ideal %d) has a variance %.2f outside of the tolerated range %.2f", counter, splitSize, idealSplitSize, sigma, test.maxAllowableVariance)); } - return files; - } - @DataProvider(name="unmergedIntervals") - public Object[][] getUnmergedIntervals() { - return new Object[][] { - new Object[] {"small_unmerged_picard_intervals.list"}, - new Object[] {"small_unmerged_gatk_intervals.list"} - }; + Assert.assertEquals(totalSize, sumOfSplitSizes, "Split intervals don't contain the exact number of bases in the origianl intervals"); } - @Test(dataProvider="unmergedIntervals") - public void testUnmergedIntervals(String unmergedIntervals) { - List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Collections.singletonList(validationDataLocation + unmergedIntervals), false); - Assert.assertEquals(locs.size(), 2); - - List merged = IntervalUtils.mergeIntervalLocations(locs, IntervalMergingRule.ALL); - Assert.assertEquals(merged.size(), 1); - } +// @Test(expectedExceptions=UserException.class) +// public void testMergeListsBySetOperatorNoOverlap() { +// // a couple of lists we'll use for the testing +// List listEveryTwoFromOne = new ArrayList(); +// List listEveryTwoFromTwo = new ArrayList(); +// +// // create the two lists we'll use +// for (int x = 1; x < 101; x++) { +// if (x % 2 == 0) +// listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); +// else +// listEveryTwoFromOne.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); +// } +// +// List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.UNION); +// Assert.assertEquals(ret.size(), 100); +// ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.INTERSECTION); +// Assert.assertEquals(ret.size(), 0); +// } +// +// @Test +// public void testMergeListsBySetOperatorAllOverlap() { +// // a couple of lists we'll use for the testing +// List allSites = new ArrayList(); +// List listEveryTwoFromTwo = new ArrayList(); +// +// // create the two lists we'll use +// for (int x = 1; x < 101; x++) { +// if (x % 2 == 0) +// listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); +// allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); +// } +// +// List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); +// Assert.assertEquals(ret.size(), 150); +// ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); +// Assert.assertEquals(ret.size(), 50); +// } +// +// @Test +// public void testMergeListsBySetOperator() { +// // a couple of lists we'll use for the testing +// List allSites = new ArrayList(); +// List listEveryTwoFromTwo = new ArrayList(); +// +// // create the two lists we'll use +// for (int x = 1; x < 101; x++) { +// if (x % 5 == 0) { +// listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); +// allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); +// } +// } +// +// List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); +// Assert.assertEquals(ret.size(), 40); +// ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); +// Assert.assertEquals(ret.size(), 20); +// } +// +// @Test +// public void testGetContigLengths() { +// Map lengths = IntervalUtils.getContigSizes(new File(BaseTest.hg18Reference)); +// Assert.assertEquals((long)lengths.get("chr1"), 247249719); +// Assert.assertEquals((long)lengths.get("chr2"), 242951149); +// Assert.assertEquals((long)lengths.get("chr3"), 199501827); +// Assert.assertEquals((long)lengths.get("chr20"), 62435964); +// Assert.assertEquals((long)lengths.get("chrX"), 154913754); +// } +// +// @Test +// public void testParseIntervalArguments() { +// Assert.assertEquals(getLocs().size(), 45); +// Assert.assertEquals(getLocs("chr1", "chr2", "chr3").size(), 3); +// Assert.assertEquals(getLocs("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2").size(), 4); +// } +// +// @Test +// public void testIsIntervalFile() { +// Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list")); +// Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list", true)); +// +// List extensions = Arrays.asList("bed", "interval_list", "intervals", "list", "picard"); +// for (String extension: extensions) { +// Assert.assertTrue(IntervalUtils.isIntervalFile("test_intervals." + extension, false), "Tested interval file extension: " + extension); +// } +// } +// +// @Test(expectedExceptions = UserException.CouldNotReadInputFile.class) +// public void testMissingIntervalFile() { +// IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "no_such_intervals.list"); +// } +// +// @Test +// public void testFixedScatterIntervalsBasic() { +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); +// +// List files = testFiles("basic.", 3, ".intervals"); +// +// List locs = getLocs("chr1", "chr2", "chr3"); +// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 1); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2); +// Assert.assertEquals(locs3.get(0), chr3); +// } +// +// @Test +// public void testScatterFixedIntervalsLessFiles() { +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); +// GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); +// +// List files = testFiles("less.", 3, ".intervals"); +// +// List locs = getLocs("chr1", "chr2", "chr3", "chr4"); +// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 2); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2); +// Assert.assertEquals(locs3.get(0), chr3); +// Assert.assertEquals(locs3.get(1), chr4); +// } +// +// @Test(expectedExceptions=UserException.BadArgumentValue.class) +// public void testSplitFixedIntervalsMoreFiles() { +// List files = testFiles("more.", 3, ".intervals"); +// List locs = getLocs("chr1", "chr2"); +// IntervalUtils.splitFixedIntervals(locs, files.size()); +// } +// +// @Test(expectedExceptions=UserException.BadArgumentValue.class) +// public void testScatterFixedIntervalsMoreFiles() { +// List files = testFiles("more.", 3, ".intervals"); +// List locs = getLocs("chr1", "chr2"); +// List splits = IntervalUtils.splitFixedIntervals(locs, locs.size()); // locs.size() instead of files.size() +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// } +// @Test +// public void testScatterFixedIntervalsStart() { +// List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); +// GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); +// GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); +// +// List files = testFiles("split.", 3, ".intervals"); +// +// List locs = getLocs(intervals); +// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 2); +// +// Assert.assertEquals(locs1.get(0), chr1a); +// Assert.assertEquals(locs2.get(0), chr1b); +// Assert.assertEquals(locs3.get(0), chr2); +// Assert.assertEquals(locs3.get(1), chr3); +// } +// +// @Test +// public void testScatterFixedIntervalsMiddle() { +// List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); +// GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); +// GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); +// +// List files = testFiles("split.", 3, ".intervals"); +// +// List locs = getLocs(intervals); +// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 2); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2a); +// Assert.assertEquals(locs3.get(0), chr2b); +// Assert.assertEquals(locs3.get(1), chr3); +// } +// +// @Test +// public void testScatterFixedIntervalsEnd() { +// List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); +// GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); +// GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); +// +// List files = testFiles("split.", 3, ".intervals"); +// +// List locs = getLocs(intervals); +// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 2); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 1); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs1.get(1), chr2); +// Assert.assertEquals(locs2.get(0), chr3a); +// Assert.assertEquals(locs3.get(0), chr3b); +// } +// +// @Test +// public void testScatterFixedIntervalsFile() { +// List files = testFiles("sg.", 20, ".intervals"); +// List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(BaseTest.GATKDataLocation + "whole_exome_agilent_designed_120.targets.hg18.chr20.interval_list"), false); +// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); +// +// int[] counts = { +// 125, 138, 287, 291, 312, 105, 155, 324, +// 295, 298, 141, 121, 285, 302, 282, 88, +// 116, 274, 282, 248 +//// 5169, 5573, 10017, 10567, 10551, +//// 5087, 4908, 10120, 10435, 10399, +//// 5391, 4735, 10621, 10352, 10654, +//// 5227, 5256, 10151, 9649, 9825 +// }; +// +// //String splitCounts = ""; +// for (int lastIndex = 0, i = 0; i < splits.size(); i++) { +// int splitIndex = splits.get(i); +// int splitCount = (splitIndex - lastIndex); +// //splitCounts += ", " + splitCount; +// lastIndex = splitIndex; +// Assert.assertEquals(splitCount, counts[i], "Num intervals in split " + i); +// } +// //System.out.println(splitCounts.substring(2)); +// +// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); +// +// int locIndex = 0; +// for (int i = 0; i < files.size(); i++) { +// String file = files.get(i).toString(); +// List parsedLocs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(file), false); +// Assert.assertEquals(parsedLocs.size(), counts[i], "Intervals in " + file); +// for (GenomeLoc parsedLoc: parsedLocs) +// Assert.assertEquals(parsedLoc, locs.get(locIndex), String.format("Genome loc %d from file %d", locIndex++, i)); +// } +// Assert.assertEquals(locIndex, locs.size(), "Total number of GenomeLocs"); +// } +// +// @Test +// public void testScatterFixedIntervalsMax() { +// List files = testFiles("sg.", 85, ".intervals"); +// List splits = IntervalUtils.splitFixedIntervals(hg19ReferenceLocs, files.size()); +// IntervalUtils.scatterFixedIntervals(hg19Header, hg19ReferenceLocs, splits, files); +// +// for (int i = 0; i < files.size(); i++) { +// String file = files.get(i).toString(); +// List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); +// Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); +// Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); +// } +// } +// +// @Test +// public void testScatterContigIntervalsOrder() { +// List intervals = Arrays.asList("chr2:1-1", "chr1:1-1", "chr3:2-2"); +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); +// +// List files = testFiles("split.", 3, ".intervals"); +// +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 1); +// +// Assert.assertEquals(locs1.get(0), chr2); +// Assert.assertEquals(locs2.get(0), chr1); +// Assert.assertEquals(locs3.get(0), chr3); +// } +// +// @Test +// public void testScatterContigIntervalsBasic() { +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); +// +// List files = testFiles("contig_basic.", 3, ".intervals"); +// +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3"), files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 1); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2); +// Assert.assertEquals(locs3.get(0), chr3); +// } +// +// @Test +// public void testScatterContigIntervalsLessFiles() { +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); +// GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); +// +// List files = testFiles("contig_less.", 3, ".intervals"); +// +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3", "chr4"), files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 2); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2); +// Assert.assertEquals(locs3.get(0), chr3); +// Assert.assertEquals(locs3.get(1), chr4); +// } +// +// @Test(expectedExceptions=UserException.BadArgumentValue.class) +// public void testScatterContigIntervalsMoreFiles() { +// List files = testFiles("contig_more.", 3, ".intervals"); +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2"), files); +// } +// +// @Test +// public void testScatterContigIntervalsStart() { +// List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); +// GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); +// GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); +// +// List files = testFiles("contig_split_start.", 3, ".intervals"); +// +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 2); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 1); +// +// Assert.assertEquals(locs1.get(0), chr1a); +// Assert.assertEquals(locs1.get(1), chr1b); +// Assert.assertEquals(locs2.get(0), chr2); +// Assert.assertEquals(locs3.get(0), chr3); +// } +// +// @Test +// public void testScatterContigIntervalsMiddle() { +// List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); +// GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); +// GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); +// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); +// +// List files = testFiles("contig_split_middle.", 3, ".intervals"); +// +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 2); +// Assert.assertEquals(locs3.size(), 1); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2a); +// Assert.assertEquals(locs2.get(1), chr2b); +// Assert.assertEquals(locs3.get(0), chr3); +// } +// +// @Test +// public void testScatterContigIntervalsEnd() { +// List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); +// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); +// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); +// GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); +// GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); +// +// List files = testFiles("contig_split_end.", 3 ,".intervals"); +// +// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); +// +// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); +// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); +// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); +// +// Assert.assertEquals(locs1.size(), 1); +// Assert.assertEquals(locs2.size(), 1); +// Assert.assertEquals(locs3.size(), 2); +// +// Assert.assertEquals(locs1.get(0), chr1); +// Assert.assertEquals(locs2.get(0), chr2); +// Assert.assertEquals(locs3.get(0), chr3a); +// Assert.assertEquals(locs3.get(1), chr3b); +// } +// +// @Test +// public void testScatterContigIntervalsMax() { +// List files = testFiles("sg.", 85, ".intervals"); +// IntervalUtils.scatterContigIntervals(hg19Header, hg19ReferenceLocs, files); +// +// for (int i = 0; i < files.size(); i++) { +// String file = files.get(i).toString(); +// List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); +// Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); +// Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); +// } +// } +// +// private List testFiles(String prefix, int count, String suffix) { +// ArrayList files = new ArrayList(); +// for (int i = 1; i <= count; i++) { +// files.add(createTempFile(prefix + i, suffix)); +// } +// return files; +// } +// +// @DataProvider(name="unmergedIntervals") +// public Object[][] getUnmergedIntervals() { +// return new Object[][] { +// new Object[] {"small_unmerged_picard_intervals.list"}, +// new Object[] {"small_unmerged_gatk_intervals.list"} +// }; +// } +// +// @Test(dataProvider="unmergedIntervals") +// public void testUnmergedIntervals(String unmergedIntervals) { +// List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Collections.singletonList(validationDataLocation + unmergedIntervals), false); +// Assert.assertEquals(locs.size(), 2); +// +// List merged = IntervalUtils.mergeIntervalLocations(locs, IntervalMergingRule.ALL); +// Assert.assertEquals(merged.size(), 1); +// } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervals.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervals.scala index aae5e438c3..0fb997f43e 100755 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervals.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervals.scala @@ -1,65 +1,65 @@ -/* - * 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 java.io.File -import collection.JavaConversions._ -import org.broadinstitute.sting.utils.interval.IntervalUtils -import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource -import net.sf.samtools.SAMFileHeader -import java.util.Collections -import org.broadinstitute.sting.utils.{GenomeLoc, GenomeLocSortedSet, GenomeLocParser} - -case class GATKIntervals(reference: File, intervals: List[String]) { - private lazy val referenceDataSource = new ReferenceDataSource(reference) - private var splitsBySize = Map.empty[Int, java.util.List[java.lang.Integer]] - - lazy val samFileHeader = { - val header = new SAMFileHeader - header.setSequenceDictionary(referenceDataSource.getReference.getSequenceDictionary) - header - } - - lazy val locs: java.util.List[GenomeLoc] = { - val parser = new GenomeLocParser(referenceDataSource.getReference) - val parsedLocs = - if (intervals.isEmpty) - GenomeLocSortedSet.createSetFromSequenceDictionary(samFileHeader.getSequenceDictionary).toList - else - IntervalUtils.parseIntervalArguments(parser, intervals, false) - Collections.sort(parsedLocs) - Collections.unmodifiableList(parsedLocs) - } - - lazy val contigs = locs.map(_.getContig).distinct.toList - - def getSplits(size: Int) = { - splitsBySize.getOrElse(size, { - val splits: java.util.List[java.lang.Integer] = IntervalUtils.splitFixedIntervals(locs, size) - splitsBySize += size -> splits - splits - }) - } -} +/* + * 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 java.io.File +import collection.JavaConversions._ +import org.broadinstitute.sting.utils.interval.IntervalUtils +import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource +import net.sf.samtools.SAMFileHeader +import java.util.Collections +import org.broadinstitute.sting.utils.{GenomeLoc, GenomeLocSortedSet, GenomeLocParser} + +case class GATKIntervals(reference: File, intervals: List[String]) { + private lazy val referenceDataSource = new ReferenceDataSource(reference) +// private var splitsBySize = Map.empty[Int, java.util.List[java.lang.Integer]] + + lazy val samFileHeader = { + val header = new SAMFileHeader + header.setSequenceDictionary(referenceDataSource.getReference.getSequenceDictionary) + header + } + + lazy val locs: java.util.List[GenomeLoc] = { + val parser = new GenomeLocParser(referenceDataSource.getReference) + val parsedLocs = + if (intervals.isEmpty) + GenomeLocSortedSet.createSetFromSequenceDictionary(samFileHeader.getSequenceDictionary).toList + else + IntervalUtils.parseIntervalArguments(parser, intervals, false) + Collections.sort(parsedLocs) + Collections.unmodifiableList(parsedLocs) + } + + lazy val contigs = locs.map(_.getContig).distinct.toList + +// def getSplits(size: Int) = { +// splitsBySize.getOrElse(size, { +// val splits: java.util.List[java.lang.Integer] = IntervalUtils.splitFixedIntervals(locs, size) +// splitsBySize += size -> splits +// splits +// }) +// } +} diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/IntervalScatterFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/IntervalScatterFunction.scala index d88d272b90..f65d5ab29c 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/IntervalScatterFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/IntervalScatterFunction.scala @@ -37,7 +37,7 @@ class IntervalScatterFunction extends GATKScatterFunction with InProcessFunction def run() { val gi = GATKScatterFunction.getGATKIntervals(this.referenceSequence, this.intervals) - IntervalUtils.scatterFixedIntervals(gi.samFileHeader, gi.locs, - gi.getSplits(this.scatterOutputFiles.size), this.scatterOutputFiles) + val splits = IntervalUtils.splitFixedIntervals(gi.locs, this.scatterOutputFiles.size) + IntervalUtils.scatterFixedIntervals(gi.samFileHeader, splits, this.scatterOutputFiles) } } diff --git a/public/scala/test/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervalsUnitTest.scala b/public/scala/test/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervalsUnitTest.scala index b3a2d23ae6..38abe24efb 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervalsUnitTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/extensions/gatk/GATKIntervalsUnitTest.scala @@ -53,8 +53,8 @@ class GATKIntervalsUnitTest { val gi = new GATKIntervals(hg18Reference, List("chr1:1-1", "chr2:2-3", "chr3:3-5")) Assert.assertEquals(gi.locs.toList, List(chr1, chr2, chr3)) Assert.assertEquals(gi.contigs, List("chr1", "chr2", "chr3")) - Assert.assertEquals(gi.getSplits(2).toList, List(2, 3)) - Assert.assertEquals(gi.getSplits(3).toList, List(1, 2, 3)) +// Assert.assertEquals(gi.getSplits(2).toList, List(2, 3)) +// Assert.assertEquals(gi.getSplits(3).toList, List(1, 2, 3)) } @Test(timeOut = 30000) @@ -65,7 +65,7 @@ class GATKIntervalsUnitTest { // for(Item item: javaConvertedScalaList) // This for loop is actually an O(N^2) operation as the iterator calls the // O(N) javaConvertedScalaList.size() for each iteration of the loop. - Assert.assertEquals(gi.getSplits(gi.locs.size).size, 189894) + //Assert.assertEquals(gi.getSplits(gi.locs.size).size, 189894) Assert.assertEquals(gi.contigs.size, 24) } @@ -74,8 +74,8 @@ class GATKIntervalsUnitTest { val gi = new GATKIntervals(hg18Reference, Nil) Assert.assertEquals(gi.locs, hg18ReferenceLocs) Assert.assertEquals(gi.contigs.size, hg18ReferenceLocs.size) - Assert.assertEquals(gi.getSplits(2).toList, List(10, 45)) - Assert.assertEquals(gi.getSplits(4).toList, List(5, 10, 16, 45)) +// Assert.assertEquals(gi.getSplits(2).toList, List(10, 45)) +// Assert.assertEquals(gi.getSplits(4).toList, List(5, 10, 16, 45)) } @Test From 87dc5cfb24a8065a07f0fdec3d09a0943210e4ae Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 9 Sep 2011 14:23:13 -0400 Subject: [PATCH 015/363] Whitespace cleanup --- public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java index b969235895..b661987136 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java @@ -306,7 +306,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return (int)( start << 16 + stop << 4 + contigIndex ); + return start << 16 | stop << 4 | contigIndex; } From c6436ee5f0f3359912e8210f99828a33680c745c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 9 Sep 2011 14:24:29 -0400 Subject: [PATCH 016/363] Whitespace cleanup --- public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java | 1 + 1 file changed, 1 insertion(+) diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java index b661987136..ba49191753 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java @@ -307,6 +307,7 @@ public boolean equals(Object other) { @Override public int hashCode() { return start << 16 | stop << 4 | contigIndex; + } From 3c8445b934c127581919d6be960ebc372be21342 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 9 Sep 2011 14:25:37 -0400 Subject: [PATCH 017/363] Performance bugfix for GenomeLoc.hashcode -- old version overflowed so most GenomeLocs had 0 hashcode. Now uses or not plus to combine --- public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java | 1 - 1 file changed, 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java index ba49191753..b661987136 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLoc.java @@ -307,7 +307,6 @@ public boolean equals(Object other) { @Override public int hashCode() { return start << 16 | stop << 4 | contigIndex; - } From 72536e5d6db56f495d560f1bcf2536c6896a49c0 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 9 Sep 2011 15:44:47 -0400 Subject: [PATCH 018/363] Done --- build.xml | 4 +- .../sting/utils/interval/IntervalUtils.java | 72 +- .../utils/interval/IntervalUtilsUnitTest.java | 1004 +++++++++-------- 3 files changed, 525 insertions(+), 555 deletions(-) diff --git a/build.xml b/build.xml index beca6bce0f..efefdd438e 100644 --- a/build.xml +++ b/build.xml @@ -855,8 +855,8 @@ - - + + 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 41cbbe59fa..2cfcc19a9d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java @@ -333,28 +333,6 @@ public static void scatterContigIntervals(SAMFileHeader fileHeader, List> splitIntervalsToSubLists(List locs, List splits) { - int locIndex = 1; - int start = 0; - List> sublists = new ArrayList>(splits.size()); - for (Integer stop: splits) { - List curList = new ArrayList(); - for (int i = start; i < stop; i++) - curList.add(locs.get(i)); - start = stop; - sublists.add(curList); - } - - return sublists; - } - - /** * Splits an interval list into multiple files. * @param fileHeader The sam file header. @@ -384,39 +362,27 @@ public static void scatterFixedIntervals(SAMFileHeader fileHeader, List> splitFixedIntervals(List locs, int numParts) { if (locs.size() < numParts) throw new UserException.BadArgumentValue("scatterParts", String.format("Cannot scatter %d locs into %d parts.", locs.size(), numParts)); - final long locsSize = intervalSize(locs); - final List splitPoints = new ArrayList(); - addFixedSplit(splitPoints, locs, locsSize, 0, locs.size(), numParts); - Collections.sort(splitPoints); - splitPoints.add(locs.size()); - return splitIntervalsToSubLists(locs, splitPoints); - } - - private static void addFixedSplit(List splitPoints, List locs, long locsSize, int startIndex, int stopIndex, int numParts) { - if (numParts < 2) - return; - int halfParts = (numParts + 1) / 2; - Pair splitPoint = getFixedSplit(locs, locsSize, startIndex, stopIndex, halfParts, numParts - halfParts); - int splitIndex = splitPoint.first; - long splitSize = splitPoint.second; - splitPoints.add(splitIndex); - addFixedSplit(splitPoints, locs, splitSize, startIndex, splitIndex, halfParts); - addFixedSplit(splitPoints, locs, locsSize - splitSize, splitIndex, stopIndex, numParts - halfParts); - } - private static Pair getFixedSplit(List locs, long locsSize, int startIndex, int stopIndex, int minLocs, int maxLocs) { - int splitIndex = startIndex; - long splitSize = 0; - for (int i = 0; i < minLocs; i++) { - splitSize += locs.get(splitIndex).size(); - splitIndex++; - } - long halfSize = locsSize / 2; - while (splitIndex < (stopIndex - maxLocs) && splitSize < halfSize) { - splitSize += locs.get(splitIndex).size(); - splitIndex++; + final long locsSize = intervalSize(locs); + final double idealSplitSize = locsSize / numParts; + final List> splits = new ArrayList>(numParts); + final LinkedList remainingLocs = new LinkedList(locs); + + for ( int i = 0; i < numParts; i++ ) { + long splitSize = 0; + List split = new ArrayList(); + while ( ! remainingLocs.isEmpty() ) { + final GenomeLoc toAdd = remainingLocs.pop(); + splitSize += toAdd.size(); + split.add(toAdd); + final long nextEltSize = remainingLocs.isEmpty() ? 0 : remainingLocs.peek().size(); + if ( splitSize + (i % 2 == 0 ? 0 : nextEltSize) > idealSplitSize ) + break; + } + splits.add(split); } - return new Pair(splitIndex, splitSize); + + return splits; } /** 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 bd6bf9591a..4809f1b5c8 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java @@ -1,6 +1,7 @@ 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.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; @@ -99,19 +100,26 @@ public String toString() { @DataProvider(name = "intervalslicingdata") public Object[][] createTrees() { -// new IntervalSlicingTest(1, 0); -// new IntervalSlicingTest(2, 0.1); - new IntervalSlicingTest(5, 0.1); -// new IntervalSlicingTest(10, 0.1); -// new IntervalSlicingTest(67, 0.1); -// new IntervalSlicingTest(100, 0.1); -// new IntervalSlicingTest(500, 0.1); -// new IntervalSlicingTest(1000, 0.1); + new IntervalSlicingTest(1, 0); + new IntervalSlicingTest(2, 0.1); + new IntervalSlicingTest(3, 0.1); + new IntervalSlicingTest(7, 0.1); + new IntervalSlicingTest(10, 0.1); + new IntervalSlicingTest(31, 0.1); + new IntervalSlicingTest(67, 0.1); + new IntervalSlicingTest(100, 0.1); + new IntervalSlicingTest(127, 0.1); + // starts to become a bit less efficiency with larger cuts + new IntervalSlicingTest(500, 0.5); + new IntervalSlicingTest(1000, 1); + new IntervalSlicingTest(10000, 10); return IntervalSlicingTest.getTests(IntervalSlicingTest.class); } @Test(dataProvider = "intervalslicingdata") public void testFixedScatterIntervalsAlgorithm(IntervalSlicingTest test) { + Set locsSet = new HashSet(hg19exomeIntervals); + Set notFoundSet = new HashSet(hg19exomeIntervals); List> splits = IntervalUtils.splitFixedIntervals(hg19exomeIntervals, test.parts); long totalSize = IntervalUtils.intervalSize(hg19exomeIntervals); @@ -122,501 +130,497 @@ public void testFixedScatterIntervalsAlgorithm(IntervalSlicingTest test) { for ( final List split : splits ) { long splitSize = IntervalUtils.intervalSize(split); double sigma = (splitSize - idealSplitSize) / (1.0 * idealSplitSize); - logger.warn(String.format("Split %d size %d ideal %d sigma %.2f", counter, splitSize, idealSplitSize, sigma)); + //logger.warn(String.format("Split %d size %d ideal %d sigma %.2f", counter, splitSize, idealSplitSize, sigma)); counter++; sumOfSplitSizes += splitSize; Assert.assertTrue(Math.abs(sigma) <= test.maxAllowableVariance, String.format("Interval %d (size %d ideal %d) has a variance %.2f outside of the tolerated range %.2f", counter, splitSize, idealSplitSize, sigma, test.maxAllowableVariance)); + + for ( final GenomeLoc loc : split ) { + Assert.assertTrue(locsSet.contains(loc), "Split location " + loc + " not found in set of input locs"); + notFoundSet.remove(loc); + } + } + + Assert.assertEquals(sumOfSplitSizes, totalSize, "Split intervals don't contain the exact number of bases in the original intervals"); + Assert.assertTrue(notFoundSet.isEmpty(), "Not all intervals were present in the split set"); + } + + @Test(expectedExceptions=UserException.class) + public void testMergeListsBySetOperatorNoOverlap() { + // a couple of lists we'll use for the testing + List listEveryTwoFromOne = new ArrayList(); + List listEveryTwoFromTwo = new ArrayList(); + + // create the two lists we'll use + for (int x = 1; x < 101; x++) { + if (x % 2 == 0) + listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); + else + listEveryTwoFromOne.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); + } + + List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.UNION); + Assert.assertEquals(ret.size(), 100); + ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.INTERSECTION); + Assert.assertEquals(ret.size(), 0); + } + + @Test + public void testMergeListsBySetOperatorAllOverlap() { + // a couple of lists we'll use for the testing + List allSites = new ArrayList(); + List listEveryTwoFromTwo = new ArrayList(); + + // create the two lists we'll use + for (int x = 1; x < 101; x++) { + if (x % 2 == 0) + listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); + allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); + } + + List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); + Assert.assertEquals(ret.size(), 150); + ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); + Assert.assertEquals(ret.size(), 50); + } + + @Test + public void testMergeListsBySetOperator() { + // a couple of lists we'll use for the testing + List allSites = new ArrayList(); + List listEveryTwoFromTwo = new ArrayList(); + + // create the two lists we'll use + for (int x = 1; x < 101; x++) { + if (x % 5 == 0) { + listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); + allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); + } + } + + List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); + Assert.assertEquals(ret.size(), 40); + ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); + Assert.assertEquals(ret.size(), 20); + } + + @Test + public void testGetContigLengths() { + Map lengths = IntervalUtils.getContigSizes(new File(BaseTest.hg18Reference)); + Assert.assertEquals((long)lengths.get("chr1"), 247249719); + Assert.assertEquals((long)lengths.get("chr2"), 242951149); + Assert.assertEquals((long)lengths.get("chr3"), 199501827); + Assert.assertEquals((long)lengths.get("chr20"), 62435964); + Assert.assertEquals((long)lengths.get("chrX"), 154913754); + } + + @Test + public void testParseIntervalArguments() { + Assert.assertEquals(getLocs().size(), 45); + Assert.assertEquals(getLocs("chr1", "chr2", "chr3").size(), 3); + Assert.assertEquals(getLocs("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2").size(), 4); + } + + @Test + public void testIsIntervalFile() { + Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list")); + Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list", true)); + + List extensions = Arrays.asList("bed", "interval_list", "intervals", "list", "picard"); + for (String extension: extensions) { + Assert.assertTrue(IntervalUtils.isIntervalFile("test_intervals." + extension, false), "Tested interval file extension: " + extension); } + } + + @Test(expectedExceptions = UserException.CouldNotReadInputFile.class) + public void testMissingIntervalFile() { + IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "no_such_intervals.list"); + } + + @Test + public void testFixedScatterIntervalsBasic() { + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); + + List files = testFiles("basic.", 3, ".intervals"); + + List locs = getLocs("chr1", "chr2", "chr3"); + IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs2.get(0), chr2); + Assert.assertEquals(locs3.get(0), chr3); + } + + @Test + public void testScatterFixedIntervalsLessFiles() { + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); + GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); + + List files = testFiles("less.", 3, ".intervals"); + + List locs = getLocs("chr1", "chr2", "chr3", "chr4"); + IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 2); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs1.get(1), chr2); + Assert.assertEquals(locs2.get(0), chr3); + Assert.assertEquals(locs3.get(0), chr4); + } + + @Test(expectedExceptions=UserException.BadArgumentValue.class) + public void testSplitFixedIntervalsMoreFiles() { + List files = testFiles("more.", 3, ".intervals"); + List locs = getLocs("chr1", "chr2"); + IntervalUtils.splitFixedIntervals(locs, files.size()); + } + + @Test(expectedExceptions=UserException.BadArgumentValue.class) + public void testScatterFixedIntervalsMoreFiles() { + List files = testFiles("more.", 3, ".intervals"); + List locs = getLocs("chr1", "chr2"); + IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, locs.size()), files); + } + @Test + public void testScatterFixedIntervalsStart() { + List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); + GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); + GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); + + List files = testFiles("split.", 3, ".intervals"); + + List locs = getLocs(intervals); + IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 2); + + Assert.assertEquals(locs1.get(0), chr1a); + Assert.assertEquals(locs2.get(0), chr1b); + Assert.assertEquals(locs3.get(0), chr2); + Assert.assertEquals(locs3.get(1), chr3); + } + + @Test + public void testScatterFixedIntervalsMiddle() { + List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); + GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); + GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); + + List files = testFiles("split.", 3, ".intervals"); + + List locs = getLocs(intervals); + IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 2); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs2.get(0), chr2a); + Assert.assertEquals(locs3.get(0), chr2b); + Assert.assertEquals(locs3.get(1), chr3); + } + + @Test + public void testScatterFixedIntervalsEnd() { + List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); + GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); + GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); + + List files = testFiles("split.", 3, ".intervals"); + + List locs = getLocs(intervals); + IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 2); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs1.get(1), chr2); + Assert.assertEquals(locs2.get(0), chr3a); + Assert.assertEquals(locs3.get(0), chr3b); + } + + @Test + public void testScatterFixedIntervalsFile() { + List files = testFiles("sg.", 20, ".intervals"); + List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(BaseTest.GATKDataLocation + "whole_exome_agilent_designed_120.targets.hg18.chr20.interval_list"), false); + List> splits = IntervalUtils.splitFixedIntervals(locs, files.size()); - Assert.assertEquals(totalSize, sumOfSplitSizes, "Split intervals don't contain the exact number of bases in the origianl intervals"); - } - -// @Test(expectedExceptions=UserException.class) -// public void testMergeListsBySetOperatorNoOverlap() { -// // a couple of lists we'll use for the testing -// List listEveryTwoFromOne = new ArrayList(); -// List listEveryTwoFromTwo = new ArrayList(); -// -// // create the two lists we'll use -// for (int x = 1; x < 101; x++) { -// if (x % 2 == 0) -// listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); -// else -// listEveryTwoFromOne.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); -// } -// -// List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.UNION); -// Assert.assertEquals(ret.size(), 100); -// ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, listEveryTwoFromOne, IntervalSetRule.INTERSECTION); -// Assert.assertEquals(ret.size(), 0); -// } -// -// @Test -// public void testMergeListsBySetOperatorAllOverlap() { -// // a couple of lists we'll use for the testing -// List allSites = new ArrayList(); -// List listEveryTwoFromTwo = new ArrayList(); -// -// // create the two lists we'll use -// for (int x = 1; x < 101; x++) { -// if (x % 2 == 0) -// listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); -// allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); -// } -// -// List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); -// Assert.assertEquals(ret.size(), 150); -// ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); -// Assert.assertEquals(ret.size(), 50); -// } -// -// @Test -// public void testMergeListsBySetOperator() { -// // a couple of lists we'll use for the testing -// List allSites = new ArrayList(); -// List listEveryTwoFromTwo = new ArrayList(); -// -// // create the two lists we'll use -// for (int x = 1; x < 101; x++) { -// if (x % 5 == 0) { -// listEveryTwoFromTwo.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); -// allSites.add(hg18GenomeLocParser.createGenomeLoc("chr1",x,x)); -// } -// } -// -// List ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.UNION); -// Assert.assertEquals(ret.size(), 40); -// ret = IntervalUtils.mergeListsBySetOperator(listEveryTwoFromTwo, allSites, IntervalSetRule.INTERSECTION); -// Assert.assertEquals(ret.size(), 20); -// } -// -// @Test -// public void testGetContigLengths() { -// Map lengths = IntervalUtils.getContigSizes(new File(BaseTest.hg18Reference)); -// Assert.assertEquals((long)lengths.get("chr1"), 247249719); -// Assert.assertEquals((long)lengths.get("chr2"), 242951149); -// Assert.assertEquals((long)lengths.get("chr3"), 199501827); -// Assert.assertEquals((long)lengths.get("chr20"), 62435964); -// Assert.assertEquals((long)lengths.get("chrX"), 154913754); -// } -// -// @Test -// public void testParseIntervalArguments() { -// Assert.assertEquals(getLocs().size(), 45); -// Assert.assertEquals(getLocs("chr1", "chr2", "chr3").size(), 3); -// Assert.assertEquals(getLocs("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2").size(), 4); -// } -// -// @Test -// public void testIsIntervalFile() { -// Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list")); -// Assert.assertTrue(IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "empty_intervals.list", true)); -// -// List extensions = Arrays.asList("bed", "interval_list", "intervals", "list", "picard"); -// for (String extension: extensions) { -// Assert.assertTrue(IntervalUtils.isIntervalFile("test_intervals." + extension, false), "Tested interval file extension: " + extension); -// } -// } -// -// @Test(expectedExceptions = UserException.CouldNotReadInputFile.class) -// public void testMissingIntervalFile() { -// IntervalUtils.isIntervalFile(BaseTest.validationDataLocation + "no_such_intervals.list"); -// } -// -// @Test -// public void testFixedScatterIntervalsBasic() { -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); -// -// List files = testFiles("basic.", 3, ".intervals"); -// -// List locs = getLocs("chr1", "chr2", "chr3"); -// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 1); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2); -// Assert.assertEquals(locs3.get(0), chr3); -// } -// -// @Test -// public void testScatterFixedIntervalsLessFiles() { -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); -// GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); -// -// List files = testFiles("less.", 3, ".intervals"); -// -// List locs = getLocs("chr1", "chr2", "chr3", "chr4"); -// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 2); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2); -// Assert.assertEquals(locs3.get(0), chr3); -// Assert.assertEquals(locs3.get(1), chr4); -// } -// -// @Test(expectedExceptions=UserException.BadArgumentValue.class) -// public void testSplitFixedIntervalsMoreFiles() { -// List files = testFiles("more.", 3, ".intervals"); -// List locs = getLocs("chr1", "chr2"); -// IntervalUtils.splitFixedIntervals(locs, files.size()); -// } -// -// @Test(expectedExceptions=UserException.BadArgumentValue.class) -// public void testScatterFixedIntervalsMoreFiles() { -// List files = testFiles("more.", 3, ".intervals"); -// List locs = getLocs("chr1", "chr2"); -// List splits = IntervalUtils.splitFixedIntervals(locs, locs.size()); // locs.size() instead of files.size() -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// } -// @Test -// public void testScatterFixedIntervalsStart() { -// List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); -// GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); -// GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); -// -// List files = testFiles("split.", 3, ".intervals"); -// -// List locs = getLocs(intervals); -// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 2); -// -// Assert.assertEquals(locs1.get(0), chr1a); -// Assert.assertEquals(locs2.get(0), chr1b); -// Assert.assertEquals(locs3.get(0), chr2); -// Assert.assertEquals(locs3.get(1), chr3); -// } -// -// @Test -// public void testScatterFixedIntervalsMiddle() { -// List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); -// GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); -// GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); -// -// List files = testFiles("split.", 3, ".intervals"); -// -// List locs = getLocs(intervals); -// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 2); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2a); -// Assert.assertEquals(locs3.get(0), chr2b); -// Assert.assertEquals(locs3.get(1), chr3); -// } -// -// @Test -// public void testScatterFixedIntervalsEnd() { -// List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); -// GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); -// GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); -// -// List files = testFiles("split.", 3, ".intervals"); -// -// List locs = getLocs(intervals); -// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 2); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 1); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs1.get(1), chr2); -// Assert.assertEquals(locs2.get(0), chr3a); -// Assert.assertEquals(locs3.get(0), chr3b); -// } -// -// @Test -// public void testScatterFixedIntervalsFile() { -// List files = testFiles("sg.", 20, ".intervals"); -// List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(BaseTest.GATKDataLocation + "whole_exome_agilent_designed_120.targets.hg18.chr20.interval_list"), false); -// List splits = IntervalUtils.splitFixedIntervals(locs, files.size()); -// -// int[] counts = { -// 125, 138, 287, 291, 312, 105, 155, 324, -// 295, 298, 141, 121, 285, 302, 282, 88, -// 116, 274, 282, 248 -//// 5169, 5573, 10017, 10567, 10551, -//// 5087, 4908, 10120, 10435, 10399, -//// 5391, 4735, 10621, 10352, 10654, -//// 5227, 5256, 10151, 9649, 9825 -// }; -// -// //String splitCounts = ""; -// for (int lastIndex = 0, i = 0; i < splits.size(); i++) { -// int splitIndex = splits.get(i); -// int splitCount = (splitIndex - lastIndex); -// //splitCounts += ", " + splitCount; -// lastIndex = splitIndex; -// Assert.assertEquals(splitCount, counts[i], "Num intervals in split " + i); -// } -// //System.out.println(splitCounts.substring(2)); -// -// IntervalUtils.scatterFixedIntervals(hg18Header, locs, splits, files); -// -// int locIndex = 0; -// for (int i = 0; i < files.size(); i++) { -// String file = files.get(i).toString(); -// List parsedLocs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(file), false); -// Assert.assertEquals(parsedLocs.size(), counts[i], "Intervals in " + file); -// for (GenomeLoc parsedLoc: parsedLocs) -// Assert.assertEquals(parsedLoc, locs.get(locIndex), String.format("Genome loc %d from file %d", locIndex++, i)); -// } -// Assert.assertEquals(locIndex, locs.size(), "Total number of GenomeLocs"); -// } -// -// @Test -// public void testScatterFixedIntervalsMax() { -// List files = testFiles("sg.", 85, ".intervals"); -// List splits = IntervalUtils.splitFixedIntervals(hg19ReferenceLocs, files.size()); -// IntervalUtils.scatterFixedIntervals(hg19Header, hg19ReferenceLocs, splits, files); -// -// for (int i = 0; i < files.size(); i++) { -// String file = files.get(i).toString(); -// List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); -// Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); -// Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); -// } -// } -// -// @Test -// public void testScatterContigIntervalsOrder() { -// List intervals = Arrays.asList("chr2:1-1", "chr1:1-1", "chr3:2-2"); -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); -// -// List files = testFiles("split.", 3, ".intervals"); -// -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 1); -// -// Assert.assertEquals(locs1.get(0), chr2); -// Assert.assertEquals(locs2.get(0), chr1); -// Assert.assertEquals(locs3.get(0), chr3); -// } -// -// @Test -// public void testScatterContigIntervalsBasic() { -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); -// -// List files = testFiles("contig_basic.", 3, ".intervals"); -// -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3"), files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 1); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2); -// Assert.assertEquals(locs3.get(0), chr3); -// } -// -// @Test -// public void testScatterContigIntervalsLessFiles() { -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); -// GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); -// -// List files = testFiles("contig_less.", 3, ".intervals"); -// -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3", "chr4"), files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 2); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2); -// Assert.assertEquals(locs3.get(0), chr3); -// Assert.assertEquals(locs3.get(1), chr4); -// } -// -// @Test(expectedExceptions=UserException.BadArgumentValue.class) -// public void testScatterContigIntervalsMoreFiles() { -// List files = testFiles("contig_more.", 3, ".intervals"); -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2"), files); -// } -// -// @Test -// public void testScatterContigIntervalsStart() { -// List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); -// GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); -// GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); -// -// List files = testFiles("contig_split_start.", 3, ".intervals"); -// -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 2); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 1); -// -// Assert.assertEquals(locs1.get(0), chr1a); -// Assert.assertEquals(locs1.get(1), chr1b); -// Assert.assertEquals(locs2.get(0), chr2); -// Assert.assertEquals(locs3.get(0), chr3); -// } -// -// @Test -// public void testScatterContigIntervalsMiddle() { -// List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); -// GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); -// GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); -// GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); -// -// List files = testFiles("contig_split_middle.", 3, ".intervals"); -// -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 2); -// Assert.assertEquals(locs3.size(), 1); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2a); -// Assert.assertEquals(locs2.get(1), chr2b); -// Assert.assertEquals(locs3.get(0), chr3); -// } -// -// @Test -// public void testScatterContigIntervalsEnd() { -// List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); -// GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); -// GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); -// GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); -// GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); -// -// List files = testFiles("contig_split_end.", 3 ,".intervals"); -// -// IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); -// -// List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); -// List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); -// List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); -// -// Assert.assertEquals(locs1.size(), 1); -// Assert.assertEquals(locs2.size(), 1); -// Assert.assertEquals(locs3.size(), 2); -// -// Assert.assertEquals(locs1.get(0), chr1); -// Assert.assertEquals(locs2.get(0), chr2); -// Assert.assertEquals(locs3.get(0), chr3a); -// Assert.assertEquals(locs3.get(1), chr3b); -// } -// -// @Test -// public void testScatterContigIntervalsMax() { -// List files = testFiles("sg.", 85, ".intervals"); -// IntervalUtils.scatterContigIntervals(hg19Header, hg19ReferenceLocs, files); -// -// for (int i = 0; i < files.size(); i++) { -// String file = files.get(i).toString(); -// List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); -// Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); -// Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); -// } -// } -// -// private List testFiles(String prefix, int count, String suffix) { -// ArrayList files = new ArrayList(); -// for (int i = 1; i <= count; i++) { -// files.add(createTempFile(prefix + i, suffix)); -// } -// return files; -// } -// -// @DataProvider(name="unmergedIntervals") -// public Object[][] getUnmergedIntervals() { -// return new Object[][] { -// new Object[] {"small_unmerged_picard_intervals.list"}, -// new Object[] {"small_unmerged_gatk_intervals.list"} -// }; -// } -// -// @Test(dataProvider="unmergedIntervals") -// public void testUnmergedIntervals(String unmergedIntervals) { -// List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Collections.singletonList(validationDataLocation + unmergedIntervals), false); -// Assert.assertEquals(locs.size(), 2); -// -// List merged = IntervalUtils.mergeIntervalLocations(locs, IntervalMergingRule.ALL); -// Assert.assertEquals(merged.size(), 1); -// } + int[] counts = { + 125, 138, 287, 291, 312, 105, 155, 324, + 295, 298, 141, 121, 285, 302, 282, 88, + 116, 274, 282, 248 +// 5169, 5573, 10017, 10567, 10551, +// 5087, 4908, 10120, 10435, 10399, +// 5391, 4735, 10621, 10352, 10654, +// 5227, 5256, 10151, 9649, 9825 + }; + + //String splitCounts = ""; + for (int i = 0; i < splits.size(); i++) { + long splitCount = splits.get(i).size(); + Assert.assertEquals(splitCount, counts[i], "Num intervals in split " + i); + } + //System.out.println(splitCounts.substring(2)); + + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); + + int locIndex = 0; + for (int i = 0; i < files.size(); i++) { + String file = files.get(i).toString(); + List parsedLocs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(file), false); + Assert.assertEquals(parsedLocs.size(), counts[i], "Intervals in " + file); + for (GenomeLoc parsedLoc: parsedLocs) + Assert.assertEquals(parsedLoc, locs.get(locIndex), String.format("Genome loc %d from file %d", locIndex++, i)); + } + Assert.assertEquals(locIndex, locs.size(), "Total number of GenomeLocs"); + } + + @Test + public void testScatterFixedIntervalsMax() { + List files = testFiles("sg.", 85, ".intervals"); + IntervalUtils.scatterFixedIntervals(hg19Header, IntervalUtils.splitFixedIntervals(hg19ReferenceLocs, files.size()), files); + + for (int i = 0; i < files.size(); i++) { + String file = files.get(i).toString(); + List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); + Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); + Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); + } + } + + @Test + public void testScatterContigIntervalsOrder() { + List intervals = Arrays.asList("chr2:1-1", "chr1:1-1", "chr3:2-2"); + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); + + List files = testFiles("split.", 3, ".intervals"); + + IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr2); + Assert.assertEquals(locs2.get(0), chr1); + Assert.assertEquals(locs3.get(0), chr3); + } + + @Test + public void testScatterContigIntervalsBasic() { + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); + + List files = testFiles("contig_basic.", 3, ".intervals"); + + IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3"), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs2.get(0), chr2); + Assert.assertEquals(locs3.get(0), chr3); + } + + @Test + public void testScatterContigIntervalsLessFiles() { + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3"); + GenomeLoc chr4 = hg18GenomeLocParser.parseGenomeLoc("chr4"); + + List files = testFiles("contig_less.", 3, ".intervals"); + + IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2", "chr3", "chr4"), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 2); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs2.get(0), chr2); + Assert.assertEquals(locs3.get(0), chr3); + Assert.assertEquals(locs3.get(1), chr4); + } + + @Test(expectedExceptions=UserException.BadArgumentValue.class) + public void testScatterContigIntervalsMoreFiles() { + List files = testFiles("contig_more.", 3, ".intervals"); + IntervalUtils.scatterContigIntervals(hg18Header, getLocs("chr1", "chr2"), files); + } + + @Test + public void testScatterContigIntervalsStart() { + List intervals = Arrays.asList("chr1:1-2", "chr1:4-5", "chr2:1-1", "chr3:2-2"); + GenomeLoc chr1a = hg18GenomeLocParser.parseGenomeLoc("chr1:1-2"); + GenomeLoc chr1b = hg18GenomeLocParser.parseGenomeLoc("chr1:4-5"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:1-1"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); + + List files = testFiles("contig_split_start.", 3, ".intervals"); + + IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 2); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr1a); + Assert.assertEquals(locs1.get(1), chr1b); + Assert.assertEquals(locs2.get(0), chr2); + Assert.assertEquals(locs3.get(0), chr3); + } + + @Test + public void testScatterContigIntervalsMiddle() { + List intervals = Arrays.asList("chr1:1-1", "chr2:1-2", "chr2:4-5", "chr3:2-2"); + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); + GenomeLoc chr2a = hg18GenomeLocParser.parseGenomeLoc("chr2:1-2"); + GenomeLoc chr2b = hg18GenomeLocParser.parseGenomeLoc("chr2:4-5"); + GenomeLoc chr3 = hg18GenomeLocParser.parseGenomeLoc("chr3:2-2"); + + List files = testFiles("contig_split_middle.", 3, ".intervals"); + + IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 2); + Assert.assertEquals(locs3.size(), 1); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs2.get(0), chr2a); + Assert.assertEquals(locs2.get(1), chr2b); + Assert.assertEquals(locs3.get(0), chr3); + } + + @Test + public void testScatterContigIntervalsEnd() { + List intervals = Arrays.asList("chr1:1-1", "chr2:2-2", "chr3:1-2", "chr3:4-5"); + GenomeLoc chr1 = hg18GenomeLocParser.parseGenomeLoc("chr1:1-1"); + GenomeLoc chr2 = hg18GenomeLocParser.parseGenomeLoc("chr2:2-2"); + GenomeLoc chr3a = hg18GenomeLocParser.parseGenomeLoc("chr3:1-2"); + GenomeLoc chr3b = hg18GenomeLocParser.parseGenomeLoc("chr3:4-5"); + + List files = testFiles("contig_split_end.", 3 ,".intervals"); + + IntervalUtils.scatterContigIntervals(hg18Header, getLocs(intervals), files); + + List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); + List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); + List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); + + Assert.assertEquals(locs1.size(), 1); + Assert.assertEquals(locs2.size(), 1); + Assert.assertEquals(locs3.size(), 2); + + Assert.assertEquals(locs1.get(0), chr1); + Assert.assertEquals(locs2.get(0), chr2); + Assert.assertEquals(locs3.get(0), chr3a); + Assert.assertEquals(locs3.get(1), chr3b); + } + + @Test + public void testScatterContigIntervalsMax() { + List files = testFiles("sg.", 85, ".intervals"); + IntervalUtils.scatterContigIntervals(hg19Header, hg19ReferenceLocs, files); + + for (int i = 0; i < files.size(); i++) { + String file = files.get(i).toString(); + List parsedLocs = IntervalUtils.parseIntervalArguments(hg19GenomeLocParser, Arrays.asList(file), false); + Assert.assertEquals(parsedLocs.size(), 1, "parsedLocs[" + i + "].size()"); + Assert.assertEquals(parsedLocs.get(0), hg19ReferenceLocs.get(i), "parsedLocs[" + i + "].get()"); + } + } + + private List testFiles(String prefix, int count, String suffix) { + ArrayList files = new ArrayList(); + for (int i = 1; i <= count; i++) { + files.add(createTempFile(prefix + i, suffix)); + } + return files; + } + + @DataProvider(name="unmergedIntervals") + public Object[][] getUnmergedIntervals() { + return new Object[][] { + new Object[] {"small_unmerged_picard_intervals.list"}, + new Object[] {"small_unmerged_gatk_intervals.list"} + }; + } + + @Test(dataProvider="unmergedIntervals") + public void testUnmergedIntervals(String unmergedIntervals) { + List locs = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Collections.singletonList(validationDataLocation + unmergedIntervals), false); + Assert.assertEquals(locs.size(), 2); + + List merged = IntervalUtils.mergeIntervalLocations(locs, IntervalMergingRule.ALL); + Assert.assertEquals(merged.size(), 1); + } } From 9a6b1f26819bc0f86362ba75947aea17dba77b75 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Sun, 11 Sep 2011 21:56:30 -0400 Subject: [PATCH 019/363] Fixed bug where exome could not output after contig change Check for new contig uses ContigIndex, not the string From 2316b6aad3e81cc0cd88980acd73d716fd4cdb2d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 12 Sep 2011 22:02:42 -0400 Subject: [PATCH 020/363] Trying to fix problems with S3 uploading behind firewalls -- Cannot reproduce the very long waits reported by some users. -- Fixed problem that exception might result in an undeleted file, which is now fixed with deleteOnExit() --- .../sting/gatk/phonehome/GATKRunReport.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java b/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java index 4d94130a80..70307380b7 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java +++ b/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java @@ -293,15 +293,16 @@ private void postReportToFile(File destination) throws IOException { * That is, postReport() is guarenteed not to fail for any reason. */ private File postReportToLocalDisk(File rootDir) { + String filename = getID() + ".report.xml.gz"; + File file = new File(rootDir, filename); try { - String filename = getID() + ".report.xml.gz"; - File file = new File(rootDir, filename); postReportToFile(file); logger.debug("Wrote report to " + file); return file; } catch ( Exception e ) { // we catch everything, and no matter what eat the error exceptDuringRunReport("Couldn't read report file", e); + file.delete(); return null; } } @@ -312,6 +313,7 @@ private void postReportToAWSS3() { File localFile = postReportToLocalDisk(new File("./")); logger.debug("Generating GATK report to AWS S3 based on local file " + localFile); if ( localFile != null ) { // we succeeded in creating the local file + localFile.deleteOnExit(); try { // stop us from printing the annoying, and meaningless, mime types warning Logger mimeTypeLogger = Logger.getLogger(org.jets3t.service.utils.Mimetypes.class); @@ -342,8 +344,6 @@ private void postReportToAWSS3() { exceptDuringRunReport("Couldn't calculate MD5", e); } catch ( IOException e ) { exceptDuringRunReport("Couldn't read report file", e); - } finally { - localFile.delete(); } } } From 64df3950c1cb367ac2bd61089e24bc962c289511 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 12 Sep 2011 22:03:13 -0400 Subject: [PATCH 021/363] Emit minimalTable that can be read into R From edf29d0616c576ece9a99af23cd42a54feb83e87 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 12 Sep 2011 22:16:52 -0400 Subject: [PATCH 022/363] Explicit info message about uploading S3 log --- .../org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java | 1 + 1 file changed, 1 insertion(+) diff --git a/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java b/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java index 70307380b7..5a7658031f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java +++ b/public/java/src/org/broadinstitute/sting/gatk/phonehome/GATKRunReport.java @@ -338,6 +338,7 @@ private void postReportToAWSS3() { //logger.info("Uploading " + localFile + " to AWS bucket"); S3Object s3Object = s3Service.putObject(REPORT_BUCKET_NAME, fileObject); logger.debug("Uploaded to AWS: " + s3Object); + logger.info("Uploaded run statistics report to AWS S3"); } catch ( S3ServiceException e ) { exceptDuringRunReport("S3 exception occurred", e); } catch ( NoSuchAlgorithmException e ) { From a942fa38ef87c1e1565d6a1cff041d8a62aaeb0a Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Thu, 15 Sep 2011 10:22:28 -0400 Subject: [PATCH 023/363] Refine the way we merge records in CombineVariants of different types. As of before, two records of different types were not combined and were kept separate. This is still the case, except when the alleles of one record are a strict subset of alleles of another record. For example, a SNP with alleles {A*,T} and a mixed record with alleles {A*,T, AAT} are now combined when start position matches. --- .../walkers/variantutils/CombineVariants.java | 35 +++++++++++++++++-- .../variantcontext/VariantContextUtils.java | 12 +++++++ 2 files changed, 45 insertions(+), 2 deletions(-) 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 7062f17e5d..3e3b29a7f3 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 @@ -234,16 +234,47 @@ public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentCo if (minimumN > 1 && (vcs.size() - numFilteredRecords < minimumN)) return 0; - List mergedVCs = new ArrayList(); + List preMergedVCs = new ArrayList(); Map> VCsByType = VariantContextUtils.separateVariantContextsByType(vcs); // iterate over the types so that it's deterministic for ( VariantContext.Type type : VariantContext.Type.values() ) { if ( VCsByType.containsKey(type) ) - mergedVCs.add(VariantContextUtils.simpleMerge(getToolkit().getGenomeLocParser(), VCsByType.get(type), + preMergedVCs.add(VariantContextUtils.simpleMerge(getToolkit().getGenomeLocParser(), VCsByType.get(type), priority, filteredRecordsMergeType, genotypeMergeOption, true, printComplexMerges, SET_KEY, filteredAreUncalled, MERGE_INFO_WITH_MAX_AC)); } + List mergedVCs = new ArrayList(); + // se have records merged but separated by type. If a particular record is for example a snp but all alleles are a subset of an existing mixed record, + // we will still merge those records. + if (preMergedVCs.size() > 1) { + for (VariantContext vc1 : preMergedVCs) { + VariantContext newvc = vc1; + boolean merged = false; + for (int k=0; k < mergedVCs.size(); k++) { + VariantContext vc2 = mergedVCs.get(k); + + if (VariantContextUtils.allelesAreSubset(vc1,vc2) || VariantContextUtils.allelesAreSubset(vc2,vc1)) { + // all alleles of vc1 are contained in vc2 but they are of different type (say, vc1 is snp, vc2 is complex): try to merget v1 into v2 + List vcpair = new ArrayList(); + vcpair.add(vc1); + vcpair.add(vc2); + newvc = VariantContextUtils.simpleMerge(getToolkit().getGenomeLocParser(), vcpair, + priority, filteredRecordsMergeType, genotypeMergeOption, true, printComplexMerges, + SET_KEY, filteredAreUncalled, MERGE_INFO_WITH_MAX_AC); + mergedVCs.set(k,newvc); + merged = true; + break; + } + } + if (!merged) + mergedVCs.add(vc1); + } + } + else { + mergedVCs = preMergedVCs; + } + for ( VariantContext mergedVC : mergedVCs ) { // only operate at the start of events if ( mergedVC == 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 986d6305c4..506bb3b33f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -663,6 +663,18 @@ else if ( nVariant == 0 ) // everyone was reference return merged; } + 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; From 04f572339945f876bd993e4fa1cb6d150f3832fc Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Thu, 15 Sep 2011 14:28:06 -0400 Subject: [PATCH 024/363] Updated to use new ROD-binding system From 7d6b34b1bb5db700c3360ac92be0e7a6f14fa100 Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Thu, 15 Sep 2011 15:31:38 -0400 Subject: [PATCH 025/363] Can now optionally annotate with a dbSNP VCF From 1960bcabb89781116eb641c93664ba249529580a Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Thu, 15 Sep 2011 18:07:32 -0400 Subject: [PATCH 026/363] Updated to use new ROD-binding system From e6e9b08c9a47640f9be32b47f495174118636a5c Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Thu, 15 Sep 2011 18:51:09 -0400 Subject: [PATCH 027/363] Must provide alleles VCF to UGCallVariants --- .../sting/gatk/walkers/genotyper/UGCallVariants.java | 1 - 1 file changed, 1 deletion(-) 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 500b113608..d88e556878 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 @@ -30,7 +30,6 @@ 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.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.SampleUtils; From 92f7db322838d0745892737a8f23a291591acfe8 Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Fri, 16 Sep 2011 02:40:24 -0400 Subject: [PATCH 028/363] Can now optionally suppress the use of all annotations and then add back in specific annotations/groups. This was necessary, since using all annotations for VariantAnnotator NOW includes SNPeff, which (annoyingly) requires a SNPeff VCF... From 7fa1e237d9e1e41fbdcd42f069df7c658f523bc7 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Fri, 16 Sep 2011 12:53:54 -0400 Subject: [PATCH 029/363] Forgot to git stash pop new MD5's for CombineVariants integration test --- .../walkers/variantutils/CombineVariantsIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java index 3267173a76..35495d7979 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java @@ -90,7 +90,7 @@ public void combinePLs(String file1, String file2, String md5) { @Test public void combineTrioCalls() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", "", "1d5a021387a8a86554db45a29f66140f"); } // official project VCF files in tabix format @Test public void combineTrioCallsMin() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", " -minimalVCF", "20163d60f18a46496f6da744ab5cc0f9"); } // official project VCF files in tabix format - @Test public void combine2Indels() { combine2("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "f1cf095c2fe9641b7ca1f8ee2c46fd4a"); } + @Test public void combine2Indels() { combine2("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "312a22aedb088b678bc891f1a1b03c91"); } @Test public void combineSNPsAndIndels() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "e144b6283765494bfe8189ac59965083"); } @@ -110,7 +110,7 @@ public void combinePLs(String file1, String file2, String md5) { " -priority NA19240_BGI,NA19240_ILLUMINA,NA19240_WUGSC,denovoInfo" + " -genotypeMergeOptions UNIQUIFY -L 1"), 1, - Arrays.asList("1de95f91ca15d2a8856de35dee0ce33e")); + Arrays.asList("35acb0f15f9cd18c653ede4e15e365c9")); executeTest("threeWayWithRefs", spec); } From cb4a50b1478bb54c19ae57703733e4f330bd7a2f Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Sat, 17 Sep 2011 16:42:49 -0400 Subject: [PATCH 030/363] Adding ability to try both small and large kmer lengths. Highest likelihood wins. --- .../sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java | 1 + 1 file changed, 1 insertion(+) 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 87dd37bf64..a5cb00a5d7 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 @@ -40,6 +40,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; +import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileupImpl; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.*; From 091c7197cdfa63e341e72230e41f187cf66931e0 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Sun, 18 Sep 2011 19:21:51 -0400 Subject: [PATCH 031/363] Fixed memory leak and bug with deletions in clipping The ClippingOp clip cigar function would run into a endless loop if the parameter were out of the reads range, I stopped the bug. * There is no check to make sure the read coordinate are covered by the read though When Hard clipping to interval, I added a check for deletions. NOTE: method works for NA12878 WEx but needs to be more thoroughly tested/optimized --- .../sting/utils/clipreads/ClippingOp.java | 4 +++ .../sting/utils/clipreads/ReadClipper.java | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 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 bc200372fe..951ab265c8 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -324,6 +324,8 @@ else if (index + shift > stop + 1) { if (index <= stop && cigarElementIterator.hasNext()) cigarElement = cigarElementIterator.next(); + else + break; } // add the remaining cigar elements @@ -363,6 +365,8 @@ else if (index + shift > stop + 1) { index += shift; if (index < start && cigarElementIterator.hasNext()) cigarElement = cigarElementIterator.next(); + else + break; } // check if we are hard clipping indels 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 26c25850ad..a8a3fad9ec 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -1,6 +1,8 @@ package org.broadinstitute.sting.utils.clipreads; import com.google.java.contract.Requires; +import net.sf.samtools.CigarElement; +import net.sf.samtools.CigarOperator; import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.sam.ReadUtils; @@ -56,17 +58,34 @@ public SAMRecord hardClipByReferenceCoordinatesRightTail(int refStart) { return hardClipByReferenceCoordinates(refStart, -1); } + private int numDeletions(SAMRecord read) { + int result = 0; + for (CigarElement e: read.getCigar().getCigarElements()) { + if ( e.getOperator() == CigarOperator.DELETION || e.getOperator() == CigarOperator.D ) + result =+ e.getLength(); + } + return result; + } + private SAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart); - int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop); + int stop = (refStop < 0) ? read.getReadLength() - 1: ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop); - if (start < 0 || stop > read.getReadLength() - 1) + if (start < 0 || stop > read.getReadLength() - 1 + numDeletions(read)) throw new ReviewedStingException("Trying to clip before the start or after the end of a read"); - // TODO add requires statement/check in the Hardclip function + // TODO add check in the Hardclip function if ( start > stop ) stop = ReadUtils.getReadCoordinateForReferenceCoordinate(read, ReadUtils.getRefCoordSoftUnclippedEnd(read)); + + //This tries to fix the bug where the deletion is counted a read base and as a result, the hardCLipper runs into + //an endless loop when hard clipping the cigar string because the read coordinates are not covered by the read + stop -= numDeletions(read); + if ( start > stop ) + start -= numDeletions(read); + + //System.out.println("Clipping start/stop: " + start + "/" + stop); this.addOp(new ClippingOp(start, stop)); SAMRecord clippedRead = clipRead(ClippingRepresentation.HARDCLIP_BASES); From bed78b47e090e19274273a1a552e0e40c82e0161 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sun, 18 Sep 2011 20:18:18 -0400 Subject: [PATCH 032/363] Marginally better formating, with hours the default time --- public/R/queueJobReport.R | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/public/R/queueJobReport.R b/public/R/queueJobReport.R index a24d269c9f..9f37aa038a 100644 --- a/public/R/queueJobReport.R +++ b/public/R/queueJobReport.R @@ -12,14 +12,14 @@ if ( onCMDLine ) { inputFileName = args[1] outputPDF = args[2] } else { - #inputFileName = "~/Desktop/broadLocal/GATK/unstable/report.txt" - inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/Q-25718@node1149.jobreport.txt" + inputFileName = "~/Desktop/Q-30033@gsa1.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 } -RUNTIME_UNITS = "(sec)" -ORIGINAL_UNITS_TO_SECONDS = 1/1000 +RUNTIME_UNITS = "(hours)" +ORIGINAL_UNITS_TO_SECONDS = 1/1000/60/60 # # Helper function to aggregate all of the jobs in the report across all tables @@ -33,7 +33,7 @@ allJobsFromReport <- function(report) { # # Creates segmentation plots of time (x) vs. job (y) with segments for the duration of the job # -plotJobsGantt <- function(gatkReport, sortOverall) { +plotJobsGantt <- function(gatkReport, sortOverall, includeText) { allJobs = allJobsFromReport(gatkReport) if ( sortOverall ) { title = "All jobs, by analysis, by start time" @@ -44,16 +44,18 @@ plotJobsGantt <- function(gatkReport, sortOverall) { } allJobs$index = 1:nrow(allJobs) minTime = min(allJobs$startTime) - allJobs$relStartTime = allJobs$startTime - minTime - allJobs$relDoneTime = allJobs$doneTime - minTime + allJobs$relStartTime = (allJobs$startTime - minTime) * ORIGINAL_UNITS_TO_SECONDS + allJobs$relDoneTime = (allJobs$doneTime - minTime) * ORIGINAL_UNITS_TO_SECONDS allJobs$ganttName = paste(allJobs$jobName, "@", allJobs$exechosts) maxRelTime = max(allJobs$relDoneTime) p <- ggplot(data=allJobs, aes(x=relStartTime, y=index, color=analysisName)) - p <- p + geom_segment(aes(xend=relDoneTime, yend=index), size=2, arrow=arrow(length = unit(0.1, "cm"))) - p <- p + geom_text(aes(x=relDoneTime, label=ganttName, hjust=-0.2), size=2) + p <- p + theme_bw() + p <- p + geom_segment(aes(xend=relDoneTime, yend=index), size=1, arrow=arrow(length = unit(0.1, "cm"))) + if ( includeText ) + p <- p + geom_text(aes(x=relDoneTime, label=ganttName, hjust=-0.2), size=2) p <- p + xlim(0, maxRelTime * 1.1) p <- p + xlab(paste("Start time (relative to first job)", RUNTIME_UNITS)) - p <- p + ylab("Job") + p <- p + ylab("Job number") p <- p + opts(title=title) print(p) } @@ -155,8 +157,8 @@ if ( ! is.na(outputPDF) ) { pdf(outputPDF, height=8.5, width=11) } -plotJobsGantt(gatkReportData, T) -plotJobsGantt(gatkReportData, F) +plotJobsGantt(gatkReportData, T, F) +plotJobsGantt(gatkReportData, F, F) plotProgressByTime(gatkReportData) for ( group in gatkReportData ) { plotGroup(group) From 4ad330008ddb29e163089afa2c264f62dccd4c3f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 19 Sep 2011 10:19:10 -0400 Subject: [PATCH 033/363] Final intervals cleanup -- No functional changes (my algorithm wouldn't work) -- Major structural cleanup (returning more basic data structures that allow us to development new algorithm) -- Unit tests for the efficiency of interval partitioning --- build.xml | 4 +- .../sting/utils/interval/IntervalUtils.java | 72 ++++++++++++++----- .../utils/interval/IntervalUtilsUnitTest.java | 63 ++++++++-------- 3 files changed, 83 insertions(+), 56 deletions(-) diff --git a/build.xml b/build.xml index 1196f32dcd..e5ad9daf0c 100644 --- a/build.xml +++ b/build.xml @@ -852,8 +852,8 @@ - - + + 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 2cfcc19a9d..41cbbe59fa 100644 --- a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java @@ -333,6 +333,28 @@ public static void scatterContigIntervals(SAMFileHeader fileHeader, List> splitIntervalsToSubLists(List locs, List splits) { + int locIndex = 1; + int start = 0; + List> sublists = new ArrayList>(splits.size()); + for (Integer stop: splits) { + List curList = new ArrayList(); + for (int i = start; i < stop; i++) + curList.add(locs.get(i)); + start = stop; + sublists.add(curList); + } + + return sublists; + } + + /** * Splits an interval list into multiple files. * @param fileHeader The sam file header. @@ -362,27 +384,39 @@ public static void scatterFixedIntervals(SAMFileHeader fileHeader, List> splitFixedIntervals(List locs, int numParts) { if (locs.size() < numParts) throw new UserException.BadArgumentValue("scatterParts", String.format("Cannot scatter %d locs into %d parts.", locs.size(), numParts)); - final long locsSize = intervalSize(locs); - final double idealSplitSize = locsSize / numParts; - final List> splits = new ArrayList>(numParts); - final LinkedList remainingLocs = new LinkedList(locs); - - for ( int i = 0; i < numParts; i++ ) { - long splitSize = 0; - List split = new ArrayList(); - while ( ! remainingLocs.isEmpty() ) { - final GenomeLoc toAdd = remainingLocs.pop(); - splitSize += toAdd.size(); - split.add(toAdd); - final long nextEltSize = remainingLocs.isEmpty() ? 0 : remainingLocs.peek().size(); - if ( splitSize + (i % 2 == 0 ? 0 : nextEltSize) > idealSplitSize ) - break; - } - splits.add(split); - } + final List splitPoints = new ArrayList(); + addFixedSplit(splitPoints, locs, locsSize, 0, locs.size(), numParts); + Collections.sort(splitPoints); + splitPoints.add(locs.size()); + return splitIntervalsToSubLists(locs, splitPoints); + } - return splits; + private static void addFixedSplit(List splitPoints, List locs, long locsSize, int startIndex, int stopIndex, int numParts) { + if (numParts < 2) + return; + int halfParts = (numParts + 1) / 2; + Pair splitPoint = getFixedSplit(locs, locsSize, startIndex, stopIndex, halfParts, numParts - halfParts); + int splitIndex = splitPoint.first; + long splitSize = splitPoint.second; + splitPoints.add(splitIndex); + addFixedSplit(splitPoints, locs, splitSize, startIndex, splitIndex, halfParts); + addFixedSplit(splitPoints, locs, locsSize - splitSize, splitIndex, stopIndex, numParts - halfParts); + } + + private static Pair getFixedSplit(List locs, long locsSize, int startIndex, int stopIndex, int minLocs, int maxLocs) { + int splitIndex = startIndex; + long splitSize = 0; + for (int i = 0; i < minLocs; i++) { + splitSize += locs.get(splitIndex).size(); + splitIndex++; + } + long halfSize = locsSize / 2; + while (splitIndex < (stopIndex - maxLocs) && splitSize < halfSize) { + splitSize += locs.get(splitIndex).size(); + splitIndex++; + } + return new Pair(splitIndex, splitSize); } /** 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 4809f1b5c8..98b878d23a 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java @@ -1,7 +1,6 @@ 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.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; @@ -101,25 +100,18 @@ public String toString() { @DataProvider(name = "intervalslicingdata") public Object[][] createTrees() { new IntervalSlicingTest(1, 0); - new IntervalSlicingTest(2, 0.1); - new IntervalSlicingTest(3, 0.1); - new IntervalSlicingTest(7, 0.1); - new IntervalSlicingTest(10, 0.1); - new IntervalSlicingTest(31, 0.1); - new IntervalSlicingTest(67, 0.1); - new IntervalSlicingTest(100, 0.1); - new IntervalSlicingTest(127, 0.1); - // starts to become a bit less efficiency with larger cuts - new IntervalSlicingTest(500, 0.5); + new IntervalSlicingTest(2, 1); + new IntervalSlicingTest(5, 1); + new IntervalSlicingTest(10, 1); + new IntervalSlicingTest(67, 1); + new IntervalSlicingTest(100, 1); + new IntervalSlicingTest(500, 1); new IntervalSlicingTest(1000, 1); - new IntervalSlicingTest(10000, 10); return IntervalSlicingTest.getTests(IntervalSlicingTest.class); } - @Test(dataProvider = "intervalslicingdata") + @Test(enabled = true, dataProvider = "intervalslicingdata") public void testFixedScatterIntervalsAlgorithm(IntervalSlicingTest test) { - Set locsSet = new HashSet(hg19exomeIntervals); - Set notFoundSet = new HashSet(hg19exomeIntervals); List> splits = IntervalUtils.splitFixedIntervals(hg19exomeIntervals, test.parts); long totalSize = IntervalUtils.intervalSize(hg19exomeIntervals); @@ -134,15 +126,9 @@ public void testFixedScatterIntervalsAlgorithm(IntervalSlicingTest test) { counter++; sumOfSplitSizes += splitSize; Assert.assertTrue(Math.abs(sigma) <= test.maxAllowableVariance, String.format("Interval %d (size %d ideal %d) has a variance %.2f outside of the tolerated range %.2f", counter, splitSize, idealSplitSize, sigma, test.maxAllowableVariance)); - - for ( final GenomeLoc loc : split ) { - Assert.assertTrue(locsSet.contains(loc), "Split location " + loc + " not found in set of input locs"); - notFoundSet.remove(loc); - } } - Assert.assertEquals(sumOfSplitSizes, totalSize, "Split intervals don't contain the exact number of bases in the original intervals"); - Assert.assertTrue(notFoundSet.isEmpty(), "Not all intervals were present in the split set"); + Assert.assertEquals(totalSize, sumOfSplitSizes, "Split intervals don't contain the exact number of bases in the origianl intervals"); } @Test(expectedExceptions=UserException.class) @@ -246,7 +232,8 @@ public void testFixedScatterIntervalsBasic() { List files = testFiles("basic.", 3, ".intervals"); List locs = getLocs("chr1", "chr2", "chr3"); - IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(locs, files.size()); + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); @@ -271,20 +258,21 @@ public void testScatterFixedIntervalsLessFiles() { List files = testFiles("less.", 3, ".intervals"); List locs = getLocs("chr1", "chr2", "chr3", "chr4"); - IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(locs, files.size()); + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); List locs3 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(2).toString()), false); - Assert.assertEquals(locs1.size(), 2); + Assert.assertEquals(locs1.size(), 1); Assert.assertEquals(locs2.size(), 1); - Assert.assertEquals(locs3.size(), 1); + Assert.assertEquals(locs3.size(), 2); Assert.assertEquals(locs1.get(0), chr1); - Assert.assertEquals(locs1.get(1), chr2); - Assert.assertEquals(locs2.get(0), chr3); - Assert.assertEquals(locs3.get(0), chr4); + Assert.assertEquals(locs2.get(0), chr2); + Assert.assertEquals(locs3.get(0), chr3); + Assert.assertEquals(locs3.get(1), chr4); } @Test(expectedExceptions=UserException.BadArgumentValue.class) @@ -298,7 +286,8 @@ public void testSplitFixedIntervalsMoreFiles() { public void testScatterFixedIntervalsMoreFiles() { List files = testFiles("more.", 3, ".intervals"); List locs = getLocs("chr1", "chr2"); - IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, locs.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(locs, locs.size()); // locs.size() instead of files.size() + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); } @Test public void testScatterFixedIntervalsStart() { @@ -311,7 +300,8 @@ public void testScatterFixedIntervalsStart() { List files = testFiles("split.", 3, ".intervals"); List locs = getLocs(intervals); - IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(locs, files.size()); + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); @@ -338,7 +328,8 @@ public void testScatterFixedIntervalsMiddle() { List files = testFiles("split.", 3, ".intervals"); List locs = getLocs(intervals); - IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(locs, files.size()); + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); @@ -365,7 +356,8 @@ public void testScatterFixedIntervalsEnd() { List files = testFiles("split.", 3, ".intervals"); List locs = getLocs(intervals); - IntervalUtils.scatterFixedIntervals(hg18Header, IntervalUtils.splitFixedIntervals(locs, files.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(locs, files.size()); + IntervalUtils.scatterFixedIntervals(hg18Header, splits, files); List locs1 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(0).toString()), false); List locs2 = IntervalUtils.parseIntervalArguments(hg18GenomeLocParser, Arrays.asList(files.get(1).toString()), false); @@ -399,7 +391,7 @@ public void testScatterFixedIntervalsFile() { //String splitCounts = ""; for (int i = 0; i < splits.size(); i++) { - long splitCount = splits.get(i).size(); + int splitCount = splits.get(i).size(); Assert.assertEquals(splitCount, counts[i], "Num intervals in split " + i); } //System.out.println(splitCounts.substring(2)); @@ -420,7 +412,8 @@ public void testScatterFixedIntervalsFile() { @Test public void testScatterFixedIntervalsMax() { List files = testFiles("sg.", 85, ".intervals"); - IntervalUtils.scatterFixedIntervals(hg19Header, IntervalUtils.splitFixedIntervals(hg19ReferenceLocs, files.size()), files); + List> splits = IntervalUtils.splitFixedIntervals(hg19ReferenceLocs, files.size()); + IntervalUtils.scatterFixedIntervals(hg19Header, splits, files); for (int i = 0; i < files.size(); i++) { String file = files.get(i).toString(); From ca1b30e4a4822672803421278eb8301b14cff417 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Mon, 19 Sep 2011 10:29:06 -0400 Subject: [PATCH 034/363] Fix the -T argument in the DepthOfCoverage docs Add documentation for the RefSeqCodec, pointing users to the wiki page describing how to create the file --- .../coverage/DepthOfCoverageWalker.java | 9 ++++--- .../utils/codecs/refseq/RefSeqCodec.java | 24 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java index 86f97a36cd..664c319ab5 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java @@ -63,9 +63,12 @@ *

Input

*

* One or more bam files (with proper headers) to be analyzed for coverage statistics - * (Optional) A REFSEQ Rod to aggregate coverage to the gene level *

- * + *

+ *(Optional) A REFSEQ Rod to aggregate coverage to the gene level + *

+ * (for information about creating the REFSEQ Rod, please consult the RefSeqCodec documentation) + *

*

Output

*

* Tables pertaining to different coverage summaries. Suffix on the table files declares the contents: @@ -93,7 +96,7 @@ *

  * java -Xmx2g -jar GenomeAnalysisTK.jar \
  *   -R ref.fasta \
- *   -T VariantEval \
+ *   -T DepthOfCoverage \
  *   -o file_name_base \
  *   -I input_bams.list
  *   [-geneList refSeq.sorted.txt] \
diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java
index d94d9ff841..f142fa5aaf 100644
--- a/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java
+++ b/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java
@@ -12,19 +12,35 @@
 import java.util.ArrayList;
 
 /**
- * TODO FOR CHRIS HARTL
+ * Allows for reading in RefSeq information
  *
  * 

- * Codec Description + * Parses a sorted UCSC RefSeq file (see below) into relevant features: the gene name, the unique gene name (if multiple transcrips get separate entries), exons, gene start/stop, coding start/stop, + * strandedness of transcription. *

* *

- * See also: link to file specification + * Instructions for generating a RefSeq file for use with the RefSeq codec can be found on the Wiki here + * http://www.broadinstitute.org/gsa/wiki/index.php/RefSeq *

+ *

Usage

+ * The RefSeq Rod can be bound as any other rod, and is specified by REFSEQ, for example + *
+ * -refSeqBinding:REFSEQ /path/to/refSeq.txt
+ * 
+ * + * You will need to consult individual walkers for the binding name ("refSeqBinding", above) * *

File format example

+ * If you want to define your own file for use, the format is (tab delimited): + * bin, name, chrom, strand, transcription start, transcription end, coding start, coding end, num exons, exon starts, exon ends, id, alt. name, coding start status (complete/incomplete), coding end status (complete,incomplete) + * and exon frames, for example: + *
+ * 76 NM_001011874 1 - 3204562 3661579 3206102 3661429 3 3204562,3411782,3660632, 3207049,3411982,3661579, 0 Xkr4 cmpl cmpl 1,2,0,
+ * 
+ * for more information see here *

- * A BAM file containing exactly one sample. + * *

* * @author Mark DePristo From 3e93f246f7b8849a3126fab5e0757cfdee22e661 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 19 Sep 2011 11:40:45 -0400 Subject: [PATCH 035/363] Support for sample sets in AssignSomaticStatus -- Also cleaned up SampleUtils.getSamplesFromCommandLine() to return a set, not a list, and trim the sample names. --- .../sting/utils/SampleUtils.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java b/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java index f9997bfd8f..1b4703e4af 100755 --- a/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java @@ -190,11 +190,21 @@ else if ( occurrences >= 2 ) { } - public static List getSamplesFromCommandLineInput(Collection sampleArgs) { + /** + * Returns a new set of samples, containing a final list of samples expanded from sampleArgs + * + * Each element E of sampleArgs can either be a literal sample name or a file. For each E, + * we try to read a file named E from disk, and if possible all lines from that file are expanded + * into unique sample names. + * + * @param sampleArgs + * @return + */ + public static Set getSamplesFromCommandLineInput(Collection sampleArgs) { if (sampleArgs != 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 // sample list set, and treat the entries as if they had been specified on the command line. - List samplesFromFiles = new ArrayList(); + Set samplesFromFiles = new HashSet(); for (String SAMPLE_EXPRESSION : sampleArgs) { File sampleFile = new File(SAMPLE_EXPRESSION); @@ -203,7 +213,7 @@ public static List getSamplesFromCommandLineInput(Collection sam List lines = reader.readLines(); for (String line : lines) { - samplesFromFiles.add(line); + samplesFromFiles.add(line.trim()); } } catch (FileNotFoundException e) { samplesFromFiles.add(SAMPLE_EXPRESSION); // not a file, so must be a sample @@ -212,7 +222,8 @@ public static List getSamplesFromCommandLineInput(Collection sam return samplesFromFiles; } - return new ArrayList(); + + return new HashSet(); } public static Set getSamplesFromCommandLineInput(Collection vcfSamples, Collection sampleExpressions) { From 034b8685889a879eda0e7c1a001358a26845755a Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Mon, 19 Sep 2011 12:16:07 -0400 Subject: [PATCH 036/363] Revert "Fix the -T argument in the DepthOfCoverage docs" This reverts commit 0994efda998cf3a41b1a43696dbc852a441d5316. --- .../coverage/DepthOfCoverageWalker.java | 9 +++---- .../utils/codecs/refseq/RefSeqCodec.java | 24 ++++--------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java index 664c319ab5..86f97a36cd 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java @@ -63,12 +63,9 @@ *

Input

*

* One or more bam files (with proper headers) to be analyzed for coverage statistics + * (Optional) A REFSEQ Rod to aggregate coverage to the gene level *

- *

- *(Optional) A REFSEQ Rod to aggregate coverage to the gene level - *

- * (for information about creating the REFSEQ Rod, please consult the RefSeqCodec documentation) - *

+ * *

Output

*

* Tables pertaining to different coverage summaries. Suffix on the table files declares the contents: @@ -96,7 +93,7 @@ *

  * java -Xmx2g -jar GenomeAnalysisTK.jar \
  *   -R ref.fasta \
- *   -T DepthOfCoverage \
+ *   -T VariantEval \
  *   -o file_name_base \
  *   -I input_bams.list
  *   [-geneList refSeq.sorted.txt] \
diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java b/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java
index f142fa5aaf..d94d9ff841 100644
--- a/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java
+++ b/public/java/src/org/broadinstitute/sting/utils/codecs/refseq/RefSeqCodec.java
@@ -12,35 +12,19 @@
 import java.util.ArrayList;
 
 /**
- * Allows for reading in RefSeq information
+ * TODO FOR CHRIS HARTL
  *
  * 

- * Parses a sorted UCSC RefSeq file (see below) into relevant features: the gene name, the unique gene name (if multiple transcrips get separate entries), exons, gene start/stop, coding start/stop, - * strandedness of transcription. + * Codec Description *

* *

- * Instructions for generating a RefSeq file for use with the RefSeq codec can be found on the Wiki here - * http://www.broadinstitute.org/gsa/wiki/index.php/RefSeq + * See also: link to file specification *

- *

Usage

- * The RefSeq Rod can be bound as any other rod, and is specified by REFSEQ, for example - *
- * -refSeqBinding:REFSEQ /path/to/refSeq.txt
- * 
- * - * You will need to consult individual walkers for the binding name ("refSeqBinding", above) * *

File format example

- * If you want to define your own file for use, the format is (tab delimited): - * bin, name, chrom, strand, transcription start, transcription end, coding start, coding end, num exons, exon starts, exon ends, id, alt. name, coding start status (complete/incomplete), coding end status (complete,incomplete) - * and exon frames, for example: - *
- * 76 NM_001011874 1 - 3204562 3661579 3206102 3661429 3 3204562,3411782,3660632, 3207049,3411982,3661579, 0 Xkr4 cmpl cmpl 1,2,0,
- * 
- * for more information see here *

- * + * A BAM file containing exactly one sample. *

* * @author Mark DePristo From 85626e7a5dbae8a263b8e2ff2e64bd25656d6e9c Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 19 Sep 2011 12:24:05 -0400 Subject: [PATCH 037/363] We no longer want people to use the August 2010 Dindel calls for indel realignment but instead Guillermo's new whole genome bi-allelic indel calls; updating the bundle accordingly. Also, there was some confusion by the 1000G data processing folks as to exactly what these indel files are, so I've renamed them so that it's clear. Wiki updated too. --- .../sting/queue/qscripts/GATKResourcesBundle.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 59c00b8cd3..e8b8258c1b 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala @@ -131,11 +131,11 @@ class GATKResourcesBundle extends QScript { addResource(new Resource("/humgen/gsa-hpprojects/GATK/data/Comparisons/Validated/HapMap/3.3/genotypes_r27_nr.b37_fwd.vcf", "hapmap_3.3", b37, true, true)) - addResource(new Resource("/humgen/gsa-hpprojects/GATK/data/Comparisons/Unvalidated/AFR+EUR+ASN+1KG.dindel_august_release_merged_pilot1.20110126.sites.vcf", - "1000G_indels_for_realignment", b37, true, false)) + addResource(new Resource("/humgen/1kg/processing/official_release/phase1/ALL.wgs.VQSR_consensus_biallelic.20101123.indels.sites.vcf", + "1000G_biallelic.indels", b37, true, false)) addResource(new Resource("/humgen/gsa-hpprojects/GATK/data/Comparisons/Validated/Mills_Devine_Indels_2011/ALL.wgs.indels_mills_devine_hg19_leftAligned_collapsed_double_hit.sites.vcf", - "indels_mills_devine", b37, true, true)) + "Mills_Devine_2hit.indels", b37, true, true)) // // example call set for wiki tutorial From 5e832254a4e024378f7fdee252abf7df9e289c6a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 19 Sep 2011 13:28:41 -0400 Subject: [PATCH 038/363] Fixing ReadAndInterval overlap comments. --- .../sting/utils/sam/ReadUtils.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 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 62bbb03073..18fcdabf2d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -118,31 +118,40 @@ public enum OverlapType { NOT_OVERLAPPING, IN_ADAPTOR} /** * This enum represents all the different ways in which a read can overlap an interval. * - * NO_OVERLAP: + * NO_OVERLAP_CONTIG: + * read and interval are in different contigs. + * + * NO_OVERLAP_LEFT: + * the read does not overlap the interval. + * + * |----------------| (interval) + * <----------------> (read) + * + * NO_OVERLAP_RIGHT: * the read does not overlap the interval. * * |----------------| (interval) * <----------------> (read) * - * LEFT_OVERLAP: + * OVERLAP_LEFT: * the read starts before the beginning of the interval but ends inside of it * * |----------------| (interval) * <----------------> (read) * - * RIGHT_OVERLAP: + * OVERLAP_RIGHT: * the read starts inside the interval but ends outside of it * * |----------------| (interval) * <----------------> (read) * - * FULL_OVERLAP: + * OVERLAP_LEFT_AND_RIGHT: * the read starts before the interval and ends after the interval * * |-----------| (interval) * <-------------------> (read) * - * CONTAINED: + * OVERLAP_CONTAINED: * the read starts and ends inside the interval * * |----------------| (interval) From ba150570f3d7747256f634a2828ab673a98953f7 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 19 Sep 2011 13:30:32 -0400 Subject: [PATCH 039/363] Updating to use new rod system syntax plus name change for CountRODs --- .../sting/queue/qscripts/GATKResourcesBundle.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e8b8258c1b..036a77b580 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/GATKResourcesBundle.scala @@ -300,9 +300,9 @@ class GATKResourcesBundle extends QScript { bamFile = bamIn } - class IndexVCF(@Input vcf: File, @Input ref: File) extends CountRod with UNIVERSAL_GATK_ARGS { + class IndexVCF(@Input vcf: File, @Input ref: File) extends CountRODs with UNIVERSAL_GATK_ARGS { //@Output val vcfIndex: File = swapExt(vcf.getParent, vcf, ".vcf", ".vcf.idx") - this.rodBind :+= RodBind(vcf.getName, "VCF", vcf) + this.rod :+= vcf this.reference_sequence = ref } @@ -313,7 +313,7 @@ class GATKResourcesBundle extends QScript { } class MakeDBSNP129(@Input dbsnp: File, @Input ref: File, @Output dbsnp129: File) extends SelectVariants with UNIVERSAL_GATK_ARGS { - this.rodBind :+= RodBind("variant", "VCF", dbsnp) + this.variant = dbsnp this.select ++= List("\"dbSNPBuildID <= 129\"") this.reference_sequence = ref this.out = dbsnp129 From 080c9575470c505e10f7b09d59fa22fcb668867d Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 19 Sep 2011 13:53:08 -0400 Subject: [PATCH 040/363] Fixing contracts for SoftUnclippedEnd utils Now accepts reads that are entirely contained inside an insertion. --- .../broadinstitute/sting/utils/sam/ReadUtils.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 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 18fcdabf2d..2de17db14b 100644 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -667,7 +667,7 @@ else if ( (start < interval.getStart()) ) return ReadAndIntervalOverlap.OVERLAP_RIGHT; } - @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd()"}) + @Ensures({"(result >= read.getUnclippedStart() && result <= read.getUnclippedEnd()) || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedStart(SAMRecord read) { int start = read.getUnclippedStart(); for (CigarElement cigarElement : read.getCigar().getCigarElements()) { @@ -679,7 +679,7 @@ public static int getRefCoordSoftUnclippedStart(SAMRecord read) { return start; } - @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd()"}) + @Ensures({"(result >= read.getUnclippedStart() && result <= read.getUnclippedEnd()) || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedEnd(SAMRecord read) { int stop = read.getUnclippedStart(); int shift = 0; @@ -695,6 +695,14 @@ public static int getRefCoordSoftUnclippedEnd(SAMRecord read) { return (lastOperator == CigarOperator.HARD_CLIP) ? stop-1 : stop+shift-1 ; } + private static boolean readIsEntirelyInsertion(SAMRecord read) { + for (CigarElement cigarElement : read.getCigar().getCigarElements()) { + if (cigarElement.getOperator() != CigarOperator.INSERTION) + return false; + } + return true; + } + /** * Looks for a read coordinate that corresponds to the reference coordinate in the soft clipped region before * the alignment start of the read. From 56106d54ed620965aea0b39052de43c81671c817 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 19 Sep 2011 14:00:00 -0400 Subject: [PATCH 041/363] Changing ReadUtils behavior to comply with GenomeLocParser Now the functions getRefCoordSoftUnclippedStart and getRefCoordSoftUnclippedEnd will return getUnclippedStart if the read is all contained within an insertion. Updated the contracts accordingly. This should give the same behavior as the GenomeLocParser now. --- .../src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 2de17db14b..5d3ef30866 100644 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -667,7 +667,7 @@ else if ( (start < interval.getStart()) ) return ReadAndIntervalOverlap.OVERLAP_RIGHT; } - @Ensures({"(result >= read.getUnclippedStart() && result <= read.getUnclippedEnd()) || readIsEntirelyInsertion(read)"}) + @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedStart(SAMRecord read) { int start = read.getUnclippedStart(); for (CigarElement cigarElement : read.getCigar().getCigarElements()) { @@ -679,9 +679,13 @@ public static int getRefCoordSoftUnclippedStart(SAMRecord read) { return start; } - @Ensures({"(result >= read.getUnclippedStart() && result <= read.getUnclippedEnd()) || readIsEntirelyInsertion(read)"}) + @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedEnd(SAMRecord read) { int stop = read.getUnclippedStart(); + + if (readIsEntirelyInsertion(read)) + return stop; + int shift = 0; CigarOperator lastOperator = null; for (CigarElement cigarElement : read.getCigar().getCigarElements()) { From 5d0705acd654cfed6e9bf2ba690a0c75ee5a50d8 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Tue, 20 Sep 2011 09:07:28 -0400 Subject: [PATCH 042/363] Adding quality scores to the VCF records created by the Haplotype Caller --- .../sting/gatk/walkers/genotyper/UnifiedGenotyperEngine.java | 1 - 1 file changed, 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 a5cb00a5d7..87dd37bf64 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 @@ -40,7 +40,6 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; -import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileupImpl; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.*; From b7511c5ff3b36e16037bfbbbd17b9fd4c9ea47af Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 20 Sep 2011 10:53:18 -0400 Subject: [PATCH 043/363] Fixed long-standing bug in tribble index creation -- Previously, on the fly indices didn't have dictionary set on the fly, so the GATK would read, add dictionary, and rewrite the index. This is now fixed, so that the on the fly index contains the reference dictionary when first written, avoiding the unnecessary read and write -- Added a GenomeAnalysisEngine and Walker function called getMasterSequenceDictionary() that fetches the reference sequence dictionary. This can be used conveniently everywhere, and is what's written into the Tribble index -- Refactored tribble index utilities from RMDTrackBuilder into IndexDictionaryUtils -- VCFWriter now requires the master sequence dictionary -- Updated walkers that create VCFWriters to provide the master sequence dictionary --- .../sting/gatk/GenomeAnalysisEngine.java | 8 ++ .../gatk/io/storage/VCFWriterStorage.java | 4 +- .../sting/gatk/io/stubs/VCFWriterStub.java | 10 ++ .../gatk/refdata/indexer/RMDIndexer.java | 2 +- .../refdata/tracks/IndexDictionaryUtils.java | 106 ++++++++++++++++ .../gatk/refdata/tracks/RMDTrackBuilder.java | 113 ++++-------------- .../sting/gatk/walkers/Walker.java | 10 ++ .../variantutils/LiftoverVariants.java | 2 +- .../variantutils/RandomlySplitVariants.java | 2 +- .../utils/codecs/vcf/IndexingVCFWriter.java | 38 ++++-- .../utils/codecs/vcf/StandardVCFWriter.java | 26 ++-- .../sting/utils/gcf/GCFWriter.java | 5 +- .../org/broadinstitute/sting/WalkerTest.java | 2 +- .../tracks/RMDTrackBuilderUnitTest.java | 4 +- .../codecs/vcf/IndexFactoryUnitTest.java | 22 +++- .../utils/genotype/vcf/VCFWriterUnitTest.java | 5 +- 16 files changed, 228 insertions(+), 131 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/IndexDictionaryUtils.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 5b9ebd99b1..972943e263 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -929,6 +929,14 @@ public SAMFileHeader getSAMFileHeader(SAMReaderID reader) { return readsDataSource.getHeader(reader); } + /** + * Gets the master sequence dictionary for this GATK engine instance + * @return a never-null dictionary listing all of the contigs known to this engine instance + */ + public SAMSequenceDictionary getMasterSequenceDictionary() { + return getReferenceDataSource().getReference().getSequenceDictionary(); + } + /** * Returns data source object encapsulating all essential info and handlers used to traverse * reads; header merger, individual file readers etc can be accessed through the returned data source object. diff --git a/public/java/src/org/broadinstitute/sting/gatk/io/storage/VCFWriterStorage.java b/public/java/src/org/broadinstitute/sting/gatk/io/storage/VCFWriterStorage.java index ebb4cbe668..4ca7b935f4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/io/storage/VCFWriterStorage.java +++ b/public/java/src/org/broadinstitute/sting/gatk/io/storage/VCFWriterStorage.java @@ -46,7 +46,7 @@ public VCFWriterStorage( VCFWriterStub stub ) { else if ( stub.getOutputStream() != null ) { this.file = null; this.stream = stub.getOutputStream(); - writer = new StandardVCFWriter(stream, stub.doNotWriteGenotypes()); + writer = new StandardVCFWriter(stream, stub.getMasterSequenceDictionary(), stub.doNotWriteGenotypes()); } else throw new ReviewedStingException("Unable to create target to which to write; storage was provided with neither a file nor a stream."); @@ -71,7 +71,7 @@ private StandardVCFWriter vcfWriterToFile(VCFWriterStub stub, File file, boolean } // The GATK/Tribble can't currently index block-compressed files on the fly. Disable OTF indexing even if the user explicitly asked for it. - return new StandardVCFWriter(file, this.stream, indexOnTheFly && !stub.isCompressed(), stub.doNotWriteGenotypes()); + return new StandardVCFWriter(file, this.stream, stub.getMasterSequenceDictionary(), indexOnTheFly && !stub.isCompressed(), stub.doNotWriteGenotypes()); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/io/stubs/VCFWriterStub.java b/public/java/src/org/broadinstitute/sting/gatk/io/stubs/VCFWriterStub.java index 936243f9de..82cb436344 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/io/stubs/VCFWriterStub.java +++ b/public/java/src/org/broadinstitute/sting/gatk/io/stubs/VCFWriterStub.java @@ -25,6 +25,7 @@ package org.broadinstitute.sting.gatk.io.stubs; +import net.sf.samtools.SAMSequenceDictionary; import net.sf.samtools.SAMSequenceRecord; import org.broadinstitute.sting.gatk.CommandLineExecutable; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; @@ -150,6 +151,15 @@ public boolean isCompressed() { return isCompressed; } + /** + * Gets the master sequence dictionary from the engine associated with this stub + * @link GenomeAnalysisEngine.getMasterSequenceDictionary + * @return + */ + public SAMSequenceDictionary getMasterSequenceDictionary() { + return engine.getMasterSequenceDictionary(); + } + /** * Should we tell the VCF writer not to write genotypes? * @return true if the writer should not write genotypes. diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java index 029800aea6..9e5a95d10e 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java @@ -101,7 +101,7 @@ protected int execute() throws Exception { Index index = IndexFactory.createIndex(inputFileSource, codec, approach); // add writing of the sequence dictionary, if supplied - builder.setIndexSequenceDictionary(inputFileSource, index, ref.getSequenceDictionary(), indexFile, false); + builder.validateAndUpdateIndexSequenceDictionary(inputFileSource, index, ref.getSequenceDictionary()); // create the output stream, and write the index LittleEndianOutputStream stream = new LittleEndianOutputStream(new FileOutputStream(indexFile)); diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/IndexDictionaryUtils.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/IndexDictionaryUtils.java new file mode 100644 index 0000000000..d133439dc9 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/IndexDictionaryUtils.java @@ -0,0 +1,106 @@ +/* + * 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.tracks; + +import net.sf.samtools.SAMSequenceDictionary; +import net.sf.samtools.SAMSequenceRecord; +import org.apache.log4j.Logger; +import org.broad.tribble.index.Index; +import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; +import org.broadinstitute.sting.utils.SequenceDictionaryUtils; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * Utilities for working with Sequence Dictionaries embedded in tribble indices + * + * @author Your Name + * @since Date created + */ +public class IndexDictionaryUtils { + private final static Logger logger = Logger.getLogger(IndexDictionaryUtils.class); + + // a constant we use for marking sequence dictionary entries in the Tribble index property list + public static final String SequenceDictionaryPropertyPredicate = "DICT:"; + + /** + * get the sequence dictionary from the track, if available. If not, make it from the contig list that is always in the index + * @param index the index file to use + * @return a SAMSequenceDictionary if available, null if unavailable + */ + public static SAMSequenceDictionary getSequenceDictionaryFromProperties(Index index) { + SAMSequenceDictionary dict = new SAMSequenceDictionary(); + for (Map.Entry entry : index.getProperties().entrySet()) { + if (entry.getKey().startsWith(SequenceDictionaryPropertyPredicate)) + dict.addSequence(new SAMSequenceRecord(entry.getKey().substring(SequenceDictionaryPropertyPredicate.length() , entry.getKey().length()), + Integer.valueOf(entry.getValue()))); + } + return dict; + } + + /** + * create the sequence dictionary with the contig list; a backup approach + * @param index the index file to use + * @param dict the sequence dictionary to add contigs to + * @return the filled-in sequence dictionary + */ + static SAMSequenceDictionary createSequenceDictionaryFromContigList(Index index, SAMSequenceDictionary dict) { + LinkedHashSet seqNames = index.getSequenceNames(); + if (seqNames == null) { + return dict; + } + for (String name : seqNames) { + SAMSequenceRecord seq = new SAMSequenceRecord(name, 0); + dict.addSequence(seq); + } + return dict; + } + + public static void setIndexSequenceDictionary(Index index, SAMSequenceDictionary dict) { + for ( SAMSequenceRecord seq : dict.getSequences() ) { + final String contig = IndexDictionaryUtils.SequenceDictionaryPropertyPredicate + seq.getSequenceName(); + final String length = String.valueOf(seq.getSequenceLength()); + index.addProperty(contig,length); + } + } + + public static void validateTrackSequenceDictionary(final String trackName, + final SAMSequenceDictionary trackDict, + final SAMSequenceDictionary referenceDict, + final ValidationExclusion.TYPE validationExclusionType ) { + // if the sequence dictionary is empty (as well as null which means it doesn't have a dictionary), skip validation + if (trackDict == null || trackDict.size() == 0) + logger.info("Track " + trackName + " doesn't have a sequence dictionary built in, skipping dictionary validation"); + else { + Set trackSequences = new TreeSet(); + for (SAMSequenceRecord dictionaryEntry : trackDict.getSequences()) + trackSequences.add(dictionaryEntry.getSequenceName()); + SequenceDictionaryUtils.validateDictionaries(logger, validationExclusionType, trackName, trackDict, "reference", referenceDict); + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilder.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilder.java index 46eb79aa72..3b45585796 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilder.java @@ -25,7 +25,6 @@ package org.broadinstitute.sting.gatk.refdata.tracks; import net.sf.samtools.SAMSequenceDictionary; -import net.sf.samtools.SAMSequenceRecord; import org.apache.log4j.Logger; import org.broad.tribble.FeatureCodec; import org.broad.tribble.FeatureSource; @@ -41,7 +40,6 @@ import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet; import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet.RMDStorageType; import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.SequenceDictionaryUtils; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -52,16 +50,11 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - /** - * - * @author aaron + * + * @author aaron * ` * Class RMDTrackBuilder * @@ -76,9 +69,6 @@ public class RMDTrackBuilder { // extends PluginManager { private final static Logger logger = Logger.getLogger(RMDTrackBuilder.class); public final static boolean MEASURE_TRIBBLE_QUERY_PERFORMANCE = false; - // a constant we use for marking sequence dictionary entries in the Tribble index property list - public static final String SequenceDictionaryPropertyPredicate = "DICT:"; - // private sequence dictionary we use to set our tracks with private SAMSequenceDictionary dict = null; @@ -210,13 +200,19 @@ private Pair getFeatureSource(FeatureManag try { logger.info(String.format(" Index for %s has size in bytes %d", inputFile, Sizeof.getObjectGraphSize(index))); } catch (ReviewedStingException e) { } - sequenceDictionary = getSequenceDictionaryFromProperties(index); + sequenceDictionary = IndexDictionaryUtils.getSequenceDictionaryFromProperties(index); // if we don't have a dictionary in the Tribble file, and we've set a dictionary for this builder, set it in the file if they match if (sequenceDictionary.size() == 0 && dict != null) { File indexFile = Tribble.indexFile(inputFile); - setIndexSequenceDictionary(inputFile,index,dict,indexFile,true); - sequenceDictionary = getSequenceDictionaryFromProperties(index); + validateAndUpdateIndexSequenceDictionary(inputFile, index, dict); + try { // re-write the index + writeIndexToDisk(index,indexFile,new FSLockWithShared(indexFile)); + } catch (IOException e) { + logger.warn("Unable to update index with the sequence dictionary for file " + indexFile + "; this will not effect your run of the GATK"); + } + + sequenceDictionary = IndexDictionaryUtils.getSequenceDictionaryFromProperties(index); } if ( MEASURE_TRIBBLE_QUERY_PERFORMANCE ) @@ -363,96 +359,31 @@ private Index createIndexInMemory(File inputFile, FeatureCodec codec) { // this can take a while, let them know what we're doing logger.info("Creating Tribble index in memory for file " + inputFile); Index idx = IndexFactory.createIndex(inputFile, codec, IndexFactory.IndexBalanceApproach.FOR_SEEK_TIME); - setIndexSequenceDictionary(inputFile, idx, dict, null, false); + validateAndUpdateIndexSequenceDictionary(inputFile, idx, dict); return idx; } - - // --------------------------------------------------------------------------------------------------------- - // static functions to work with the sequence dictionaries of indexes - // --------------------------------------------------------------------------------------------------------- - - /** - * get the sequence dictionary from the track, if available. If not, make it from the contig list that is always in the index - * @param index the index file to use - * @return a SAMSequenceDictionary if available, null if unavailable - */ - public static SAMSequenceDictionary getSequenceDictionaryFromProperties(Index index) { - SAMSequenceDictionary dict = new SAMSequenceDictionary(); - for (Map.Entry entry : index.getProperties().entrySet()) { - if (entry.getKey().startsWith(SequenceDictionaryPropertyPredicate)) - dict.addSequence(new SAMSequenceRecord(entry.getKey().substring(SequenceDictionaryPropertyPredicate.length() , entry.getKey().length()), - Integer.valueOf(entry.getValue()))); - } - return dict; - } - - /** - * create the sequence dictionary with the contig list; a backup approach - * @param index the index file to use - * @param dict the sequence dictionary to add contigs to - * @return the filled-in sequence dictionary - */ - private static SAMSequenceDictionary createSequenceDictionaryFromContigList(Index index, SAMSequenceDictionary dict) { - LinkedHashSet seqNames = index.getSequenceNames(); - if (seqNames == null) { - return dict; - } - for (String name : seqNames) { - SAMSequenceRecord seq = new SAMSequenceRecord(name, 0); - dict.addSequence(seq); - } - return dict; - } - /** * set the sequence dictionary of the track. This function checks that the contig listing of the underlying file is compatible. * (that each contig in the index is in the sequence dictionary). * @param inputFile for proper error message formatting. * @param dict the sequence dictionary * @param index the index file - * @param indexFile the index file - * @param rewriteIndex should we rewrite the index when we're done? - * */ - public void setIndexSequenceDictionary(File inputFile, Index index, SAMSequenceDictionary dict, File indexFile, boolean rewriteIndex) { - if (dict == null) return; - - SAMSequenceDictionary currentDict = createSequenceDictionaryFromContigList(index, new SAMSequenceDictionary()); + public void validateAndUpdateIndexSequenceDictionary(final File inputFile, final Index index, final SAMSequenceDictionary dict) { + if (dict == null) throw new ReviewedStingException("BUG: dict cannot be null"); // check that every contig in the RMD contig list is at least in the sequence dictionary we're being asked to set - validateTrackSequenceDictionary(inputFile.getAbsolutePath(),currentDict,dict); - - for (SAMSequenceRecord seq : currentDict.getSequences()) { - if (dict.getSequence(seq.getSequenceName()) == null) - continue; - index.addProperty(SequenceDictionaryPropertyPredicate + dict.getSequence(seq.getSequenceName()).getSequenceName(), String.valueOf(dict.getSequence(seq.getSequenceName()).getSequenceLength())); - } - // re-write the index - if (rewriteIndex) try { - writeIndexToDisk(index,indexFile,new FSLockWithShared(indexFile)); - } catch (IOException e) { - logger.warn("Unable to update index with the sequence dictionary for file " + indexFile + "; this will not effect your run of the GATK"); - } - } + final SAMSequenceDictionary currentDict = IndexDictionaryUtils.createSequenceDictionaryFromContigList(index, new SAMSequenceDictionary()); + validateTrackSequenceDictionary(inputFile.getAbsolutePath(), currentDict, dict); - public void setIndexSequenceDictionary(Index index, SAMSequenceDictionary dict) { - for ( SAMSequenceRecord seq : dict.getSequences() ) { - final String contig = SequenceDictionaryPropertyPredicate + seq.getSequenceName(); - final String length = String.valueOf(seq.getSequenceLength()); - index.addProperty(contig,length); - } + // actually update the dictionary in the index + IndexDictionaryUtils.setIndexSequenceDictionary(index, dict); } - public void validateTrackSequenceDictionary(String trackName, SAMSequenceDictionary trackDict, SAMSequenceDictionary referenceDict) { - // if the sequence dictionary is empty (as well as null which means it doesn't have a dictionary), skip validation - if (trackDict == null || trackDict.size() == 0) - logger.info("Track " + trackName + " doesn't have a sequence dictionary built in, skipping dictionary validation"); - else { - Set trackSequences = new TreeSet(); - for (SAMSequenceRecord dictionaryEntry : trackDict.getSequences()) - trackSequences.add(dictionaryEntry.getSequenceName()); - SequenceDictionaryUtils.validateDictionaries(logger, validationExclusionType, trackName, trackDict, "reference", referenceDict); - } + public void validateTrackSequenceDictionary(final String trackName, + final SAMSequenceDictionary trackDict, + final SAMSequenceDictionary referenceDict ) { + IndexDictionaryUtils.validateTrackSequenceDictionary(trackName, trackDict, referenceDict, validationExclusionType); } } 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 c88c7c3c47..10261112ca 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java @@ -25,6 +25,7 @@ package org.broadinstitute.sting.gatk.walkers; +import net.sf.samtools.SAMSequenceDictionary; import org.apache.log4j.Logger; import org.broadinstitute.sting.gatk.CommandLineGATK; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; @@ -77,6 +78,15 @@ protected GenomeAnalysisEngine getToolkit() { return toolkit; } + /** + * Gets the master sequence dictionary for this walker + * @link GenomeAnalysisEngine.getMasterSequenceDictionary + * @return + */ + protected SAMSequenceDictionary getMasterSequenceDictionary() { + return getToolkit().getMasterSequenceDictionary(); + } + /** * (conceptual static) method that states whether you want to see reads piling up at a locus * that contain a deletion at the locus. 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 1c76a21eac..a932d44ed2 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 @@ -99,7 +99,7 @@ public void initialize() { final VCFHeader vcfHeader = new VCFHeader(metaData, samples); - writer = new StandardVCFWriter(file, false); + writer = new StandardVCFWriter(file, getMasterSequenceDictionary(), false); writer.writeHeader(vcfHeader); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/RandomlySplitVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/RandomlySplitVariants.java index 1fefd20fc0..fa50938397 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/RandomlySplitVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/RandomlySplitVariants.java @@ -75,7 +75,7 @@ public void initialize() { hInfo.addAll(VCFUtils.getHeaderFields(getToolkit(), inputNames)); vcfWriter1.writeHeader(new VCFHeader(hInfo, samples)); - vcfWriter2 = new StandardVCFWriter(file2, true); + vcfWriter2 = new StandardVCFWriter(file2, getMasterSequenceDictionary(), true); vcfWriter2.writeHeader(new VCFHeader(hInfo, samples)); } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java index 4ae87ddcb1..71ec4ce1be 100644 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/IndexingVCFWriter.java @@ -24,6 +24,9 @@ package org.broadinstitute.sting.utils.codecs.vcf; +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; @@ -31,7 +34,9 @@ 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 org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.*; @@ -41,21 +46,24 @@ */ public abstract class IndexingVCFWriter implements VCFWriter { final private String name; + private final SAMSequenceDictionary refDict; - private File indexFile = null; private OutputStream outputStream; private PositionalStream positionalStream = null; private DynamicIndexCreator indexer = null; private LittleEndianOutputStream idxStream = null; - protected IndexingVCFWriter(String name, File location, OutputStream output, boolean enableOnTheFlyIndexing) { + @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 ) { - indexFile = Tribble.indexFile(location); try { - idxStream = new LittleEndianOutputStream(new FileOutputStream(indexFile)); + 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()); @@ -66,15 +74,16 @@ protected IndexingVCFWriter(String name, File location, OutputStream output, boo idxStream = null; indexer = null; positionalStream = null; - indexFile = null; } } } + @Ensures("result != null") public OutputStream getOutputStream() { return outputStream; } + @Ensures("result != null") public String getStreamName() { return name; } @@ -89,6 +98,7 @@ public void close() { if ( indexer != null ) { try { Index index = indexer.finalizeIndex(positionalStream.getPosition()); + IndexDictionaryUtils.setIndexSequenceDictionary(index, refDict); index.write(idxStream); idxStream.close(); } catch (IOException e) { @@ -108,15 +118,27 @@ public void add(VariantContext vc) { indexer.addFeature(vc, positionalStream.getPosition()); } - protected static final String writerName(File location, OutputStream stream) { + /** + * 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(); } - protected static OutputStream openOutputStream(File location) { + /** + * 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 ReviewedStingException("Unable to create VCF file at location: " + location, e); + throw new UserException.CouldNotCreateOutputFile(location, "Unable to create VCF writer", e); } } } 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 7cba5fc3e3..0da7a100fd 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 @@ -24,6 +24,7 @@ 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; @@ -62,21 +63,12 @@ public class StandardVCFWriter extends IndexingVCFWriter { * * @param location the file location to write to */ - public StandardVCFWriter(File location) { - this(location, openOutputStream(location), true, false); + public StandardVCFWriter(final File location, final SAMSequenceDictionary refDict) { + this(location, openOutputStream(location), refDict, true, false); } - public StandardVCFWriter(File location, boolean enableOnTheFlyIndexing) { - this(location, openOutputStream(location), enableOnTheFlyIndexing, false); - } - - /** - * create a VCF writer, given a stream to write to - * - * @param output the file location to write to - */ - public StandardVCFWriter(OutputStream output) { - this(output, false); + public StandardVCFWriter(File location, final SAMSequenceDictionary refDict, boolean enableOnTheFlyIndexing) { + this(location, openOutputStream(location), refDict, enableOnTheFlyIndexing, false); } /** @@ -85,12 +77,12 @@ public StandardVCFWriter(OutputStream output) { * @param output the file location to write to * @param doNotWriteGenotypes do not write genotypes */ - public StandardVCFWriter(OutputStream output, boolean doNotWriteGenotypes) { - this(null, output, false, doNotWriteGenotypes); + public StandardVCFWriter(final OutputStream output, final SAMSequenceDictionary refDict, final boolean doNotWriteGenotypes) { + this(null, output, refDict, false, doNotWriteGenotypes); } - public StandardVCFWriter(File location, OutputStream output, boolean enableOnTheFlyIndexing, boolean doNotWriteGenotypes) { - super(writerName(location, output), location, output, enableOnTheFlyIndexing); + public StandardVCFWriter(final File location, final OutputStream output, final SAMSequenceDictionary refDict, final boolean enableOnTheFlyIndexing, boolean doNotWriteGenotypes) { + super(writerName(location, output), location, output, refDict, enableOnTheFlyIndexing); mWriter = new BufferedWriter(new OutputStreamWriter(getOutputStream())); // todo -- fix buffer size this.doNotWriteGenotypes = doNotWriteGenotypes; } diff --git a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java index 7ff6e27a26..18fae18c40 100644 --- a/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java +++ b/public/java/src/org/broadinstitute/sting/utils/gcf/GCFWriter.java @@ -24,6 +24,7 @@ package org.broadinstitute.sting.utils.gcf; +import net.sf.samtools.SAMSequenceDictionary; import org.broadinstitute.sting.utils.codecs.vcf.IndexingVCFWriter; import org.broadinstitute.sting.utils.codecs.vcf.VCFHeader; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -52,8 +53,8 @@ public class GCFWriter extends IndexingVCFWriter { // // -------------------------------------------------------------------------------- - public GCFWriter(File location, boolean enableOnTheFlyIndexing, boolean doNotWriteGenotypes) { - super(writerName(location, null), location, null, enableOnTheFlyIndexing); + public GCFWriter(final File location, final SAMSequenceDictionary refDict, boolean enableOnTheFlyIndexing, boolean doNotWriteGenotypes) { + super(writerName(location, null), location, null, refDict, enableOnTheFlyIndexing); this.location = location; this.skipGenotypes = doNotWriteGenotypes; diff --git a/public/java/test/org/broadinstitute/sting/WalkerTest.java b/public/java/test/org/broadinstitute/sting/WalkerTest.java index 386c17659c..a1817e3c70 100755 --- a/public/java/test/org/broadinstitute/sting/WalkerTest.java +++ b/public/java/test/org/broadinstitute/sting/WalkerTest.java @@ -75,7 +75,7 @@ public static void assertOnDiskIndexEqualToNewlyCreatedIndex(final File indexFil Index indexFromOutputFile = IndexFactory.createIndex(resultFile, new VCFCodec()); Index dynamicIndex = IndexFactory.loadIndex(indexFile.getAbsolutePath()); - if ( ! indexFromOutputFile.equalsIgnoreTimestamp(dynamicIndex) ) { + if ( ! indexFromOutputFile.equalsIgnoreProperties(dynamicIndex) ) { Assert.fail(String.format("Index on disk from indexing on the fly not equal to the index created after the run completed. FileIndex %s vs. on-the-fly %s%n", indexFromOutputFile.getProperties(), dynamicIndex.getProperties())); diff --git a/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilderUnitTest.java index ae218e8980..724c343e43 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/RMDTrackBuilderUnitTest.java @@ -29,7 +29,6 @@ import net.sf.samtools.SAMSequenceDictionary; import org.broad.tribble.Tribble; import org.broad.tribble.index.Index; -import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrackBuilder; import org.broadinstitute.sting.utils.codecs.vcf.VCF3Codec; import org.broadinstitute.sting.utils.codecs.vcf.VCFCodec; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -45,7 +44,6 @@ import java.io.*; import java.nio.channels.FileChannel; -import java.util.Map; /** @@ -164,7 +162,7 @@ public void testBuilderIndexSequenceDictionary() { try { Index idx = builder.loadIndex(vcfFile, new VCFCodec()); // catch any exception; this call should pass correctly - SAMSequenceDictionary dict = RMDTrackBuilder.getSequenceDictionaryFromProperties(idx); + SAMSequenceDictionary dict = IndexDictionaryUtils.getSequenceDictionaryFromProperties(idx); } catch (IOException e) { e.printStackTrace(); Assert.fail("IO exception unexpected" + e.getMessage()); diff --git a/public/java/test/org/broadinstitute/sting/utils/codecs/vcf/IndexFactoryUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/codecs/vcf/IndexFactoryUnitTest.java index 1809ab778c..55bd4783ba 100755 --- a/public/java/test/org/broadinstitute/sting/utils/codecs/vcf/IndexFactoryUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/codecs/vcf/IndexFactoryUnitTest.java @@ -1,27 +1,45 @@ package org.broadinstitute.sting.utils.codecs.vcf; +import net.sf.samtools.SAMSequenceDictionary; import org.broad.tribble.Tribble; import org.broad.tribble.index.*; import org.broad.tribble.iterators.CloseableTribbleIterator; import org.broad.tribble.source.BasicFeatureSource; +import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.WalkerTest; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.testng.Assert; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.*; /** * tests out the various functions in the index factory class */ -public class IndexFactoryUnitTest { +public class IndexFactoryUnitTest extends BaseTest { File inputFile = new File("public/testdata/HiSeq.10000.vcf"); File outputFile = new File("public/testdata/onTheFlyOutputTest.vcf"); File outputFileIndex = Tribble.indexFile(outputFile); + private SAMSequenceDictionary dict; + + @BeforeTest + public void setup() { + try { + dict = new CachingIndexedFastaSequenceFile(new File(b37KGReference)).getSequenceDictionary(); + } + catch(FileNotFoundException ex) { + throw new UserException.CouldNotReadInputFile(b37KGReference,ex); + } + } + // // test out scoring the indexes // @@ -37,7 +55,7 @@ public void testOnTheFlyIndexing1() throws IOException { BasicFeatureSource source = new BasicFeatureSource(inputFile.getAbsolutePath(), indexFromInputFile, new VCFCodec()); int counter = 0; - VCFWriter writer = new StandardVCFWriter(outputFile); + VCFWriter writer = new StandardVCFWriter(outputFile, dict); writer.writeHeader((VCFHeader)source.getHeader()); CloseableTribbleIterator it = source.iterator(); while (it.hasNext() && (counter++ < maxRecords || maxRecords == -1) ) { 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 e3a926fb95..a8e6593b13 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 @@ -38,12 +38,13 @@ public class VCFWriterUnitTest extends BaseTest { private Set additionalColumns = new HashSet(); private File fakeVCFFile = new File("FAKEVCFFILEFORTESTING.vcf"); private GenomeLocParser genomeLocParser; + private IndexedFastaSequenceFile seq; @BeforeClass public void beforeTests() { File referenceFile = new File(hg18Reference); try { - IndexedFastaSequenceFile seq = new CachingIndexedFastaSequenceFile(referenceFile); + seq = new CachingIndexedFastaSequenceFile(referenceFile); genomeLocParser = new GenomeLocParser(seq); } catch(FileNotFoundException ex) { @@ -55,7 +56,7 @@ public void beforeTests() { @Test public void testBasicWriteAndRead() { VCFHeader header = createFakeHeader(metaData,additionalColumns); - VCFWriter writer = new StandardVCFWriter(fakeVCFFile); + VCFWriter writer = new StandardVCFWriter(fakeVCFFile, seq.getSequenceDictionary()); writer.writeHeader(header); writer.add(createVC(header)); writer.add(createVC(header)); From 08ffb18b96b1d22f14c488cd71a1d02b5490f6a1 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 20 Sep 2011 11:02:51 -0400 Subject: [PATCH 044/363] Renaming datasets in the MDCP Making dataset names and files generated by the MDCP more uniform. --- .../MethodsDevelopmentCallingPipeline.scala | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) 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 80bfe03d12..637d3edca7 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala @@ -98,40 +98,52 @@ class MethodsDevelopmentCallingPipeline extends QScript { // BUGBUG: We no longer support b36/hg18 because several of the necessary files aren't available aligned to those references val targetDataSets: Map[String, Target] = Map( - "HiSeq" -> new Target("NA12878.HiSeq", hg18, dbSNP_hg18_129, hapmap_hg18, + "NA12878_wgs_b37" -> new Target("NA12878.HiSeq.WGS.b37", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, + new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.HiSeq.WGS.bwa.cleaned.recal.hg19.bam"), + new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.HiSeq19.filtered.vcf"), + "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.noChrY.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), + "NA12878_wgs_decoy" -> new Target("NA12878.HiSeq.WGS.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, + new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WGS.b37_decoy.NA12878.clean.dedup.recal.bam"), + new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.HiSeq19.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** + "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.noChrY.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), + "NA12878_wgs_hg18" -> new Target("NA12878.HiSeq.WGS.hg18", hg18, dbSNP_hg18_129, hapmap_hg18, "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/1000GenomesProcessingPaper/wgs.v13/HiSeq.WGS.cleaned.indels.10.mask", new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.HiSeq.WGS.bwa.cleaned.recal.bam"), new File("/home/radon01/depristo/work/oneOffProjects/1000GenomesProcessingPaper/wgs.v13/HiSeq.WGS.cleaned.ug.snpfiltered.indelfiltered.vcf"), "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg18.intervals", 2.14, 99.0, !lowPass, !exome, 1), - "HiSeq19" -> new Target("NA12878.HiSeq19", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, - new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.HiSeq.WGS.bwa.cleaned.recal.hg19.bam"), - new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.HiSeq19.filtered.vcf"), - "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.noChrY.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), - "GA2hg19" -> new Target("NA12878.GA2.hg19", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, - new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.GA2.WGS.bwa.cleaned.hg19.bam"), - new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.GA2.hg19.filtered.vcf"), - "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), - "WEx" -> new Target("NA12878.WEx", hg18, dbSNP_hg18_129, hapmap_hg18, + "NA12878_wex_b37" -> new Target("NA12878.HiSeq.WEx.b37", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, + new File("/seq/picard_aggregation/C339/NA12878/v3/NA12878.bam"), + new File("/humgen/gsa-hpprojects/dev/carneiro/trio/analysis/snps/CEUTrio.WEx.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** + "/seq/references/HybSelOligos/whole_exome_agilent_1.1_refseq_plus_3_boosters/whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.interval_list", 3.3, 98.0, !lowPass, exome, 1), + "NA12878_wex_hg18" -> new Target("NA12878.HiSeq.WEx.hg18", hg18, dbSNP_hg18_129, hapmap_hg18, "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/1000GenomesProcessingPaper/wgs.v13/GA2.WEx.cleaned.indels.10.mask", new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.WEx.cleaned.recal.bam"), new File("/home/radon01/depristo/work/oneOffProjects/1000GenomesProcessingPaper/wgs.v13/GA2.WEx.cleaned.ug.snpfiltered.indelfiltered.vcf"), "/seq/references/HybSelOligos/whole_exome_agilent_1.1_refseq_plus_3_boosters/whole_exome_agilent_1.1_refseq_plus_3_boosters.targets.interval_list", 3.3, 98.0, !lowPass, exome, 1), - "WExTrio" -> new Target("CEUTrio.WEx", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, + "NA12878_wex_decoy" -> new Target("NA12878.HiSeq.WEx.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, + new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WEx.b37_decoy.NA12878.clean.dedup.recal.bam"), + new File("/humgen/gsa-hpprojects/dev/carneiro/trio/analysis/snps/CEUTrio.WEx.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** + "/seq/references/HybSelOligos/whole_exome_agilent_1.1_refseq_plus_3_boosters/whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.interval_list", 3.3, 98.0, !lowPass, exome, 1), + "CEUTrio_wex_b37" -> new Target("CEUTrio.HiSeq.WEx.b37", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WEx.bwa.cleaned.recal.bam"), new File("/humgen/gsa-hpprojects/dev/carneiro/trio/analysis/snps/CEUTrio.WEx.filtered.vcf"), "/seq/references/HybSelOligos/whole_exome_agilent_1.1_refseq_plus_3_boosters/whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.interval_list", 3.3, 98.0, !lowPass, exome, 3), - "WGSTrio" -> new Target("CEUTrio.WGS", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, + "CEUTrio_wgs_b37" -> new Target("CEUTrio.HiSeq.WGS.b37", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WGS.bwa.cleaned.recal.bam"), new File("/humgen/gsa-hpprojects/dev/carneiro/trio/analysis/snps/CEUTrio.WEx.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg19.intervals", 2.3, 99.0, !lowPass, !exome, 3), - "WExTrioDecoy" -> new Target("CEUTrio.HiSeq.WEx.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, + "CEUTrio_wex_decoy" -> new Target("CEUTrio.HiSeq.WEx.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WEx.b37_decoy.list"), new File("/humgen/gsa-hpprojects/dev/carneiro/trio/analysis/snps/CEUTrio.WEx.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** "/seq/references/HybSelOligos/whole_exome_agilent_1.1_refseq_plus_3_boosters/whole_exome_agilent_1.1_refseq_plus_3_boosters.Homo_sapiens_assembly19.targets.interval_list", 3.3, 98.0, !lowPass, exome, 3), - "WGSTrioDecoy" -> new Target("CEUTrio.HiSeq.WGS.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, + "CEUTrio_wgs_decoy" -> new Target("CEUTrio.HiSeq.WGS.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WGS.b37_decoy.list"), new File("/humgen/gsa-hpprojects/dev/carneiro/trio/analysis/snps/CEUTrio.WEx.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg19.intervals", 2.3, 99.0, !lowPass, !exome, 3), + "GA2hg19" -> new Target("NA12878.GA2.hg19", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, + new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.GA2.WGS.bwa.cleaned.hg19.bam"), + new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.GA2.hg19.filtered.vcf"), + "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), "FIN" -> new Target("FIN", b37, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/1kg/processing/pipeline_test_bams/FIN.79sample.Nov2010.chr20.bam"), new File("/humgen/gsa-hpprojects/dev/data/AugChr20Calls_v4_3state/ALL.august.v4.chr20.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** From a1b4cafe7a63ecbeea9b06a94445f6bbf925d5e1 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 20 Sep 2011 13:59:59 -0400 Subject: [PATCH 045/363] Bug fix for NPE when timer wasn't initialized --- .../broadinstitute/sting/gatk/traversals/TraversalEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 27fd173cba..c6321e2ad7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java @@ -358,7 +358,7 @@ private final double calculateFractionGenomeTargetCompleted(ProcessingHistory la public void printOnTraversalDone() { printProgress(null, null, true); - final double elapsed = timer.getElapsedTime(); + final double elapsed = timer == null ? 0 : timer.getElapsedTime(); ReadMetrics cumulativeMetrics = engine.getCumulativeMetrics(); From 827c942c8027d7888b29ddff8399834b7bcf94e4 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 20 Sep 2011 14:01:14 -0400 Subject: [PATCH 046/363] Rev tribble --- .../{tribble-24.jar => tribble-25.jar} | Bin 299210 -> 305986 bytes .../{tribble-24.xml => tribble-25.xml} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename settings/repository/org.broad/{tribble-24.jar => tribble-25.jar} (89%) rename settings/repository/org.broad/{tribble-24.xml => tribble-25.xml} (51%) diff --git a/settings/repository/org.broad/tribble-24.jar b/settings/repository/org.broad/tribble-25.jar similarity index 89% rename from settings/repository/org.broad/tribble-24.jar rename to settings/repository/org.broad/tribble-25.jar index b1c39e60a14ce895e515cadb07c356d6d428bbc4..6d467ba3fdd6e6285e8244a87aefa4a0d232cfe1 100644 GIT binary patch delta 11585 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 HISu{?jA+l@ delta 11458 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 I_~7V&0me&b8~^|S diff --git a/settings/repository/org.broad/tribble-24.xml b/settings/repository/org.broad/tribble-25.xml similarity index 51% rename from settings/repository/org.broad/tribble-24.xml rename to settings/repository/org.broad/tribble-25.xml index 9b2b967f81..ed7a1fd693 100644 --- a/settings/repository/org.broad/tribble-24.xml +++ b/settings/repository/org.broad/tribble-25.xml @@ -1,3 +1,3 @@ - + From a97e039a6251de3df5a79187ea286666b1345216 Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Tue, 20 Sep 2011 14:29:39 -0400 Subject: [PATCH 047/363] Thanks to Chris for instructing me to use VCFExtractIntervals to get proper scattering of Variant Annotation From bffd3cca6fe26fe31a6fa5a33745dcaee0139c56 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 20 Sep 2011 15:07:06 -0400 Subject: [PATCH 048/363] Bug fix for reduced read; only adds regular bases for calculation -- No longer passes on deletions for genotyping --- .../walkers/genotyper/DiploidSNPGenotypeLikelihoods.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 5f6865d042..ec180f0cdf 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 @@ -276,8 +276,11 @@ public int add(PileupElement elt, boolean ignoreBadBases, boolean capBaseQualsAt if ( elt.isReducedRead() ) { // reduced read representation byte qual = elt.getReducedQual(); - add(obsBase, qual, (byte)0, (byte)0, elt.getReducedCount()); // fast calculation of n identical likelihoods - return elt.getReducedCount(); // we added nObs bases here + if ( BaseUtils.isRegularBase( elt.getBase() )) { + add(obsBase, qual, (byte)0, (byte)0, elt.getReducedCount()); // fast calculation of n identical likelihoods + return elt.getReducedCount(); // 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; From 3b9314aecf0f14294bef5445d970aa8022e1bf5a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 20 Sep 2011 20:42:18 -0400 Subject: [PATCH 049/363] Max fraction of mismatch test for debugging -- Useful example for individuals who want to compute mismatches between a read and the reference. From 48c413fee899082d3edf05ecc9c3381e4ab52a9e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 20 Sep 2011 21:28:31 -0400 Subject: [PATCH 050/363] Now throws an error when the mismatch fraction is too high From ac4f2d6d34145ea907572cc0112e6a163401e357 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 21 Sep 2011 00:49:50 -0400 Subject: [PATCH 051/363] Fixing choppy consensus reads When the consensus read had holes in the middle, the consensus was being finalized but not properly reinitialized. It was restarting with the old coordinates of the finalized consensus, misaligning following bases. From 34f435565ca294be93792e6998243116bf14f755 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 21 Sep 2011 10:16:17 -0400 Subject: [PATCH 052/363] Accidentally committed unclean tribble jar to repo --- settings/repository/org.broad/tribble-25.jar | Bin 305986 -> 299110 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/settings/repository/org.broad/tribble-25.jar b/settings/repository/org.broad/tribble-25.jar index 6d467ba3fdd6e6285e8244a87aefa4a0d232cfe1..e260764a5b7e4c676c5b260010ccad0dd46becfe 100644 GIT binary patch delta 5549 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 From 174859fc68c434ee568884ea9d9e53541070b24a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 21 Sep 2011 11:14:54 -0400 Subject: [PATCH 053/363] Don't allow whitespace in the INFO field --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 624d06a710..18646b0578 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 @@ -215,7 +215,7 @@ private Feature reallyDecode(String line) { 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 != null && !header.hasGenotypingData())) && nParts != NUM_STANDARD_FIELDS) || + 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); @@ -345,6 +345,9 @@ private Map parseInfo(String infoField, String id) { 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 infoValueSplitSize = ParsingUtils.split(infoField, infoValueArray, VCFConstants.INFO_FIELD_SEPARATOR_CHAR); for (int i = 0; i < infoValueSplitSize; i++) { String key; From 9f6f0c443c1288a5de622a95dc346f46cfed094b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 21 Sep 2011 15:25:01 -0400 Subject: [PATCH 054/363] Marginally cleaner isVCFStream() function -- cleanup trying to debug minor bug. Failed to fix the bug, but the code is nicer now --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 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 18646b0578..e8d1dc6d61 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 @@ -601,12 +601,15 @@ public final static boolean canDecodeFile(final File potentialInput, final Strin private final static boolean isVCFStream(final InputStream stream, final String MAGIC_HEADER_LINE) { try { byte[] buff = new byte[MAGIC_HEADER_LINE.length()]; - stream.read(buff, 0, MAGIC_HEADER_LINE.length()); - String firstLine = new String(buff); - stream.close(); - return firstLine.startsWith(MAGIC_HEADER_LINE); + 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; + } finally { + try { stream.close(); } catch ( IOException e ) {} } } } From 6bcfce225f42e31a03d721b119410da7c9063f5d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 21 Sep 2011 15:39:19 -0400 Subject: [PATCH 055/363] Fix for dynamic type determination for bgzip files -- GZipInputStream handles bgzip files under linux, but not mac -- Added BlockCompressedInputStream test as well, which works properly on bgzip files --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 6 +++++- .../sting/gatk/refdata/tracks/FeatureManagerUnitTest.java | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) 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 e8d1dc6d61..83c7083d05 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 @@ -6,6 +6,7 @@ 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.gatk.refdata.SelfScopingFeatureCodec; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -590,7 +591,8 @@ protected static int clipAlleles(int position, String ref, List unclippe 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 GZIPInputStream(new FileInputStream(potentialInput)), MAGIC_HEADER_LINE) || + isVCFStream(new BlockCompressedInputStream(new FileInputStream(potentialInput)), MAGIC_HEADER_LINE); } catch ( FileNotFoundException e ) { return false; } catch ( IOException e ) { @@ -608,6 +610,8 @@ private final static boolean isVCFStream(final InputStream stream, final String // 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/gatk/refdata/tracks/FeatureManagerUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManagerUnitTest.java index bae8e99edd..e8799e2aba 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManagerUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/refdata/tracks/FeatureManagerUnitTest.java @@ -56,6 +56,7 @@ public class FeatureManagerUnitTest extends BaseTest { private static final File VCF3_FILE = new File(validationDataLocation + "vcfexample3.vcf"); private static final File VCF4_FILE = new File(testDir + "HiSeq.10000.vcf"); private static final File VCF4_FILE_GZ = new File(testDir + "HiSeq.10000.vcf.gz"); + private static final File VCF4_FILE_BGZIP = new File(testDir + "HiSeq.10000.bgzip.vcf.gz"); private FeatureManager manager; private GenomeLocParser genomeLocParser; @@ -109,6 +110,7 @@ public Object[][] createTests() { new FMTest(VariantContext.class, VCF3Codec.class, "VCF3", VCF3_FILE); new FMTest(VariantContext.class, VCFCodec.class, "VCF", VCF4_FILE); new FMTest(VariantContext.class, VCFCodec.class, "VCF", VCF4_FILE_GZ); + new FMTest(VariantContext.class, VCFCodec.class, "VCF", VCF4_FILE_BGZIP); new FMTest(TableFeature.class, BedTableCodec.class, "bedtable", null); return FMTest.getTests(FMTest.class); } From c6ba94471985e3956b2108063c9f65d350d170cb Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 21 Sep 2011 15:39:45 -0400 Subject: [PATCH 056/363] Adding bgzip vcf file for unit tests --- public/testdata/HiSeq.10000.bgzip.vcf.gz | Bin 0 -> 509234 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/testdata/HiSeq.10000.bgzip.vcf.gz diff --git a/public/testdata/HiSeq.10000.bgzip.vcf.gz b/public/testdata/HiSeq.10000.bgzip.vcf.gz new file mode 100644 index 0000000000000000000000000000000000000000..3f2b9bf14a3e046fe2b7dd71ffdbf23e6c521bd9 GIT binary patch literal 509234 zcmV(-K-|9{iwFb&00000{{{d;LjnL=LG68OZyZ;W?F+hksZ+dAV2w^ZP&F{=FwJhkg$~Yn@sA=YK9<(IXz1mEHNx zZ=d{lb9H+0=EKIUjJdhQ&)j|r{lr3lWO2Uvd2_xvy;=O*=hJgqlS%uYXX$yCo_fIZ z{P^=Mug@L!JU9M4$Ir76`@hb8;-3d){>!5upPamYaq`zk-#vTs==C?ZpZ5I^W^ud- z*t|df5r28O_#?@(_%F1VfBF-4*8b`46Q5nY-JIWk%!{8lSLdgnt{10^&)1u)2WJYKepPt{)|I?3buJKv;*5%dP&DG-kj6U@3-OqgT zvVErA^O-+xPTww`-~$(byu3L7b@7g`)aEUH?CINUy!E%dk~j3xXBY4PboXPPe%C(6 z%g20setCLxcN^Y6xTXitqt4I%Mlyc5ynHJHK6&JAXS}+(qIWU;5dP(7&%b>kJOB0Uo!>0p&^fxn-gYlz$uDVh`sTyp(RZ(P z`6|Mt$u4@j>c{_WM~-#%|H-u(KTKlAyVgZKLKhE&T1|KQ^C zUH@d`sL@gBUc&9iKY839Z<M4EqW#D>>Fdjjo71xkIyZ|~pH46M3wqWDKXpgMzx??5V$K@BJH2|pxxV2? zpS}Nl`T2VBm&=crpDxdT{YY<>o`R46>*>|mDV;0b=Vup-Z_a2ZuNF_vt~R{LJD+~~ z>6~But2dWdn`u$$)30$1=;+|%_>kac{z9L!xcTLB@kiWZRxb3nKP|2|@2@uRPjB!7 zKD5ue^9H`3ach74{?+1p(g=7?|Kj!@V&?DkohL2+_vY$ScJB5=U(8t9YgWx?@6Rsi z!9VNkS39)0##_Dp%paaiKeO#3z3q2rZ|P-k9=4_a^y|<$ef4Gb3E zx0hE>PH#?MUR}P~Twl{metAm2d;L%ra{c=9`_sQ|9@7s#{NaJC+5ETn*Qf4*eQ>k+ z`00W351&7>uH>@%%h{{VzpeiH>8sUGznre#oNrDqHg8vShELB|^f}AL-)RNmORpe) z=>^m;y@2$k7hu2mD{x=@6nyb}V88eZocrP{_|gmb;`%s$=>^y?uZ_O=0>1brlrOyi zYrgmjzW6(EU;Z6@`K$27?Xvsw@8FADB|FG}2Xzr6Wy^*{f0vHABm=bzthp56NQ@0Sbu@BhLx>1l|{POc@5 zvN?W8Up?&p{rVw7^ONh-PiLEp>yy*#>&?fX&VQvl{IePR*6Q8q_08$x>hkU5vx~RK z|MK>y>x)lI`EPrT^b#J>8?q0dK6?E>PVfRB@(-L?S5H2?r-#t5FFSFT-TIdz7Jc{i zW&6k1SEq0O_WbmtzLLkUpPhW%T)p3%Tz#ep@1LGOd+~hnMPd~{+ z03_-8au)FFw-~-{~_xp8op@XHZX0Z%)oH-+aD4wub%>8LyL@5B#lT+ieTK zW?KF6qwl_xllcDX^7E&YpU!DRPLO4j$F`i<$;ZpLoAcxU{^Hs5*C*e+dj0JCN3XwG z{B(NVzVprZFQ5JR?CVG0Ek11MBQF2r*Y}s#^xBT|s(kYxbPrOw=#3+gKRrJ`LG}$9 z))PFf$xX-b;_d0x+mnxH7xcDT3W6oM{&0CtH|O%|M03Q)-mJ{bU)g*-yFn)C7$u1X zz0&vZ=~mAFlv%&geF*=`#pSE-UOZ93=(2tONSBtrOCsxuklo=I{^|Vm2DK~QRr(Zu z$(qbL;bhLSGyI*8azF>ZNr&O%+4aZMn>Qae*C+I&!Tj{;0Um5{jFvSx5t=7iHkRMlWr~Or(f?bLmu+`-~HhannFU+?CMk!K)2I7J3nwM zzjLtSluP~P)uv|PmVf>F+vP9suU9|Uhg%!b?8E2xD^gpn`my@?&ATPnnU;??Z^wV@ z3?-}U6&+su?7g*VPciOMl5^M2i|R}A|GH*%Hsnxz3i&sxS9vAh{QIXXcpxMZooD$s z-bVUc)0^K8{N(NLwcpU1;49o0-~RT`eD(R?3?DQ8X)b^+p1(Nx%QugneDmY~zqxa7 zE}ilJOYYwPziq$ewsHOaAGrX(TmUuj=RFvLKQoA6xp?%LzwD4Ae_jOoZT@151NpN6 zL2h5?h5=aYU>O!2CS}o3CV!8+Z+n;jd-`JN?q6SizIxN$@E4zM&OXwwUj6P5U;pLD z7vCSfeDUh&*^{Fmzxnp)(RZ(p@CJ_rw{V0~(2kG?*(Up@Mk+>B_xdi4C*ef8uet=HDCPymJZVZK70@|ga(RWLkGC9P8Y>iZv# zZCyD-UzlT8%2zLbpre!HDu%CqcyjDk)_?Wt@$my|0)1VMp1xi`da{1fwyVp&GJEwtB{uuit#Js*R`rdA(KR3lT3$66IM3!=HyjQB1`m;CDn}WB3bMzS=pOF9}otGDSk=cU0dhYw1s6A z3&}y7OH#S&RwZo3DTc&(eXuymo5`&SI1QG?2`lGV96B)&r} zEo}^II*cTab1R44F|;crPDu31I&qv2e9{iRJKm?eSI6#%jyQ+QM0S+u{U{@N>7-`dvox$aXZ57?2WIatb+=Zie8(< z!W5w1ZVN*{6wRP8neGjQi6jg?iF5$n7jGeqr;XYQ6OG;*+Q3qdn&$rMu6qAU75|8m zEAH;O^E)ab&|CGa66n2JmMuEb1FF|(D^nhl$A+@>ex1$wfulXe38NQP@kgY$v5s+- znncy!>#SKHC)Kno0ly+zE$h{(ygRP0Dl0WQjjE)Rp*KDx4V|sv*PgUMq79782bT7P zbU_Ss)wWwx^Zf)Nbvr2tUH#}(kSglg^D!fHwQ^Q=g*Fv}(8;NTyHX1&0J1b>Xh;A$ zV{{%fZA!_sznvVO4d*@Nuw_yXQp2lk^h{aZ5VkV(jao#EAVvlr(%^Pj10K3Ue&I;u#LGE0(Zi{hXp*)AQO zEYCY)NeFTPoFiEry^ZaTxO^|`;!M6P$nGt7WFwn8&OrnTEJ*MxuXaCG5yX=RFenI_ zN@v#|K1?9X%7RonT9&Sg-MTP7x_j#4oy)#!SUfo#l_jAIAU>H54x;27;MA~7=0z9# z7{|=MFH17*#lq;53Un}!w8tb&@vG9iC^4u%M&B>>zPdP+bXOPe$mWWbQ+7=6Rqu^U zdhN+gBZ%ST2y}+uJd@TK*OmTjdKwKLS-nnl|!bBFD_BgFmorOUpJ(wqGB(-oUq@%0r z`SK4IQBE$_Gw-*zv-5y^59P(3K3B)p&w^qYw)s40iHtP!}A5->_GamK5wc z^1!3GtfWvq0$19%PUnZ{aRsc6#aVcZFvg&=8<70iBVGno*!g06Coqag7VZ9mD8Y}`6g*{kvJ6UM{u z_)r5E!ixXR$$x$QoDN*&s@Xuf4G542O$R9~^16|vr2xFZjw2;Nk&Yv!OZTh=4lY0h zn@zC3IL;5CZm?ej`q4=lf+}8km7<<9X9P0E%$|J_e2!cMlFJV!n>=Z!%BhV)qGx1j zD&1&@cy+rCJ{FR}JM#BkJ4Y0F4mBH(i!&ijf@l=Bxzf1&JHvY!mILUnjD)ZHtl?Z1-FDq5S!dUD?eI z;yg)?v&a@DBlSQiGAV7A{esl8bZ#AJha7@!-W!z8EbX%2=4x}+e9miR1<52H7FVTd zx46m{9CHCfMUf0z+%ExnA%30L6xSAxNI0b#$t^$%&}n2|O+ki)tfHxfY$S5E7J=ga zsA_uIR@@QxKSTk0Ni%1s?v-nm7@u*X2PBAQS=Y95QHcvM996+GZm)W^6oilfHHTt7 zZv_2--Ji@H&X`OK1+R`%5A5vnX6SKBvAgg)qQFn20G%^~nS^QDI@IJjMsNSxAGD9QwK!}z8bi$oB)#t%~ICxU9_sFxIRDCB%e zD)bPEWOw3>A|}+Pnn{J4Sgtf>$M6P8EsE=cOr0IB(73d8ba@~aiZW1LbkE33Bmcfo z#IwaR7_!|O&{tV|;BlZ}XEqBPMtS;=_r=1Oi@8QLiwp3nGhbBs6fRE3%2iYHB%G_dkLn({pdrC6if!n2-*lO;;T%;7!A#S%uj+vao})S+fkb zIS4q9;vi69ktmQO&1jo!$dd87^fpuI1a*j5_Ml+FxoNHLU`k0Cyg(5qdoBFL=3eiq z`-p$-mN{`Y78TIcva!Ha)ODf2t~-#ycCj=WU(KHx*Ar_?kTfpVZf0N?tb)8Ni)9oP zRXI&wLRYZ%d$Rjd=PgTefuQAWrii?0`A80852%(3r)GUlK2((`nu(0yv;Ypynu0kr zugM9)%H<=;YF0H<6m*8cbf8;OXiWjmgW$Q2D5VxQy=1FwJ8Z@Bwgif;YspQ&Ph>b~ z6Q_{_QXj&f4KKtgjsl?5bg7F?EyZqi9r?l(z&N;^3uV5hTT^lqfr5mF8#vV1n&~@V z1Y36;n?EO~DRGibd?2$o(Lqb6TMv6=SOCkI6!N4pN=3;xF~U7@9CAk8)aSKti#;57-$C_WLIi19T|JhCH7zcv}M97RLz!hjL&`L?OZ9`rE^3j>ADVGeRE<0&ELE zZ+UQCeX^_0L$wW-C+X#);8<7_5T!0e!pt)d6rt^gW4gpoYM*uXf z1ROFOBw3IWtikJ$yFUSpAk|wX{;_2w{;?%h&JLDLK6$$`WgOGNiw@Iaf>E4MZHCbE zov)m~5#073tOVbVyd)c_Dca1cs~HiFp_%}hx5B3CLLm)|d_M)F#U8#Fv+R4!8vSSi zLu!2%)^Vz6A2k>qgQ+;P)m-Kfw3i^YdJtf2R4fk3*vPJMs1_g3l@XX)gjJccetzv@4 z1%xgObb{10)D_v0UMAC-?7SpsvX{t{&HI!Pb~?#drQD6GszO+_?w#n6J4KnZEet~y zV*yc^rSByK3d`1m{47KW`|tLQL_dW`oRA5vf~56G z6tOs!;cZj4i4IHLN%Ttsl(xvR;yqc zvV~>mtazOOLm|`0H444T15hY$ViGJAIeP$hJry7$ovwpZ$Z0x@!6;?OL|RG?R|}=Q zC?_-GC6eJoIRY3u!4q+z8`U(VElq6FC|XT4?VjuyqbUp> zEjAU!kC=*J1tjI7w~kZDU!zDtUrnB)84$I4r|rsX0&F5Z8I}$=f=*0m7lKhNsGN1{ z&dGMDwBYxk()_e%6i`-_QnnUROG|eft)WUv6$$a-YGdY5Rny(JrMp>AZqYY3SF0Hx zkK;bR=oy}o)%v?PUG-W}XsN8E;#2V6_pRC$6yQDTij~8<$_6y^8v74BTxe%{sbKCtwSODABZ}YgWMtC$x->J zbJc!+oi|J;4y}q5LW2u6%rraM4j|dV5`0`{y0B}2@e_;~9XSF#qNE+`+QY*RTs5?YtL+x(oVX6a__no2&lRgc z=3E_9lq)9+R&!P^sM1i{K{C?cS+yn-;AXSZv34XW9RLSfN5k$AvYM#3p=d}izt^Hh zGGwcD5l`2KZuC@Ec1~>Ov{HzjcH4I!(dQvKSu@hV6+Fi`qm<$gOY79YDq z&ybJFMNfMMKB1C~MMwCXtU|Sbrj|92RA^$xs!?^SoZPnpxg`CLE{*=GTYnaX`se|i zT6NjF17YM<(}TFfk6Z_Ik##D@waNru&?s7$tW-qjk7#=6S$^AOqFry@*|f@pMVj^? zLp22X_6mGSin$04s$O{MX6k!f`D+6=k5-&ZQr8+f{u1gQz8fqx5Y3aZ;>Tcy^ zC@Igm2%n_rK%Q7ja7En$iB?D*jp3}2-b#FKyl6_)_bK%51KvV34h}MWOP51y@(B}J zdAD;-cRI+Lh6jpAjt=LxJesD0!X~2>feIS4M%rZ1n7rn4F}r4_OUj$B^C3hlkZ9lF+ zi(@4*7-QkB$u&m-?vb*~Q&P;yrF7Od^=ua1Mbo56P5Y-pM{CUaAt_M00yaaWD7#Vw zE)%=rYx49ZZ$<>(Y_Al4PA`oVs?c8CyAvta)=^!7920FC227X;fo_P*J-cYWiX1-j zk&3k0xpln7&Jk@wSo+lc-b0$kRCGI$8WcfVX<)P@4WU*H$>G6^4TPRwCoL;@MzR8Y zZ!0n@+kHrhFNH`TE+`{n=DVOXI;opDzzW7~++`ci5S6px#`a|qKRRpfJ+W>=9JZ!A z(lCZ0m71CqR$|5qu_qd9=3EI5(ea-9O?>1kDj+v$Uk&#L`9T|p3J=O26hB6;dXnah z+|5oWve#UbD(hGWOi0%s=O%KbyPn)o!a8dJ;C=5}lVcMEnU0J$w@*R|kEBt`I!O;G zf?qm^3{D5RM9&e;hA~Ge#bV&na&j?FU_EDmSXu~ET90uML~%t_(90|$+qr8Z*v=&s zJ*A!wCIQRP91HW^Ma8u1Jcx53 z(@fVnT6U>XP(xtJeiFQ)z6dck|Bluk_Bl#oXr9YdEg&`_$Pgluc)@8$8lI=%j#RPKO#XB$0;k#fJV1f*E7HNlhu=#hmYX!kyH(gx|1PxJh7%7JR0ZszI!#3E zYlF^fsa>MlCwK*^mc=fG8%px61iSahyaeKP5*n{`MnJXKZ5k+p`9e;;>p(pCx8Wp& zTe`yGdzF^T7QP!#daKtuMru?Wta7nQ(?Gdj&lomebM9|xR7OgcBfWePNTMRu0PV?j z*$*@&3H8sCqMU%VtznQq36F$O0d%}428A$r4Lg;LDyu_ zHHKYf)io8&KifMBc!y>R(aN+>OU5}DQyoim>D!D1F^sEv}X^K8q zg?l*W^6)f4^8{y!YZpl2lA=s~Ap*A#;wpQj;E|W38|L@l0zpV_;HNb!m~Godk{4ReMC zMx-GBfcFqu{sHkq??U*P8gU>UT)WTLpV5r%6^zA-;j3-;Y!w`xApcYmg&0Nv+3+?a zLpZEWoIeLBjT@d~zXO6ikSypXm#eQWLFx~~W7laPx|f;@rH9vYVk?BzNx?Pr7KhW+ zv;{UwCC2il=qfR{cqwDZ6=$l}WY9T@_{Gk`(I1d38t^leu)%laPFf|CHd~PS4DOZW zh`7X81VgJOKG3U?!xTF7d<-tZ!xz&C$>Yz&BIeQ+=cq6^QDD%h5#0 zN=Nzhu*)*b2sSo`jIs?|bE_~@t3+;PkK%0yNbQAj7I-j$$D0w@Hv9YXq9p_DbX$XIh_H3Yr8v{e^h$n5IJZa#? z&*}P?EDOwZ>|w4^1d)!w&fYUG_Sp`%G)E!UAt zv^7Yk)b{CXhBeT1`>JW3y0L4$k-ov8%|uAgOk800Q!QBNf-qcimm2Q@_px^(NGM%R zHd!L+V##xwkYVTOjOVoO`yBPog*|x^# zV406FSX+#Bj1Veu5|VpB7X3DjE|W`0eCYO)(~3q(?3T55tI`#afZ%Nc_!wKiST4e; zYx_X%K>_B2AApxi+vIv}5G};s6Ac+=*|=$Q7-`7Jp_Hgcya3{{4hM5PLc9~v1%G9nw1>FtKNquqz}`g7bmH-55+ znp=~eA9L(Ukeu&Dux@wjz`IoOsGO=-)+=)Waj6Xr!bA>kr(wXQwRDYRM{Fm_1>~sL zs3FHDgsrvP4N2<)&sOfS8}k!BRtFPwyJ04LoFtBN;tIj--LOp%pNmfG@USO)?(-bi zK*-X_{qlV+3U)m$qN;a{H1#mYaR;jR!nC2F`htwa_BLD3?>}Wjp^4@|O`eRZRbLd z(|zYg+9G60sG0dVGhU<@A3QfH<;coao0Q;eLQ$x3D=ykC8O5GW07J_hlFg)-Ry_*N zwq>J5GC$eC%^|w)s3!&ua!J;=VFGkf_7#a|P@A^5aJ!&N+eie1Rg5AFrpe6$CeAQLQD)$bau|kwb-NT~ zHkErR!R`DzctM@9(v~ErO|*HUxtx}(O|-tNr*ePLI)aw8lfcHt!ALscpa#f&mQ_2y zoNbeS6!V#Q~4N}w@a6Z(#1ii z4ZvH+_7>In)R|lK)kM=f{?(SQd-eeSs+Z(J&R|Uvrx{D=k#l>)LyP)yfTnMl{*oui zkgtgbpC}-1A&5q@r7uVF_hN#<6uZe1y-}(~Xb9Uk1J&xN6W4L4SoCTP+JOSbp)6Qp zPDEIqdE$)kw-rK^bU#sMa9%j=MrHcQ+=EHD=(XUo1%`tBcvKXyVbWbG>;{3dn0F=L za!RW)rc+IOhCIv~L*%4DbZlcIiIAyqOl+)(g4`g{BUBgU_E*WWIfmDe=5bQCCX-;g zkxI01*ZTDwI?nGqabvglaHxA*z{aj)<7w2eEKJ0;94EFxZH*? ztpig+1Z-pbJp1sT?W$>3KarcTQ3!c4AjNcyymYN9I2~(qLu+)Ut5ok9tZ@*S+exvs z18Wqx%tE0zMFF8fJW8aaVD4%vgV7ZskcHxfOLPYVXlBq^*e&%@%MXQH^~SN2Q6I=26G-hMt*V0ZjmzMtdG(m*iIykSC5#Xk(TzUNg5l3yK?3 zOdVTzwa&Jz`JvM_w{qCAgb+p(74?;raViOF7lluqc3}5|2Iy=oT_kxhWJZ#^PW~{M zwb`R}b_$pNnnCIQ-lI=oT%XWg2W^(N({+jgO*d5 zudX(RR!D`TnC~l+n_;by?repG(%HNo`D}{hNet7FRHP~_+FGM!Pc}&YE?RCtDXyC| zQ*DlA8+y(k52x>@ zr}f!3r6qSQxiUQiic{)RVY05~1#@ONGlOQ8#BQu)vSCb{5Rp4uL$c%sRz*yhiQEiD zh?Dz}wWt8vQZ3bsM`A#d+%CsHHc5poVv;FyNGpC+Qe&Ky?CnMJPAY_sTpl4o5G>e! z7KHhC+~#Hqg{jo#&|vRo@7>U>WaQb2R&dw1XD8++M6qG(#vT%A+ZjH1-BLiA*h|}> z`#$S1MeW0Jb!eWKSiuMoIok}2X0y;4jbLM+kiB}7w}IWEEXC8}ls0yvYyVDSGpbQM zi{0Cez%eRgd$n#7wQdr5O0(%pVvV}Iptq{`o!2m(N(RlfRfD4Lu8cD&;)qE*a_9IC z7Iw1(;yNPwFgj}{bKuP^^?jkN-zwq5wr95(y6cwYX47rsD2jqN>NN+-jF~vXu>|)7 zfhzg%Svr$JybQ}&`voDAq*%4-xz8dybgt&~h$!MWO3ccWKX^t~)#<=leUZ@_Owlvp zr%)_&++kP+2TZH!t=omtMN-_8z}=1RF!cN5nTa;1NuAPiI?^~YO%0zMN-BwY8f`C< zLK}kL#)aN@G(ccv*e}FxguBbzHL8hYYXl3LM5t4UudqyleY9iP$Le#(sqVQZw0E`h zc^IAF))K{5mof7&G2f$Hp41NQ#qC6agfZaFu|CPEq#AjF{^n~sBSS5UR@_OZ5UUtDJW&t zu;3&d(n08}22vAw1ly3B4tufHK*0n7WRwzp)^jw)=`e@auu0%d3$tYaT<~QJcT~hX zdVU8*u!XBF^zNh6sZi*&11r=T==Rb{QOdorF}ri1b`u6V2Fm*8{l%>OCPA5#+X+JT zXrw$5IiC#L>uo-{_K5~+@V1*9TJDj8odw))nY7&bOT9F~o@fY2tJ)e5y8fpG{zEbb zDnYTybkS+zbPftIbqczF0BdsYPtNs+r$cpr)} zP$)g1VK#Lhw}wF5;+7(-Xbp9Eeqd-l2R5_!qZ8yag6i$`EnBy#0mZ#XBMKWanNk zbR)>JtI(l?h(IF;r^(Gc;$;`E1Si1_bih>x$AB2UnEp$)y{XWTsfSjdRC;PvV?AEtIfB1(Z<~)+j(i~To`o&&A*?eZf$Q99Iw--X`(;(_E*a<$PTNZb*C+#l5kAuzl{&$;Nvkyv3}0wNB%BhT z7mkP{v$z2C)*CWLwoHepKLzKu+dtrDplWgmOTJExwOTTn8Ww9KohIP)I9+74l@VL9 z%3A#)Gg2AZX)P!?$ceyVD%{q-_b^Qd26o#%7kXSMq`Py|KKtg^S!Dx~eg$y(4r(8V zCJU#XE-02B2GE`(Naq=aCWy{bDSMI;aute3@@8zF4V_4yQjii!5u9|N!S|kwj}KXP z={1oe5h`tfsr8V8QfK5YTe-~2yFGZBNrvpZCUy#b?0qYUyR*jk6|uDX6V!{7G#8lM z2TN{=Cp~38Oo>UQtn?lkQ00<%oiNqOyjzDvS7lEQsiTRW#$By6ZjQZF8ycakN$Oye zOQ&6G9;RZXH5^K*a3>64a8V7j0se%Pvx?!YVVjJek}P68;%<_e93mqHh-o2HH-%hX z4(rw5vlF+)l51MB*z7QRVH`J?G{KZY8lAW^!-rwPW}Lhv{x=C0wIQ{^n(RbI&jK)Q zL@+ltW(kgTl>mxaKw%igYby~uCqmJet#HOCA8Ey@C`$>C!8>46y zwuv=~YoP5z5#x`C-Ag)R#bAek-IQ^0N%Ef2P|R;{2~!$)^RS$u+odQSh+3En89}1g zz62-pc#Wb^y_UWn1X%sy83v{(nK$ z*|;{BN>eI=)GOS;J4h?YEo+v?G!&i`={@^$A!HuM1_Rwh^4_N=QrX9WKC5}kHWusH znvjH$uhQ$PLPBg?N~pvz!pZ09s(=R+_uffIi44}V0C}a}h6KZezQyJmt-D9{du-PD zKog$yWdIeSDJ}uERlAC}?hPk@N5;c@@cWj^KqNRiPOguO7>e7M%W^R-Blsx-;|V-* zcJ{0Qw_6R_*{z`EvmU6YsQ{^hSfTkCJY-~O68!=8T*+i-Y0)dp+6}tj?IFX&HXizC zz^i8Ohq!RNLj-o~JGG6ZuwC1RWL3e5sNGD8?r}&?4-<2OU=(0ICNieoL=Tzp@Mlx| znR*h<1)WtIeP(jYdyR#k!Pn`Gw10BpLX^b8{~4S1^)zwM=L+k+q*enBf$Is>Vze81N{TuDbkcdhHnx!p2&`r3kyGl< z)MiIVociI5AM%poF~O2m>F?!P&g6k&f?Hn{;UGA!P+V&zBb73ei-s2$NDU-3bCNiKTtmMD1n8twr;{!@+q&(zi{{=P3NwHzULqjO zY?iI7stN1_zaB|~IVyQur?(3D2x#^yoo&q~w06#b=E{3ZvpXGT0F2$L21;#Vo#JjZ zD~CMcaQleou1@C!H<6jE6lT@I!_KMWd?SIpzc-R`M$$n2H|S}FW5=rGs|^wrriL82 z$mETJiArUu0|c^R=59E_TVbNt-2qAYJuCQ-Y))iX!)>U1a<@=XdjxWrBgusPf&H8! zgyaS(km$R?0whpKR>qoJQWClOJM&XJ$_+PNqDm@GY2)UtWg~9~>MAmmg*BUqk1pEv;w zUX={QcD@}xWFarUPpWDLZ~-1pht*c1Z`qmD?Tn0hc4j2ElMA?i(M$$HAsKYSgl7oT zw@_^gWnC>Dwa0%KHL;IwdY7!kq@8 zaZLST$|OyxU1DcHQOO+8MGzPg=C6kS$fyuqhbzpAqbg}69H?sZGf4Ydlwj|l^JLRf_l~3mERf^Wl&-m3Dput+wtIB%ftEF;9T@2CT@py zoj7D%w#_Dcho?+0xX45%?1#B^pg5>h?^!J*dYFbgWh&bGP#z^O3w@%6WeBCutw9TS z+_+?Is9@!iif?d}ol7$kf~M)J-0Ev`nhe$~$BBV^oZEm$KSE~}D>MAiiCLx8D1Nv+ znvw2ol7v$7mL}w|a>s5OdfbR$mO@LYu^)znnoiU$uFKR6PfN7fBMoIa%Cr(?Mk)Bl z;8v!;HPM)R_E?*pY{o#ODQ7G8`WSftG>7TbRcRyT4$g?xIZdNQ`A$meZ4Fw; z%B|Zy8i`c9l)>3n$c%*R8L)c``sD1ka&TF;-xY5A)4zv@^UR0VY$y9^Cy?v%;(kVZ zrRC8ng$;MdKXc1tFD^EAHR$+5TOaI<5~kIpiZ!k7pXX*(j8!(E%!}7;KtXJcHaG2l zaKTlEn#eX3jimfU+S|}lBFb$LL?P3ee4lu3QEJxD2-%NPZ-w;`1-WCGrBA06S0`cGq`2^|)} zw~2tkD-=O7FMPZQQB@93Aj&_HXG+{57O36Q8{-BuZ|(!-&tOF8$SPf%Tq}$S7?hZ9 zpcqglo#3TaIfNzG6Od35hF;HB` z6d@)?A9R|{)ZF+P{;O>rqPg*f7U9dOIyAc=SemeGK(ZSSuF*)N!z6CCsQVl|W2vD} zbIR;}Bkn$|C1|c9t-_*%bd6eR#kt0b+ndWWYfI4RUXUn7T56oZZ4j1?smVgSV9x=f zlfoC-H%xfe*)Xfd;W{-}D5tjkFxJsT%da*mO;+01mok&8-gH1nw{0Xqbzd5zEstbm z-D5^z(+(G)tl>+54XYdFX?c`qyTp~wq}9227{|T8mtan3>p)Jn;|0)Bx2=_IqKc6* z6Nx8MZKTGrzQWV$ng1nW2n=dbql5uZm932@Iwg-R>+ex2+6~tkRBzySQuj} zDN4-5*~Xe!c$Ibm`kyzN5{tJ8fQK8?=eO_KZ8w&tid$!Wo_zf%5heC+f`CiKyMgx& zzEiqTogm(t?&R#5q4*L*1!qi`$j$01DQjw!e4W{y*Z{hrk*}2=*VPl96>a9s!Cy0)$32ZXU8cZ=KWT70-)K4Jzh zZU$eN9G+WPac7*ij(Lz`?>{`XISfLDp$>y^MT5AgCrRGs;t!Ezh92rkq9I9Cw*918 zV6VZ+9!ZM8k+}`g#Wqe(8=>hrs{4Zp#&<{QuyW6g7HFd>V6>3?o+vi9h`5_BU&de( z6loR7LV6wvCRs?ZY;3)_NKPovl=Gl0Hp)lNJ!}&c{PP`XSj@alq`Jy%W1NFD6OO2l zh+!}ym_l@5_N}y4VEl~f3E>m_Z^@D)>a|68I>l0_k5trrBhMyNxD*SKf?f`%#Ax0P zboI6JbXZe#3mOC9JK$I`{utVuD97C5g)2t?GKHgEuuW z59uOSS;BuU%quA3WyYxVWJvc#5Y4zFS>8U@ANo#5tqQ`md)XZMjsS_rnbgR`x+KmA z+%@zLmoHmV-~+{&kqoN%c@ZOO%pmG2G_q6I*i6Q`#B4Ww5k-oQ42P^wxh06R_Y`E8 zZFWEqsae^M|3lek0UQ)}q?JX-PgX{T3*fY>B=L-az(o05a->Fdipf~}XrX2d3F&lg z$33BF?yTk9Zph*I+zrc?FLKo?a%K*#Ydp-lx9%X+_OI^&qS+loJ&^ed4P>jN4l=eS zyRMT9*6DUaG z2nK!K4NbwgGzNJlOX1e}V%N)~oa3}7kqdspTVSa>0i zVs3Mtq0n!_DIDkw=E)MEjsQIQ#OYxGo*)sK&y2a&^e(odl-$w49pw7<$jjZ>Zg?%B z_|m{<97p-;G!86%M@4zo`SCO{@pV)T=!X~7lkTg4E$GN*oHm-ri>!l_4U z8r^IgiXb+`sm%2CrmM`U9m#ft9FDPo2>{_W_o|{9MJ7>sW&`ibAycRX0I`>vY!_=b zki##nML;ogK{_GP@uyHgH$@EUamQ)(=$|QUOp25B_uqd^+1{52cuqAU$z|Y%8ZArt#iu8ejUJ zeJJW0h6cq(mS@mcPpqEM0u*HzS__azDyqNo8oMDW2VLo1bGUn>qc)#9+iF6t2ouMu z6+;^{N*I~gQ4)?5@;j(bdr9wb-;B_%`<|Xn#^ifpn&trhomXmUm;VpBS^%H`0096W ziwFb&00000{{{d;LjnN8Lalvm&n(Gp-RGOXlDU&A7VB*m_rtC>mJK-Z%39>BVK@RD zK#+`;j3EC$JS3~S=jrb0o@a#sQY>BFZBAA3_ z`A@@_?|<_xe)s*~;h!A7`~H7^qwjwF>07Vg{rHDJ{^c9~#CL!H&;R+SZ}M;d`X7J% z^I!kpfBfP1|Mur!{_w5bV-O3z#eAovrerlG=6(+aw#0j3wZ;BHJGNB1QGUe!fDJv} zzUF)7+t*a@Ew2sfF}NWq-6MVZ@u$E0KY#q|AAb4n@4x@&AOGd&fBKjI_5DBo!;jzp z{NKO($3Oqy-~H#m{&C-`7|zWSb3#|U=uT1Xs?AO&MOf4BA@AAJ^DV{)wZ~Wd6=F-f zGyb*6U-KyW-t#c0hEl%#@|PGx@5E|Wl)tro&a59`S))_Kl9}^Oe(4s*)l<7jZ(NoS z-9IG0wMWi!MslF#_BED!uJ||k=K9Ft-gsJ;axlwsVD_6=>zALt|NZX|zn$9Yx92-& ze0vIaF8b|VuUhE$q=%F4T@MGp{O+UaxBiGMXCtSQZ(muA#*4Yv^@FLY+k^S7e0I!- zpZ(Lfe14JGznSRH&3^BqAKf!fLO+`Im#2e|<}N=xCHdjIK8vDHq!c;7^+$8S`b z`n^|P!bdCO_LsNUAXenCwu1Y;Gpx}6Laa#lMtfG|IIL*$o8G?mdz0If+r2&g^vrqw z^r5`^>HFzVPxqLHYpVLuDd|U(+(!TCkkvJ@e0Jac@#`(pL!Pc)`-WdX$**6d{0+-@ zr@*zycmGy?|95}=m^VNF)*Ibh)1P0wkNy0K?dHdO@AiC4gC+7Rx$Iv3B#ud(Yz&XD zTqHt>e-$SRH=Ej>*2Xa9CeOFTS~}G>tm2hb@-AT2fl>ONqulPZYZb&D!mf^Y3$u!v zRr5VYw~Ahejuvkm9z|X>-AfR6beI1Gf4ucamw%;cR8gPgM#)*rC6hl!>9to%<-+0g z;%`IzFRYM{$Kl8&VU6Z|ujREFRBm!Gdd-Ub;c?B1j;_aqm5&?BT@oJC_N+zS+fgJjpMeoDZ8_dAuF@|L)q=y4po@ME#!o4Pmk3 z5w$80b8S}@xBSarI;X}hn^{P}UOt_69?<2Nh~w=fkB-rfr`dkuR5g zD|NkTh>dd7YK9jObK2UB78f(Bn^EIyGvcMhq*#zIk_(v`dduXWa<875G{0pM_xCe; zdG=WndN~a_!)ghAMM5w6eR?{LCQtv9!A9c7dDv7N^vUFvG2)eL@!~{}L?Zcq(2Vj% zqM0Hmf{O)H5=_ZYSxc%>vlf^l4qUF*yP14CHAnv|WC6+UTKL9pMYQ)(k3)+TSn@y+ zTY`lCdd~>`l4Pa_%eyO!6)}y%B))CJ=CvP&N%@Xwk3D;Mq;6HBWC6P-FieOjMl3MO zSBv|S9AA#7$em2ngxp60BiTXCpRTRyc{W1{VcqO$_UgqxvqzJdCSGH~Lt|n2hkMqdS*7Ib zaW@xk306?II;5jUL}K6IeILC_!RR({NEJ^V-;S#5MLyiFe7L`aFk(hAXN%&^oozeG zw~F}M@Q8@&Nk3h@8A>S0DEsoRIhBy7i+bT<;a<;cR`MtzVVC>~Yn~`sod-Kc_|Ok8 z@LC6jImr!ydu;}kM0KwyV0qm6fY!%iAI z4iw>nE&3ti@4g9n2C+Pfuye^?pTNxai65s%ApxlEhXeFsN~G)fXL zuBs&<-OogU6I6uZ;6#elR{W6k9e0WF;i$*(Z*W%6AHGzDe4KX3ho5_&WVnYU8u#Rx zQ^toUd9JkqGaRTiepS4dLyP13QsK5SJj zawqDmI87No(G20>w?}gCxLe^(zH= z^ujeeIH*58ezs&Gpr=#JQ{plrGi!B3>XQ^jU46flTY3KOfmRnNYN|(v7pR^mddag^ zt!B*qSfVG1wwDmWkz3@|y9d#3W+QzNHGd?MF;Nf74(9%Y_I$qA99a*Y2@uzZRiL~_srC<8 zTe-@6(Z~KSdRDge^frW4H1Vu6&<7^qtt1>ydcp+(;`IHVMu|5m9prx`#>tbBsDcZp z;T(w8`oi^G^XD#HJBI^HwMT>4!ogop^23s3+5y|@emStz-Mg4Aw_9qDm?Cal(40yh zB;O1t?X zpd0z|CA-Sj=#$?C*iAyUvdRxj_ya%L&}rH*4ua2o@)kUQ1v`1uuj`#6?LB&l*G_CmH)d_^ zCy7vs7gUQcF|2Jsp(HkhE|+QK=`ZRG>lj}M^U32Qp|ifS0bxG#8qGwKn|5JxigKMK z&ZiN*k0534`~z%GWEiJO!~M#agReO*swvJe+1HwIyNn9Z#*D&SEEqtjv+9 zXbF4@M^J4Gw^$X>kq``&(*PaGXM$8%@@(p`EvJ$`*>+`>aY}^Qd|w8 z+pTFQFGkKJM#DSh`}MZr#bh)wr%b__a;14f1q zjINIiAAo6>q*)yJG@;~KQoY$VJ1@{BKDP1!Bk^dWSc9EK8Lws^SrG4x3bz2>iKDxO z3l_CXUL8l3I_*=o)Jk_10txa(OLas7n-%B85qkwT4i6zz1U#&98lp%4rnxL>TJ}=Z^qiF z8N{SUlG%t|)Iv*me+EdL^f4t=T;Tu=G&H*J3Z73&8Dd6MbzW6+;N6-y#sp}+t_Xed z`WjM6B;w2I^pJSeE|O%vfZd0(7?yOa^y(ehJT8_tS$1I{c&&gJ566YC zMZRc>m;7(moq;aJCXW&Y46;{X(0ONIU@B+?auqB?A-N8;k6{&HK~0>l z)Risit3WMJ0hQsnV8m_OtA~h1Pk7vh4dAsD0z5vW@|f18M8Kzv&KN! z5!FXY@-Jnu{H;9g$U%>zH2gpFQmE(765sRl7yvb3Hd@X@Dx%;maKMH3C<^C;dnI9m z8=mz;2^Ab}c-ZQ88DBMzq{Jeb`|wLCXksGiP&=zb-spaFuwIjdKfPM=OGYn446SSU zfe)$>#5p++N&B^l-xTCk6lNj66v|DFNKK0O!0w(RA7Z9PMY{6F6IpWW6cKI?IRXH- zD#XBc$@9u4gOA4I%MVscZUlaEr-7RyVx(z751PFPDRzsYTM{vV{=Kx}0!J*s4OwnG z_>4(eniC*m$4&qe3Mj}nT-lesiH!!20gZ}J>zQ&>$Crs&^quH3!Y*GNR+d)F>{zr6N9htJ8lq4W&-`}Qu%iRtp zlU95<7kDqJNZ0S9a8laKr_rcb^E#y5L8ZcW82dxhA42rO8%O+SP z1T+P6TE3^XIa0qY=EUPf>L&m%=Tv}FoLB4gqhwWUXK$cO!v13HLF0$`DYE!s2~^T< zEq-f_A7Oqu*gJz^tKF+##Q~ccU^Evs2FVDNW_u8Me0-KabTF7jXqJ^`EC$d=2#psw zTVA(lN^MdTdC4tRdFpQ_Q{~)cIcEN?kIX!KD&_EpMD@N4p5+E`GibFPG=c{*zIJ$2 zWMXbZiyX{`*ZAi4F6Ax3d4q2@@BU*r%m-pMRP-J_wT*S%kKKA)4SV$_F%M)mj0DE6 zY_<&SfXf4*TAapos?I3XR?eFTvIU-ZVu&3GkGi{#1Gz8^`$$MZsN*SM&_u42g!4F& zOo{xJbrPtFyOsO&J#JRSe8k(x?6v~mqR)=5X`<^y)iIxC*{vd$2lI0o#a=g>u(dOk z9#(j%ykU+d>Jq@gZF=)OE}#f$kZrd%cn=d)kzHh#%dNnMss)qVAYYlpx;y2?hUue# zuDq_8c5?Pj!L(^+kh@&DQy%*~cn3QLsq*tCNGSdhFnp9 zyyIa9IaLXg1#MV-GiF-OD%FB_fT4#l9)f;!UoK7$r7Iz+w4CJj|7?-^1OvQ~XK1ex zKXn|pMf30)F(z$Uh9R;btnCwDlHwd_e^u+UR4po~w>76qM=Y1*uf1YNMtTF>sFw5% z9u-Q`Iwq>@y0mbRxyDdIDF=U|MD3`6KcS#LCa>4u-qJwx2?fMkt%%M2p5I<1`m8v4 zmGI~+rEqF?l2p9zb3gixI`Hs^4uGNKm`EE!)cGmBldrE$$0;Lm#)zC4QqgpIeSI!? z|2b;W;9Zu1-INuFRW_Rg$e2G!`y|z9NZfRvAk3Lh zz!>vbYG9)j;#e91!xE!P1=#TNZSZ+b9sPpI&*?ODyI_(f4_${m%qaNAU7#jldOa26 zMxyk=h+^SV<@Lr&Q;XsN^NAT30H@KIkc`D?x51})s=Hiy{H1%mXgj=H zGb86m2A#V}1y>b|#cTO+8o6b3Cb&cf<>UJk!pFW%o1@vVq52T32(zQztt%Y7=@rv} zWkLVma3!>sK=Y1*QYDJ^A3-P9b1C?1hE0taHc7z|LJ_Z}y*nViFHdG$(d`671HBw!wAAFv>~OXe%9Z z+<|N(Yt;^9EFi5$M>MP5uw2T{$fRblw_F|>ExgLg=@Q39O)H}XNUJf#ko2KYOx?{8 z$JtJrM(qk2ayE?$tuB?k6m5%{AI{G1!mRqP9g>LGM@B%=d`w?T+Pst)ibBjuPOX8I zgid5Sbz9p?duIs6qsfE5l^Gtg87Hk^vA0)%i*4axXmXsKlP`CJq)1 zG3ymy@Vb8gr~mlPAOG_&^4}dZ?$4u){A@FmjP0o<6ZOU9qd~14Fe92Iw7?(+qND!` z2xxsoHIbTDQ_hky07%Fk@cs6%u9$RQASNFjfJr_88?(2~9N}q4Qd%j3E(OH4l7>rz z(pmnG@@y0@MpZ?tN&qM5Z)DW=oMYEanyFKurLX%JVo23#grDCwWT&06wItt3UrS!J zb`2xUQySTsj+_p90Fn$)G>L&6j3Pnm7!s3eTuxI;;P$D+I@SvybOX}l&_iCk=|VK1gI|5WwpD>xkgQybWAM`m*`0iLCro559JI?+ zJ}N1gL&ooSEC+9e)|pyK%oBtTr*CE#+S#_wwuN5mL-nhI_0Av}ff<#}dJqcv) z2b}JxSBjJvgXzklTP3a{u6=J8r#?hEviV+u%_p6Y%}MMuaBNmIM`I{?=Yv3twOWiP zD-YeHoi!ES`lxoJkX4i2sLfFjl{7)Y+FnVB?ZGjxu2GpNCI~(O0+5cLI~x!D?S08Y zRjXq0w5_@LtA@cYu6h0KsTFOSht7$jbCmMQ38eEn3nZfNOxh)`F=pt!r(hG)dA#U@ zfkZJOV#F0qjXp+PmD~nF+dNI^{-EU`i?sn+&MTczd$v@<f9KJ z@$O%z0<9o<4PPr8zE_nx49tBcMJGli%G!<*r-#hy-K74950hEs_lviw;>Z-|P94f7 zCq5<=A+lGdp7=q<1u`@k43cj3J{SzpzDiG4nZO>zgOYX3G4t9^Vqd)e*wLw9=OUqb z%~)T~`8j#a=5|@*#D}?Eo;ENIpdW)l@39bfm_0s??}f;S*|Eny*sH~Hyss_q5lBoN z>{g&f2Q&p7j;P73w*2dpOf0^?rTIL3{9FFs7gG){EpUZ>G4%mmtlAF&bdX@Vt#y#T zrh=D$B;r(UbqHk6R23}gBsBpzUOQcGk!?0kGLKT;&vSqH*grbLBHQp_?HTRL)#(^R z*hyu=_Fo8N{{hN)NbCnK16h|EbjJp8h^)vrxv~>ZRrhA4xR_R_ABff8PB|X*wc|Ngo{g@cfT}R9gMGz? zw~=?nR*hL*5Se%GSi4E?t{kiuv^a+wtjfV^>jSdKVpT}833B{uCsoLWU0SQR;s}aw zrT};!oY?0?1*vcQ5$UJuA}XLKVXV_KVxQE9^Ue3vXZjc|+FM!hI`uk-8^+kXCb%nx zKDMPH8N+F$L0$qk`G|wXtgi(D6-aCyY7nC-l&G{e8h8pIzZw8Mp9UK2T~>f} z;g3-oIXv=kU%`m|Si4vp>-b`GvCI}Hs2zcDt?;Turi~)uv30UDP+a5vajgq08E4`} z<6MT)p|eH9!LXL?VKntQomd&8PN9M;(&v?u$C2HfEZ!IuIzxW@q`2BP8us#)G2iPz zgRx$FjHioZ^rMJ<~h8B-LiLaUg15 z1N(X~2AFCS1oSwFxSMESf!&c(hTL02WDhV4!2T4W5C`VXe$)mGvlc?17WY z8)75L$t=Q_9C4hol;;(P1X7ZPdc<$Xe{0^M7eqxQN>-$WFyvbdYT3ZpP>5;MjOeJ> zr?g_W2-4wrRU0f`9y^>~2x4fA>9-s)R9ih|*%_)8TQV2pTmxPq-}1}r1ZF>vydtkM z)rZJKrGoOTYjg}u9ZTo13g{q<)TN-0uWa*R`vN{*Dd^pv!T( z(gzRJR~bUJtXneHC!(7B4oCq@I%beSKaPMFC;!;)R#~d7)LW)$N;i2^-P`bfGPV^0 z-+*~u5rc{7C|DfS4@-^Hk@Sty4#)M1T5v(;UH7)N#TefR%981XuCA-B#i91U`-?gU8E+hm$J00s>;2?d z20e|&{M4b-WnCuGm6}g;Q9PKK5Ploe#Fe4YI&0o&3vhT14ewiVey7HO#F*Hb3viHr zlU^#C;4lX04$gv$?f;Xo*?cNbmq4}rXl2Uqi6NxDP%|WG;#(nG_}Y+5?>h{EhH9OY zI$8`q&88`fnUN0LKUgoxs-tADn}(PmGM=VBvq6<8{!gAMBS3DS6}tPNwuyRRHdLT;M=;)jq zkpp3$+5QW!HrNdP3fn2{GVY@au`e)=%@x&j5)(HYI2R1Fp#Tv(f9xBZ|2 zCyojm8l!SkZbM-`to5zL40+bm3|m1Gby??048PJ@Fv**oo+z{wEn76P*3pYYw_%e@ z*~>5N$MEU`@n5_Aq3vYa zGG-6jKx{VWFlj*R5YCO3(U;MCf@zj8 zmt9gL>P$n{k#s;;*l{rpsX0tTEN-OJe#oUaPUCU#TI8hX?&1v+g`EqOc@JM$-gvet zXj~`Qu^Hxu5otb-rlUdf&#fom!X+hI+r$Shd0bgh=MK9xnF5)C%N;sg9Z9dvtYBS+ zyRo9jPfadt>y5o#BgCGmx3aM0K-?e1a>YbTJD9(aw7fh@*7=L3sG)67&joS~7}}f} zZf;>Yj{3P>X%}{cf_OU^Ht}gsVyVrS6w_1)sTrWeo@K;%E!nm3qscY*rY;O)42z-$ z=N`ro9qKc_1c?6hZM!CIXFeF?AA|cp9N4Ne!f{6_Ks!B*PLI43)uGM$M)ZIxRs@JI zTc1oPWB9&1y>P(-=#LFGps^;=p1jJ1*lY}qiTZt0loy030$-hH5JAsswHpm~@X+>p z(~b)sm~DPcXBqPn*P$O{Ll{HUA4jskgt=jEp(LHb$hud^E>wG3A*y!NPt9OlvBPH+ zR9=u*c1U_#g7-nnuw#EhJ0jtQ9jT0tf!d*~IG^m$apv#ecU>27>f!>N7(`mDOm!V8 z)$_2fTTxM%eZEty3bwOp#yQwdx|jFKjv7AoKCZZ{G#a}JK6kOBDP30WFuD`$*k9_X zI>&0qT&J#01Jy2E*-<2^`jj2zJd_|wPu{q3-j=2B*j?}My!a&Pe(wX)Wl`r5AQ{l+ zJVFP|^=w7`)GNQh&MZ9S82V_ls}()m$Ed%7poiT~c^qBq4`P?TvtmlzZ%Y(fgJ{`Lpo9XU??1a>1F^z5H z|Lsk07bFWx9TQ|zI9RJ)#>za`DRj~R(VmEz9+IMRS%PE9+CD%`hXcUm2tS>Yw%H(@ z-(s;-f|UEU5dXuz3dlB}%!?0js@9-CXt^5>Rwy;b&}g%Lb=~H8Rj@MyB75NFwZ)P? zU(I{M`4_gi_A#fB2VHA64F{QSAajZd@16;P)eK#rY<+0g1%g9wPiF#(fF;BLmdeF! z>%t&kB-6fBZMnKmwrsBiq~eYqeayNzkc^h;ivv}A2F>+*Tz`IaCk(*hTKD{#bj0fO z=eUSXx~k)q%((0eMr5Fg`uRyU%A$`cbW~uEObe|^KA%4~>3TuJHt7yDdk3yztM`bD z?%>k5JystEI*! z-ID9DdGof5zupdDXn3r*8r3e{ja1FBpj*@UDaupspPb;muqIR|j?5SbRA|g17vDl1 z*vU>i`;>;>ZM4DauGfuBAP;K;%kxX0TTef~pz<(~Or}E`Y>nu^@6cJ9o1u=DHWRcj zF#L(_z@&TlSVyelha=&#;9H~9hUDyTCo9)R)WGz8o;HVqx1jZzX0(3BlC zEgR6Z=2Zos2Ep{jj;J5@i;}&0*5F_xt>Q-tl8+d@tz|ltNz1fC&9gS1R3yRV%YN~?@8NIe(JG3EU zy3t(Z7TaQmla?`ha%RSPc15=IO$DFUW?-9=vzEhXjI^RcXh#Z|b{b?`jZNR`qZzz# z;WOLg2<5afa%n4K7>_))^=Y6FPX5S?p9XH;kr`xWpe~GSGuD?%Zqnka$cDsvJ{^$W z7P`l6Xdb)xTvipV>uTYg$AnUm!f3lg&EI%ccrJpQXc+cZN11yKQ&u%cO;U)=dkn5q> zOHw_VhESr}##Ig9W0`Sq<51d$4L6RaAtl&Rzm1-hjsMa zs^_|Uc#zLc7aiW|Mm3qkJ3)tc(qc#DX@?IScW@9+rdqG-K*v0i5}((rLq#Bt0v7@r zN2yh;I{qC^jTj{g&E8?t>8SlSZoSAF+vvR-ASq)XEl|=;<_1vm0c}(M&gvRf~k&OpBrzS zEM3R0L{Hb-$8;TLOsYl>-o$NZ?axhmxFaWEEh(lUEbjB4U}9x|;WTQFydxawMvyNO zu;p*yn{0?0VnmrFMY&PBDdcIL+Wz~#k{r%Ke3zv#D+09UvWGw4_`{hGG@BxTMDB?YON+XW1Fq4i4QQeGghmm?Xh#C>lJ2ZFk&2eb% zsui`Zyi_9RTH&8Zqd>))nMMm@aHlPbyM$tNI$|!ShYwRDa&2$n|7~<#M=(f8Q-s7S;ZBMJ96YPvwK1@&Pj}%vQ$sICWW{%_xD> z+&{xU(hX4IOeEy`BTqh~?*10bfXZAB;(HwSB=J0(>W?VyR_8%Mz-p6)EHxKTD{AwJ z?%b%TfFTGb`Te<~3()Q=PmCkQ($-CRvH#Jn@v$O494kW8HVv=q_ew58KbyZjZ#;*E z-|pfk~WxD=r4ds?t+TyGX<{?Z+odoi2 zihEo*1=FVRLCy;Cuj(nTQ{zxe?WcyC;u3~%Knz46Cw5OI?0bfiKa`;8yEYd)zQfUPcpgeRIflA^4>zKm@~UDbb1Kl@7A)T_UWvAI+8 zbF|2uJrWNDZ5tN+07if!DJzUXI&fZ}TvJs9np*9(vCrPbr!}?vcm7JGNJAEK$iG@z z%RXn`2A(6O(7wnI>3-c}YZXWfKCz@euf7o*EZDVLdVm_U!srR~^4AsL8}M@*VVk{= zJXS*_9W>A@giCu6?o##vqOM|bZ4vWZ4-l0sf52tapHSzWyYp72R2@3cuDM{yfo`9+ z+UQ_bHfHdq_3k&t)0$S@5-89Eoe$S#yev$+q)+41iu=Ms`RL07;OkalI)xBMQ%Seo z8EGfy!xFEBM+G%EB$F2nchIsqpHo@cJB# z*dOy(@LyYe(Fritllfpp;n2tS-4=a@tERQ6+CEM19BocxYA6n&sIfs{%y8kd4{io_8cfiOF zo37gBgLqQ1F5hOEr5uJXeKj*o1_SE2XXA|*=*43wUb69Z@0dWM@NNu)5N=s41bL1o z%K1@)b;q(v_=L?fufelewSx>=({YItJ1)t>bno(o^+-9_INmu=?TAnVDuDetuHe#k z$=-)++dWaks(f_y*FoKiyh8buTCvq7IYtj?bjOArC=14U43W{oht2#-rl^FD4V$FW z_B^=9i4PXT2PLMThVT4FmDg;%W7rYU%)u~s9SN_88p$~Z++F_^vUa}_{GLXL&CaU|r5l(b~*NRHoo(2t0?g(fBuH z3fN}khuAju$ofhx9f>-D)T9j6qcHuXc@$BPVzHu!Hn`B(EcoW4*R5Q^b|ui5^iQMl zIuzgHbJ&W;?V(e}EmgvY;FAQFFoqD)uj{yj}mL2brxCntYl z`!SuJ1M!fHX!%esLLwheI8Ar5z|o$v5VqjJ&d+aoVVr$$Yu=|AykQ(^#tT3O2XDeu z5>f2cjbvq8tSE~WLqN2ghCRCSu&vn&$LPaa)tQm^v9C1_UgYu1rVh;uNbZG==Eb~G zHBz2d6l;4IY)*PNTD(%!9U6k{P;+tiGYJ2KN`Zm8!*GG^Yf*K_Y#ZpN6H_ad>7fAs z0KCCcy|XFoD?{;NFrPg|_uLl2J9sC?m(F5n_DGhN+04M+rEX?y6r3wWf_?CW`YS0% zIf5%JDVI8@r?kA;>Lmkk{L3*L-*#OSMcgE9B0+4k&n6NEHNyJa^Xgf%9zljA%NbMn z`c=yHsr)7?)yHDxsDsPXVRObo{xqwkV3Jj)-T3hcYN*o;=SV^P=d2kPuCvydO(7P>xH@4A#Vyv7V}Wa z-gcHtVf0i$OcwOO%igYA(E=lZyRB`c+!fSWvq+C--V6r#gQ@i>o^N z4;VJDd=@jis!AR*h#2WO+F!!9a6*Eb61jch1ZwmCY*lruI+TZ0v;fG_Yk1_sT;{R? zZzNS_g{38Gg|G8F8rHxRdD40Q5`BE!$N3S+pNufIJvD-(kId1DK-~pNm1K&VaJzNY z;Dbnl={|WO4ld!*uGMNQNm=xD1Sp;ThKHsR6PP9MMrM~>j$s!Yr7|ayj~sy z*|ucAyk}7vs!+R+LdtFxNJ_40yeE)UThAkx4eOzjD{*4y!nhdVWYc=yR7WXMXF+rT z(K4LUBVrks5e2i&`1GfxJ&=$c*QJ3wVVtOV0tsH=OBCOX1{0q4EI=oOU2(vd@1f}4 zO~U2Uv)T~5UKvB7kVdTWjvm6$7{P03+cM&O+xU60&B>)8;#>zk7NdDz`%qbuKrzsp zPug`H%XoD#@Bv~!t!!bx{HgX&8gt&Y5`vl@f*mruJYyY38rDJ(Vu^O}eE9gi z#e4BWFE9B_IIuuu!N&=j)+YKmbk$NYMRM3dwtJ5OnZ67mmu4}n zJBIq07Rp_L{~tRey1sGcAt*pwcx$EKtbci9$ufvpbp`JcGdoL{XqJuC$e?gc(V{~0|O)WZ8 zFg*!_b(IG2W^ozKmk@X0gULE+2yx^iPwW`80VLGXq!g93yus@r^^Gs&3dQuTxQFEz7e{M=6 zSdQDqYEbQ8g2zP%QZ7x^S9K#=9UIYfu{n4QWo<(bV*zt`bB*yON{+vfHV1nQQ`0_` zWmDO}1B~D9R;axw>JM#upsu=|T??ipdCa=NlQNqIBMR;4$>%x{aJj+}bQ(oLqBOa` zVa&HtJ%G|GL-_;}G`e5JBxrC71+V9UJS8q)&2jkFH?zPC;vu6$%siJMCcjGLT82J( zL3>$;iy4ZKQav3ow^h$Ur9*0bqMbIWu$Reu8ddkbf+%{>GJs_n{M1O(O|swd3?nIM%}e3NE8ZP)j6GiYfK*HwEs#Zdh@V9w1w zEi{Rb_>Db0nhH8tOVSqzeMkelTrH?Hl_qI{<>UpK&D&d~kKxTlJl%zjX2js}u47c@ z{IMWE{A}sk83wm-)2rK>kom?9bQ1p4#oHtZ`B0{*`1C@w7XXrcN1q`3S()*s2K zZ2>5!Ph_KkG~3y@O9Sb#g<>08OLIA2NqtB2Is zUgYlJppx!!Q$!YfO>O`bASAhx;};_eTorT#BC`wGuO;C3t=Qy*J4+tI*o@uXQ<)H( zBt<>Dj{TT*+ueH>Z0v%JOq*@$!pO8(%G}ZFu3;JiaS%HVNSSGi6}{!Zfqc3#fpHvZ zRk0!`a&6^0d=&>YWcMppbi0J&YJbkzl22}}vyhjX>)+aMM8wV#bI!NypdY7#lnG`Z{-&byP zNz|0SZGxB%-yY>7Qns1|vG{E=UJ+#eV#a>pwN){sPfC7Wh~?S6F7;9UnI$Rdk|V`D zo$3YDY5P80tB}g0L`6M|5jqjkG5y`2E?O*U6t15Yn4<%q)g4kdQrAKW!Lq4ry^OJ4 zN2=6)u{`dWzQ;rlNCl?#hgc3DSLw-+HV&mix%I{R&+`PGO`m&3J?PF4?szl?_ca-Zpv-P;Kx2)FYOE$8c;IE!unA<7J7{VPi+U!eLQ<(WIR^1*AwPAXp8jTjr zE=VdGF1U8DShuKXxn#5~@!KTI_;h;p9UjP};`o}v#zRX3oGq*OK(ag#HUSR=bYQH# zG)3;MC|0k9c-9vt7zgp9ctTUVv}R+{S;|_3sYo(t1XOXkJmeH>cu6w{p9KnSWNS7y zb!(TZWvP>rHpt}u(wP|-QjujS#@X`8HN{mB)=B^NJX(tf)n0~Br{RMp-xn=APtQTS zv8E#aJbZae38(Dra)r#|>q7YXd0>#Lj2VoUon6!qPot2l!Zsrsv>`LIzE!PSW|+HV z$Ico=>VIa&6)=QH;BXY7EHZ>+lsLH38s!7wgM{_zx@V&WopN)?oSyplVuo%{x8rEW z2*wWDcZ=4I71;+QZH{|k+qKGzuX158Sh2sJDM5wM=5JZq2tI#{G-X=Rf;hWmP)N#9 zWi%}fslEQ;PyhQ5FBbD&F82`r#+6bQh4)_gNI6_STnF8Psl(ZW5T_$?J5E{ThT=30 zUExGLj8cjUK3>;_|t@d2ScOdf4ETBb@#yyKN*|7Ahe#lCLg<{D6MKZax>W zaXNzh5S$y1h?M|LK(oKfy3;P7U8p(rp}a2mFDx-1=r9%!JxvegvAbgqb3q$s_i|Ka zHQ9z~HV0yWOD~U>=9=slq{rc8heAYiS=My8 z>LV_+_wADkt9ki@|f0EqRtjMV*V}mQI_G>e@WD@bPBJ)#>Z9cJyIY(y*=wgN4 z6qJBPnsR(m`m_sOKn&o~#W9t|l9ET$r(spjHR1RR3K7mngeko8?LboS`B>qz+ds@0 z@qiX9rs&F)VudOlBy-{MmyfS`u)qr{fQui0US0ZhhfW5z^~YPfDHQ_k0$=jM5>Qpv z*QnF%HbANhGb$-BAK!+eI#k}SO7>r%ghpRtgI~U{Ki+hZa@olSyq0XPf@?G-|K$tP z=7%bp&yQzaUSrLj>mPq<($}vI4?*9tEkQHnLOVmTzOSYz2;pmqoe4ll5t`07tvHE0g)>MQIjD2378#VRlvQ6RmsMoIsK{(AxHd#sx6=0)0iM6`YM072 z-WR%0BJ%DC`{hb`N4!ZNI{|=KwpF+02-|!weY{-Il)QV*xS}fUV7-@uLj2Iyu{9__ zAzWX?M&0ufYcD78zJUlKA|wNZG{}`TGq(jcJ!>W@7(?{(>xDjw5dbc1*MJ{2+FpDj zuZMyG4Obm*?DGx>Av$4wY}KVxLB;-j%v}k_<;e_6b9UgZ?M8i5D{Lr;)kd2DVG%C{ zd@pq@>>jK!guLAc!3|A=sv9=qsBjcH1^e*Xl2jIw%sVut3o0YkR)8rOw_2fIQ2-Yg z4&vA0uimiGNW4_f1B&NnIUhgUp&&LhNEtTP%cN2e(_dW>0K0-0MfeHC~vUQA@YAVYbL+s6ZHl?ZNJ4)-$1-+un% zpa1&n&!5TP3w`xJe*7H6Jyre>?sNR=$M5(b3Gu5xe)GBJyN{o~|LO7dcaQIWczpl+ z$M-*ea{rAU#odS=xjf!{zEiusd3UGEe^9(f|H%B`;(z(`fBZ_Hs*B{yr(b`IsZgW% z>Dx~~eF}G%KmG9aAOHC2>u*2%ufG1>XTnE*{XhA?eD}>)-~I5p<@X;??>|1=Taa%r zcXIaa|Muf&-)g<*3>bm#6oBK6VhD`sJrrUn;nu>Vq{mxu{vHcs@b|=s_;2=))>ahR zeY*3vfBlK>F)m+E_E7J*!|4{&qtfH8(mi&nqsGTuDtAZx#pxdK;fd}oZa>_o#1}sE_g5dXAc&Oi)gNMzJ5TO8 z3}+X|7yj1?i~<vd6`mVM*b}ua|Gm8x?*6IG21+ z{0F7XPkxIfcrP#E+fT#~EgwVR?@#Ic_p`kz^8HP|zXbl>lYKum`Ti8~vJW0|4G+G; zY)jc5d$vG)mMP+4G&W=dfE46K@KgTuyYK$+_h0|?mp}i<&z`^I?|&tH&VM~XWHOY!$P~0 z*|`S3#T2`J4T8l1OG_08!dU7(sAur}nCsL~<$8;SA>oyHzL9KW83e&%1%IdEU}C|| z`dUp@`$8L=x^t+C!W>&^m48Zne7;w?Rpbi4oav=Xx5yR?kAm-1hF5F%giq&VNn!l- zD4+iACwRQgQIH%34&(?*d=JGRLSZYj@zKS>GAR_c-2#Os{#l>|fsz>tJLn8g2cL-N z!Dgs!p`;ksQ~1zf?BB!g6apL>KHcFl5bmtMAd>^!8-FJscASeZ)b$1A}>^ z>h5Ly#S0A5_YzyYba~qD?(A^XdrcObI=|kXR2u(!c6TI3s^RXZrZweTsE^zv)My%| z7T_rC8Pb5GMe_{&K{U5V2SCbGATTiY2uj@ssu2ev2jZeWf_M*8!)zkc=mzy9AJ|M90^j$HTiH$tVq?gE<% z9Q;-!^KnH!pN&kMzG^nQx8&8wvFAtDV?Hjj4a@NnkpguL$?+lBdA#K2eB7PJ&drtF zQy>mr^{JEBaF88emasP6NdfWyCQZ+Sjo}Swh%b`FP>wGa948=UMp!my!gzdt@h{>D z&mT(KtM7Zu_i_+}q#Nx~fSa-5Z#I8L_4V;O{s0q*iQ{Lhr-n21E!55V|JZp>m++ zM=Q|ErEJlc=kZx`7BMp%s||HH9RF%1qFBrpy(=??3(9zyGhVfB&!Fef`sae-i5C=T4Qcd2yE4S9L<(Td1Rk zP0AHhLmkS^GbFxQZ;_-piW5gV&z2yz2S-nVR!|{dvedLUZDDstwFhjqqfpBhYO<24 zN7({+>VciE8n`+3Ivh?!6ema$%7AEkcs+a{B_Vq`Y75WpTHr9JB+Jm6rk37mS}avc z%?O-E1rk`#569p5aq*uB3s85L5cJ*(!UD(cYy{*lhM~{BXkagzsprb1fJnjuG#IL0 zwpbJpzHEx8an|V-4x4Dyj`*xm8BS3y+ZIg?*Mgk%91UC2q}CvH;}*Q04nk?c;^lBS zhnO0+z>#x+RT7)cVJ18bd8@(v@Cc+r7A_c^t40A@(+SQJUj$h@?|8V~@V$Rd8dSHy|BlZ zF;8Dk9lkkc+8Re#Y2$nT_?N$Z`^!ImnK-h#;1`^O$G}&$ zo{n-eWdz(x-3yYa!Wab7w`_2qa# z+DCDpUhhHzzT&mX7J7^Uo z;~&%{=pCN09#(YMu3|{fpO^2Qz^Hr*j(@bo8(qDZUbiyyM|@aw$JE%*vax0Z$?vQM zzTT2TkVA+X#*%enM{H-sSGI-o_QLN0?H!XOV!hJid+AE4A>xiWX)04A?%D4IPU`|2 zi^N`4vIU8@BS+n1`5@+IaU9)@%oa2)B)UenVm*JU99E5V*zq3) zZso?A!Go`yJ{GhL_>Lk)@NlO?U=VB1NndRQo*?Ta{!6X0>&n`csxIJ&1PwWB$hHH@ z3(#-~s%SXNLoo+vq%lkH&Ll~D`SlEr>hmzvgCb%qnB3MbNJanN&?K@}Ip4u~6_lTK zW;8+i%R!p70j(|k;m1x$O6_7kdZMb!KcNqHzv8B0NHz70r}iRsmQ-STIFaKWzG@LX z1~%~()tsnjglK1o+_DRoDg}ODDcFZP&GI*n|3g?Cw^UL=*-DBzLH$gUGVQ#J&~7*y z?IQRj?Sq)5R2|r?1sd>pcdaQ>o|g-72-z%gqLn6t0NH0X$u;&u&iv(gHou;YSz()8 zia3A9kdRv^&d`U4lXEsK6mX84w%fn{<$wQtP>Gt9rzhwV*65bFw*CW!J0To0itycvt~G`@J-tU z#4iE&iK+mK_nc9wVV{I5F0A<$PoaFxe%0rJC5r!APkY~$@}PP3Cts}0!N%K9pFG@4 z3l@FlrZ2upH#D&67!Rn(i@#vO>WR1|_Ier{PTw|t!{iyECN?M(%9}4o&4N(C5S1pI z)EVm1WJ|7rrMra_uXc5J-g4h9C1K(Yo45|u))2z~9la7=_^m;yrp%e8d zgpKlK__DjS`pu<*3--gj&Ee4|#2q^bzIJ zqxY1-8DjgDmHQO5C&)gtlmSh>NKHG@d*VzscVbQNI*XHRt{PJ?z`@|Mm4mu0amd17 zObH&JT|El2Yz(ucEqSb7WJ<-#Vf*r z9U`#dr7z?4P9a3ayJvG;!ub8oD8PV9=dvzO&$BLcAeZkSX1sFj-+y4XIHZp-TX5`6 z14u8`aZIzg@#n8gFQyLNix2q=-aWTf{5OYQp2%tY{YGD)e2{Wcbhqr({YfOfxIdI- zn|~k&_A7)|E)~E1o;SNMe^oZ9MrO{NxhX>J*5I_XurUq`Mvb)mg;Ivs>X&r=(c0J^ zhxgawdCEJxyQ@bUwM;nuc}r)er3G7Er<~4n6LupRWyvTE^Xddi5I4fX)^M?W@XPet zt3lR@+)*MzbV5?zKY!Lig@QA89E_M>@j4sx>qkOX_T}PaRXGuD#TAQ1@`44tmXy?&cqxE! ziZ!1-O4iyvig+nVlPGgQJQRl+&$VabPtYx--ss2Zq#CWX;lvnEvk8E=nvgIZ z*keMisKQfXQW_m_ux|0}!5kRy#NM|t zP#DbLS{|Mc>5Yt{01hMDfx@?RU@Yb6l8wO;{|xno)^K#Im+^`y?GRzys#>?Z*Yhld zi8gJ|?qy!Y3&~-$%tt45Y!4o`lgJXCz~%=SL-;S^q01v23qn?6TdYk;YBZ5Lv3&OBFk0d;Ac6;yo%uH0vCKDN2?b4q!&Y(lmD5#vYM$AmS)! zDC~Q8(9ow$w}>(yTWkvlV$hDI!}Hu?Nikd^FjIRjv;a?U{iCq(#G)FyWbw3U0H_Os zG|lv`H63N}2ycY#FXaCW1}0~r)sJ}6wRrkOZ(KU^!SGnFM%oaIi9nTCDxY?dFo;G6 zX>_=uAx5AwQ*F{6kEH4zY!cI84pM5DPKfXA(=exH1B5&7H{3)*yRsO2!Cp@&NL7!f z0&CC_0BTKAH*jPz9GJn>9`KZQ>4d_Gsy|wa>RB!1VLVBBu?;1(4Ic(QuBmB*txDyB@QJ5rITIY`TFPcW=POVcc=D*6cQ@6)u!ydyddQM)9V&eLxs>M zC-x0QShgpq;5Pne#ylalYp*BnJIZ^Gx$iXCb$HGMuL7!{o1To!_{_ne+{wvXlKJ2$ zym`zPIK6FQumzc}g{5iF>fME}jrsHKCsc>dYB;b>IYpCEVk}-o>S(sD0WWFe3l5Q7I(nbgf4aPYj)+pHH>lw6gJ|8Qh)LXV1EzRDzqDLMqU30dEey z0_3Jmn1A4tgIgQlLF!GkSLYW_RM%lt`*pKMnua>DdNRd9PJoKhm^p~#)Nf7)C^uhi zKrmKx`*cDOqrwG2;DAZw`z~PGQ_CmqkE~TGApy57rsmMtx0`2~s&^o0&Q+TDm^|A! zsG>xO4S1A7IkmVM1$uzgYS(FcQPSZ=MO0p|1qjY4n%dKG$!61?u{C9oW*#7jewIh8 ztyV#ila>>rd)K%v7iVVti93g2UcvBR>;0yVkf4rWOo`W;hxNonL+D-Iu&}gM zS~4s{+&B@j0Y@$Bc!M~F4A+CjnRU1x(;5DqgkG-hB6A?_ss%d}T_Zl&rZcwV=+tmB zajUiQ7BHMWt+qAJM+2%K7~zz8eQ`NL-NInxh=2sC#ub+VI_K88z9NN40{eR zO6TJZ$*R*0oJ}McH&KX&_*O>iYUqj9MF$B_FuE6-A{!M=e>6I~4g=?&=>(g`?4>Ao zq%Rw?!yJGws6_cJj|JjEGLP=!IQ_#wx>=?QIo(%~oFJaY82YS6UI;f{=^VkDptvvd zvQ2ci=Fkg$$g9A6uR4uh@;feYuB#&JYhm*VF`7)sV6?)~mc&!K?zGUklG(7M`g&jw z6FCWJ2+R1*fjj{Ihh(DhU0BvAUaLXvTW)&~V~W@~5);eS?2~U@G=uDwHWXoN=Mn+# z#%9+y|B&vl5D1iOL#~8TbT2o5vEI2?4c+^f(EUqZa%nNi*^7jb*s-kqGH5n?ojujkkhZQP;s@+ffNxEGr3avIW;$7T`X9scMQY3`D zy3K55nSq$8_q!XR(++Wil;pn2F^hcvHVi1V3~c!q2&(*}KCQ4XjH04ksZ~mpR+Z+d zx2ztCSrgKYxJr)(3ACCqZ~6fJylemzqeVg4CcHGDaY8iM8-W1Ap<6*zRKY&hnyxa( zjF*q;AE{wTUmzf79nTxrb8;TSzPdT__eQme*jllH5G~aIKrXvLL!KOCXj;1n zPG(F4hD-B^AzSMf(t9s(YbFfNU2BjdKb1Vh1mGx3tKJcEbVuO|@<^#Dk^cNPgB2jX z^$>ld4Txnz7EvG(%peSuXQA5`TKHzLdCzCQ@1}CQjCQ)ZH*pUvxpoL6H zlVdKZpuP|WPY9^995QOLBC2xE`XICpOGolw6iChjlfLX*LtE8aV;Xrug>t>bbv!fK z=Mlem(FKz**98UMb;XrzvrC)=#slsqhw(~1MY9Zjb5cPMDIdjAaE0--YZ8FvxwA|=DaV( zFCT?P&rr{X^M};i%+B_BxC7oMk#v^Wz$@Vic~%!)K)g~0JgqOVc@@uzy9Q~5*h?;H z#glw9c~fsQu`;nde>kz;0?;He!{}PXU{$0rlAMcoeQN}^61Xn zfqWV9r+QKaP_TiWJbbKGNhH$d8QiTELQEHj$tJU~`_jH`TtI1}Sp!CEV2I*Vwbvg_ z2g4nynzCoCI=M(mYk7VAl7FaSbg}rfwEbTr0iE&g;cgO3t>^Mu2uGW%kh%F}O4|NF z)uZWf1H7b+K-ASuIwUz;G$Dac7hUxkVYQ&5CJj^;tR>SQQssK-W*~mnxXbnD0UGl4RUEAdCz; z)URW6JAxU5NtYbuL@;XiR0c491BOcJLjlz&B@hFdK9z#bL{_&iq})}<3!-?#^k0eY zq8i42DZs?->xsaSbx*1EzX*=@1TS@MoZ7FQZp^{%y{aUJ+V%Dn!tv}k9d}N z6J_gHX!V{>$jy42bihQerDKDP#37{~YFMyd054A!x!NNUl33XATBB~PiDv`HZ0pyU;T?V@tiaF(gm_xI@rrV zwvWsgk&H<)lBs=3=wb}sCArIUru$%jzjo%n%mn)FJ|mD{>|jXZ%7(R9b5H3p2El?CpOUJe|86*;AS!0)l-ItEQu&;MfS29qwvN4I=618{hU`r;ldLLFv zezYVnKhN?@?-JFe(5fMFtI;+y5!ouUH}+DT#vcM;2SbRGF?**HOS;u8y$dU|-&p0> z1XfSTMWxP8tV$SJ5MlREKM|8)Ka{=X=Pyye=q#diqq4VPzIn{#4!LE{{!&3H_!Fv^ zI{%A4k=sr!BioH}5`n*=iDyl)r)UZ=u%-wuFplAn-(8Q|KkC-@fRi__i&bGX( zJ(biXW#bf~6p5zU*Rs9;V6)E0v8hgYhZ7sIJJ0m9tN~QKZ5SGqlHtL6DtF^Vhl;3~ znDyciN&<7%Y339t2i2U#Qv-F* zL}x?7Eb9mjS2HwzfqWZqe2J3|SHAH0POKEc=E~4P8d=bmH}|QVfEv&gB**nP$-RYI z+vJKOCv2**IZSqxQm+QG;e^5vIg{lHsV}QgdHsxPP2Ao`G}aCl^R*k=Ryv1=u8Nv} z?Z4HL!C6Xcmxy&?&lB4BaJObJyXq?Dju~kS| z!)Qlt0aYrC4QU!^yetW^7ao|9x=lSCg>+44qei!mGwW>FwC=;#=*T{L?74KNP)(_+ zAgU@llie;*W5;= zpsHS+lYZcbeHDL3FSX00=T2ZxRwnkrLMoZ1lBRAAcMhZ zetlGQEuQlG5#Ify@||v2bX#c`hJ;K)s3svJ4w-YPxi}8SCeSoOphz-yDa#hC^eEp6 zdoeD&DGt{tmv${5p5U5M^;#e0H6fW`Ldwo^FxSa#*l%}e20?5`3p@v{>qpt;6>Auk zi%N@|+t6uM2~zAJQt)i>U; z+Sd%g5$9JZNbQvD(z0SPFS=zL6`63EtR zs&~)g*;n-}sgR6BBY7mn718t}Z4XBO0FCyAANG6jlMpZy@anZ>VL?(d~n>&ys zNmWE+WaNM!nNw3OTl+9ing&FvN~D%+E=~^%5B1vKyl1PheWq__4ByfF5mlHSWSJ~MVZ

=T8%=q#g)u1|{e#n`5@9uKDV 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/CMx0M2+F7BPV85k)|aM9j9Ih6+^dVP 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 z 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%0DyVm3sY`Enwj8yX2< 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∑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 z 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

+~Sk2b|YGiLbR9gf$f#HnUjijz9X{D1UV%2T5dt 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

N1i?HSp@qz&UMS7PfFz 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 z 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 literal 0 HcmV?d00001 From 3b51d9106aa4035a0bb6743ae025d8620eaf6247 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Wed, 21 Sep 2011 16:40:29 -0400 Subject: [PATCH 057/363] Adding in likelihood calculations for mendelian violations. Also fixing a minor and rare bug in SelectVariants when specifying family structure on the command line. --- .../walkers/annotator/VariantAnnotator.java | 6 +++ .../walkers/variantutils/SelectVariants.java | 2 +- .../sting/utils/MendelianViolation.java | 42 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) 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 fb3dbc3cf2..27a750f99a 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 @@ -162,6 +162,12 @@ 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; + private VariantAnnotatorEngine engine; private Collection indelBufferContext; 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 018c4dcc25..e1de06ec06 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 @@ -452,7 +452,7 @@ public void initialize() { throw new UserException.CouldNotCreateOutputFile(outMVFile, "Can't open output file", e); } } else - mvSet.add(new MendelianViolation(getToolkit(), MENDELIAN_VIOLATION_QUAL_THRESHOLD)); + 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)); diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index c6a07b5ce0..8da118174c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -1,11 +1,13 @@ package org.broadinstitute.sting.utils; +import org.apache.commons.lang.ArrayUtils; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.datasources.sample.Sample; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; @@ -32,6 +34,9 @@ public class MendelianViolation { private static Pattern FAMILY_PATTERN = Pattern.compile("(.*)\\+(.*)=(.*)"); + 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 }; + public String getSampleMom() { return sampleMom; @@ -168,4 +173,41 @@ public boolean isViolation() { return true; } + /** + * @return the likelihood ratio for a mendelian violation + */ + public double violationLikelihoodRatio(VariantContext vc) { + double[] logLikAssignments = new double[27]; + // the matrix to set up is + // MOM DAD CHILD + // |- AA + // AA AA | AB + // |- BB + // |- AA + // 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(); + int offset = 0; + for ( int oMom = 0; oMom < 3; oMom++ ) { + for ( int oDad = 0; oDad < 3; oDad++ ) { + for ( int oChild = 0; oChild < 3; oChild ++ ) { + logLikAssignments[offset++] = momGL[oMom] + dadGL[oDad] + childGL[oChild]; + } + } + } + double[] mvLiks = new double[12]; + double[] nonMVLiks = new double[15]; + for ( int i = 0; i < 12; i ++ ) { + mvLiks[i] = logLikAssignments[mvOffsets[i]]; + } + + for ( int i = 0; i < 15; i++) { + nonMVLiks[i] = logLikAssignments[nonMVOffsets[i]]; + } + + return MathUtils.log10sumLog10(mvLiks) - MathUtils.log10sumLog10(nonMVLiks); + } } From 1b47dcb1b56ee9a9f97fdab654d8d4545ffff1cc Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Wed, 21 Sep 2011 16:55:09 -0400 Subject: [PATCH 058/363] Removing old intron loss genotyper (though all of the development tree got rebased away, I hope); committing a new version of the likelihood calculation model and the genotyper, as well as the sequence simulator (and a QualityScoreHisotgramWalker to help with the simulation of read qualities). RFA will remain local for now. From 70335b2b0a967f31b1aa063ace4845bff413b086 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 21 Sep 2011 17:12:01 -0400 Subject: [PATCH 059/363] Hard clipping soft clipped reads to fix misalignments. Pre-softclipped reads (with high qual) are a complicated event to deal with in the Reduced Reads environment. I chose to hard clip them out for now and added a todo item to bring them back on in the future, perhaps as a variant region. --- .../sting/utils/clipreads/ReadClipper.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) 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 a8a3fad9ec..9b9ce4fdf6 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -1,6 +1,7 @@ package org.broadinstitute.sting.utils.clipreads; import com.google.java.contract.Requires; +import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; import net.sf.samtools.SAMRecord; @@ -8,6 +9,7 @@ import org.broadinstitute.sting.utils.sam.ReadUtils; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -128,6 +130,39 @@ public SAMRecord hardClipLowQualEnds(byte lowQual) { return this.clipRead(ClippingRepresentation.HARDCLIP_BASES); } + public SAMRecord hardClipSoftClippedBases () { + int readIndex = 0; + int cutLeft = -1; // first position to hard clip (inclusive) + int cutRight = -1; // first position to hard clip (inclusive) + boolean rightTail = false; // trigger to stop clipping the left tail and start cutting the right tail + + for (CigarElement cigarElement : read.getCigar().getCigarElements()) { + if (cigarElement.getOperator() == CigarOperator.SOFT_CLIP) { + if (rightTail) { + cutRight = readIndex; + } + else { + cutLeft = readIndex + cigarElement.getLength() - 1; + } + } + else + rightTail = true; + + if (cigarElement.getOperator().consumesReadBases()) + readIndex += cigarElement.getLength(); + } + + // It is extremely important that we cut the end first otherwise the read coordinates change. + if (cutRight >= 0) + this.addOp(new ClippingOp(cutRight, read.getReadLength() - 1)); + if (cutLeft >= 0) + this.addOp(new ClippingOp(0, cutLeft)); + + return clipRead(ClippingRepresentation.HARDCLIP_BASES); + } + + + /** * Return a new read corresponding to this.read that's been clipped according to ops, if any are present. * From faff6e401998ca39ed00553185fcc20e312f3130 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Wed, 21 Sep 2011 18:15:23 -0400 Subject: [PATCH 060/363] Failed to commit changes to the GATKReport required for more easy access when using the files as data sources (read: histograms) for walkers --- .../sting/gatk/report/GATKReportColumns.java | 14 +++++++++++++- .../sting/gatk/report/GATKReportTable.java | 4 ++++ .../sting/gatk/report/GATKReportVersion.java | 0 3 files changed, 17 insertions(+), 1 deletion(-) mode change 100644 => 100755 public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumns.java mode change 100644 => 100755 public/java/src/org/broadinstitute/sting/gatk/report/GATKReportVersion.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumns.java b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumns.java old mode 100644 new mode 100755 index a33631c857..a73123b6c6 --- a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumns.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportColumns.java @@ -24,12 +24,14 @@ package org.broadinstitute.sting.gatk.report; +import org.broadinstitute.sting.utils.collections.Pair; + import java.util.*; /** * Tracks a linked list of GATKReportColumn in order by name. */ -public class GATKReportColumns extends LinkedHashMap { +public class GATKReportColumns extends LinkedHashMap implements Iterable { private List columnNames = new ArrayList(); /** @@ -52,4 +54,14 @@ public GATKReportColumn put(String key, GATKReportColumn value) { columnNames.add(key); return super.put(key, value); } + + @Override + public Iterator iterator() { + return new Iterator() { + int offset = 0; + public boolean hasNext() { return offset < columnNames.size() ; } + public GATKReportColumn next() { return getByIndex(offset++); } + public void remove() { throw new UnsupportedOperationException("Cannot remove from a GATKReportColumn iterator"); } + }; + } } 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 3e3aa29a7b..2fd5ad7e32 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java +++ b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportTable.java @@ -286,6 +286,10 @@ private void verifyEntry(Object primaryKey, String columnName) { } } + public boolean containsKey(Object primaryKey) { + return primaryKeyColumn.contains(primaryKey); + } + /** * Set the value for a given position in the table * diff --git a/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportVersion.java b/public/java/src/org/broadinstitute/sting/gatk/report/GATKReportVersion.java old mode 100644 new mode 100755 From f9cdc119afec2d0d4dd76e04f82e138c9cf96dca Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Wed, 21 Sep 2011 18:16:42 -0400 Subject: [PATCH 061/363] Added a method to ReadUtils that converts reads of the form 10S20M10S to 40M (just unclips the soft-clips). Be careful when using this - if you're writing a bam file it will be potentially written out of order (since the previous alignment start was at the M, not the S). --- .../sting/utils/sam/ReadUtils.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) mode change 100644 => 100755 public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java old mode 100644 new mode 100755 index 5d3ef30866..60c9c1780b --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -681,6 +681,9 @@ public static int getRefCoordSoftUnclippedStart(SAMRecord read) { @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedEnd(SAMRecord read) { + if ( read.getCigar().numCigarElements() == 1 && read.getCigar().getCigarElement(0).getOperator().equals(CigarOperator.INSERTION)) { + return read.getUnclippedEnd(); + } int stop = read.getUnclippedStart(); if (readIsEntirelyInsertion(read)) @@ -787,5 +790,47 @@ else if (refCoord > read.getAlignmentEnd()) { return readBases; } + public static SAMRecord unclipSoftClippedBases(SAMRecord rec) { + int newReadStart = rec.getAlignmentStart(); + int newReadEnd = rec.getAlignmentEnd(); + List newCigarElements = new ArrayList(rec.getCigar().getCigarElements().size()); + int heldOver = -1; + boolean sSeen = false; + for ( CigarElement e : rec.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(rec.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++; + } + + rec.setCigar(new Cigar(finalCigarElements)); + rec.setAlignmentStart(newReadStart); + return rec; + } } From 5d0f284305ec4274ec255474a1cd536574fc1a39 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Wed, 21 Sep 2011 20:26:28 -0400 Subject: [PATCH 062/363] Fixing exome specific arguments to the VQSR in the methods development calling pipeline --- .../qscripts/MethodsDevelopmentCallingPipeline.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 29ee2769b3..d187c1e1ab 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala @@ -266,13 +266,18 @@ class MethodsDevelopmentCallingPipeline extends QScript { this.resource :+= new TaggedFile( t.dbsnpFile, "known=true,prior=2.0" ) this.resource :+= new TaggedFile( projectConsensus_1000G, "prior=8.0" ) this.use_annotation ++= List("QD", "HaplotypeScore", "MQRankSum", "ReadPosRankSum", "MQ", "FS") - if(t.nSamples >= 10) { + if(t.nSamples >= 10) { // InbreedingCoeff is a population-wide statistic that requires at least 10 samples to calculate this.use_annotation ++= List("InbreedingCoeff") } if(!t.isExome) { this.use_annotation ++= List("DP") - } else { + } else { // exome specific parameters + this.resource :+= new TaggedFile( badSites_1000G, "bad=true,prior=2.0" ) this.mG = 6 + if(t.nSamples <= 3) { // very few exome samples means very few variants + this.mG = 4 + this.percentBad = 0.04 + } } this.tranches_file = if ( goldStandard ) { t.goldStandardTranchesFile } else { t.tranchesFile } this.recal_file = if ( goldStandard ) { t.goldStandardRecalFile } else { t.recalFile } From 8f8b59a932923d29fdeb91a7ef90d1eef24ca1fd Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 21 Sep 2011 22:23:28 -0400 Subject: [PATCH 063/363] My interpretation of the VCF spec is that the FORMAT field should only be present if there is genotype/sample data. So the VCFCodec now throws an exception when it encounters such a case. I had to fix one of the integration test VCFs. --- .../utils/codecs/vcf/AbstractVCFCodec.java | 38 ++++++++++--------- .../sting/utils/codecs/vcf/VCFHeader.java | 17 +++------ .../sting/utils/exceptions/UserException.java | 6 +++ 3 files changed, 32 insertions(+), 29 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 83c7083d05..43b07476da 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 @@ -115,15 +115,21 @@ protected Object createHeader(List headerStrings, String line) { } 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) + 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); @@ -200,28 +206,24 @@ private VCFLocFeature(String chr, int start, int stop) { * @return a VariantContext */ public Feature decode(String line) { - return reallyDecode(line); - } - - private Feature reallyDecode(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; + // 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"); + // 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)]; + 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); + 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); + // 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); + return parseVCFLine(parts); } protected void generateException(String message) { 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 fd1c74993f..66e11bc1e2 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 @@ -35,9 +35,6 @@ public enum HEADER_FIELDS { // the header string indicator public static final String HEADER_INDICATOR = "#"; - /** do we have genotying data? */ - private boolean hasGenotypingData = false; - // were the input samples sorted originally (or are we sorting them)? private boolean samplesWereAlreadySorted = true; @@ -57,17 +54,15 @@ public VCFHeader(Set metaData) { * 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 genotype format field, and the sample names + * @param genotypeSampleNames the sample names */ public VCFHeader(Set metaData, Set genotypeSampleNames) { mMetaData = new TreeSet(); if ( metaData != null ) mMetaData.addAll(metaData); - for (String col : genotypeSampleNames) { - if (!col.equals("FORMAT")) - mGenotypeSampleNames.add(col); - } - if (genotypeSampleNames.size() > 0) hasGenotypingData = true; + + mGenotypeSampleNames.addAll(genotypeSampleNames); + loadVCFVersion(); loadMetaDataMaps(); @@ -157,7 +152,7 @@ public Set getGenotypeSamples() { * @return true if we have genotyping columns, false otherwise */ public boolean hasGenotypingData() { - return hasGenotypingData; + return mGenotypeSampleNames.size() > 0; } /** @@ -171,7 +166,7 @@ public boolean samplesWereAlreadySorted() { /** @return the column count */ public int getColumnCount() { - return HEADER_FIELDS.values().length + ((hasGenotypingData) ? mGenotypeSampleNames.size() + 1 : 0); + return HEADER_FIELDS.values().length + (hasGenotypingData() ? mGenotypeSampleNames.size() + 1 : 0); } /** 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 274c64f422..70f7387f47 100755 --- a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java +++ b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java @@ -174,6 +174,12 @@ public MalformedVCF(String message, int lineNo) { } } + public static class MalformedVCFHeader extends UserException { + public MalformedVCFHeader(String message) { + super(String.format("The provided VCF file has a malformed header: %s", message)); + } + } + public static class ReadMissingReadGroup extends MalformedBAM { public ReadMissingReadGroup(SAMRecord read) { super(read, String.format("Read %s is either missing the read group or its read group is not defined in the BAM header, both of which are required by the GATK. Please use http://www.broadinstitute.org/gsa/wiki/index.php/ReplaceReadGroups to fix this problem", read.getReadName())); From b8ea9ceb68f3ded503bac08cd95e3a15237d5930 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 21 Sep 2011 22:43:31 -0400 Subject: [PATCH 064/363] Adding integration test that uses the -V:dbsnp binding to make sure it won't fail later on if someone messes with Tribble. --- .../SelectVariantsIntegrationTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 20409d4caa..e4ded491b6 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 @@ -16,7 +16,7 @@ public void testComplexSelection() { String samplesFile = validationDataLocation + "SelectVariants.samples.txt"; WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(" -sn A -se '[CDH]' -sf " + samplesFile + " -env -ef -select 'DP < 250' --variant:VCF3 " + testfile), + baseTestString(" -sn A -se '[CDH]' -sf " + samplesFile + " -env -ef -select 'DP < 250' --variant " + testfile), 1, Arrays.asList("d18516c1963802e92cb9e425c0b75fd6") ); @@ -30,7 +30,7 @@ public void testSampleExclusion() { String samplesFile = validationDataLocation + "SelectVariants.samples.txt"; WalkerTestSpec spec = new WalkerTestSpec( - "-T SelectVariants -R " + b36KGReference + " -L 1:1-1000000 -o %s -NO_HEADER -xl_sn A -xl_sf " + samplesFile + " --variant:VCF3 " + testfile, + "-T SelectVariants -R " + b36KGReference + " -L 1:1-1000000 -o %s -NO_HEADER -xl_sn A -xl_sf " + samplesFile + " --variant " + testfile, 1, Arrays.asList("730f021fd6ecf1d195dabbee2e233bfd") ); @@ -43,7 +43,7 @@ public void testRepeatedLineSelection() { String testfile = validationDataLocation + "test.dup.vcf"; WalkerTestSpec spec = new WalkerTestSpec( - baseTestString(" -sn A -sn B -sn C --variant:VCF3 " + testfile), + baseTestString(" -sn A -sn B -sn C --variant " + testfile), 1, Arrays.asList("b74038779fe6485dbb8734ae48178356") ); @@ -56,7 +56,7 @@ public void testDiscordance() { String testFile = validationDataLocation + "NA12878.hg19.example1.vcf"; WalkerTestSpec spec = new WalkerTestSpec( - "-T SelectVariants -R " + hg19Reference + " -sn NA12878 -L 20:1012700-1020000 --variant:VCF " + b37hapmapGenotypes + " -disc:VCF " + testFile + " -o %s -NO_HEADER", + "-T SelectVariants -R " + hg19Reference + " -sn NA12878 -L 20:1012700-1020000 --variant " + b37hapmapGenotypes + " -disc " + testFile + " -o %s -NO_HEADER", 1, Arrays.asList("78e6842325f1f1bc9ab30d5e7737ee6e") ); @@ -69,7 +69,7 @@ public void testConcordance() { String testFile = validationDataLocation + "NA12878.hg19.example1.vcf"; WalkerTestSpec spec = new WalkerTestSpec( - "-T SelectVariants -R " + hg19Reference + " -sn NA12878 -L 20:1012700-1020000 -conc:VCF " + b37hapmapGenotypes + " --variant " + testFile + " -o %s -NO_HEADER", + "-T SelectVariants -R " + hg19Reference + " -sn NA12878 -L 20:1012700-1020000 -conc " + b37hapmapGenotypes + " --variant " + testFile + " -o %s -NO_HEADER", 1, Arrays.asList("d2ba3ea30a810f6f0fbfb1b643292b6a") ); @@ -90,16 +90,16 @@ public void testVariantTypeSelection() { executeTest("testVariantTypeSelection--" + testFile, spec); } - @Test(enabled=false) - public void testRemovePLs() { + @Test + public void testUsingDbsnpName() { String testFile = validationDataLocation + "combine.3.vcf"; WalkerTestSpec spec = new WalkerTestSpec( - "-T SelectVariants -R " + b36KGReference + " -sn NA12892 --variant " + testFile + " -o %s -NO_HEADER", + "-T SelectVariants -R " + b36KGReference + " -sn NA12892 --variant:dbsnp " + testFile + " -o %s -NO_HEADER", 1, - Arrays.asList("") + Arrays.asList("167a1265df820978a74c267df44d5c43") ); - executeTest("testWithPLs--" + testFile, spec); + executeTest("testUsingDbsnpName--" + testFile, spec); } } From 982c47bfa7ce48e2d4d53a445cff958212354ab0 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Thu, 22 Sep 2011 10:58:26 -0400 Subject: [PATCH 065/363] Remove duplicate effort in ReadUtils (with apologies to Mauricio) Big (but not major) cleanup of code in ILG - mostly excising the old likelihood model Activated the early-abort check for ILG. I think it should be better this way. --- .../java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 3 --- 1 file changed, 3 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 60c9c1780b..c328bbc5a1 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -681,9 +681,6 @@ public static int getRefCoordSoftUnclippedStart(SAMRecord read) { @Ensures({"result >= read.getUnclippedStart()", "result <= read.getUnclippedEnd() || readIsEntirelyInsertion(read)"}) public static int getRefCoordSoftUnclippedEnd(SAMRecord read) { - if ( read.getCigar().numCigarElements() == 1 && read.getCigar().getCigarElement(0).getOperator().equals(CigarOperator.INSERTION)) { - return read.getUnclippedEnd(); - } int stop = read.getUnclippedStart(); if (readIsEntirelyInsertion(read)) From 63758efc1745dd0dd085d43733da531bed63d301 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Thu, 22 Sep 2011 11:02:26 -0400 Subject: [PATCH 066/363] Adding in a qscript for running the ILG (as calculating the insert size distribution needs to happen first). From 623c49765d7a70cd87e68cee913bd133eaf168aa Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 22 Sep 2011 11:13:40 -0400 Subject: [PATCH 067/363] NO BAQ ON EXOMES! says the boss. --- .../queue/qscripts/MethodsDevelopmentCallingPipeline.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 29ee2769b3..34bc43ff5c 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala @@ -221,7 +221,7 @@ class MethodsDevelopmentCallingPipeline extends QScript { this.max_deletion_fraction = qscript.deletions this.out = t.rawVCF this.glm = org.broadinstitute.sting.gatk.walkers.genotyper.GenotypeLikelihoodsCalculationModel.Model.SNP - this.baq = if (noBAQ) {org.broadinstitute.sting.utils.baq.BAQ.CalculationMode.OFF} else {org.broadinstitute.sting.utils.baq.BAQ.CalculationMode.CALCULATE_AS_NECESSARY} + this.baq = if (noBAQ || t.isExome) {org.broadinstitute.sting.utils.baq.BAQ.CalculationMode.OFF} else {org.broadinstitute.sting.utils.baq.BAQ.CalculationMode.CALCULATE_AS_NECESSARY} this.analysisName = t.name + "_UGs" this.jobName = queueLogDir + t.name + ".snpcall" } From 3fdee2b9ed36072163f7284c3296a2f6d353c5d1 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 11:19:43 -0400 Subject: [PATCH 068/363] Merge from stable into unstable --- .../walkers/variantutils/CombineVariantsIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java index b65de9d360..0205fe0ebc 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java @@ -90,7 +90,7 @@ public void combinePLs(String file1, String file2, String md5) { @Test public void combineTrioCalls() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", "", "1d5a021387a8a86554db45a29f66140f"); } // official project VCF files in tabix format @Test public void combineTrioCallsMin() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", " -minimalVCF", "96941ee177b0614a9879af0ac3218963"); } // official project VCF files in tabix format - @Test public void combine2Indels() { combine2("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "a8a6e7589f22e0b6c5d222066b9a2093"); } + @Test public void combine2Indels() { combine2("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "f1c8720fde62687c2e861217670d8b3c"); } @Test public void combineSNPsAndIndels() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "e144b6283765494bfe8189ac59965083"); } @@ -137,7 +137,7 @@ public void combineDBSNPDuplicateSites() { WalkerTestSpec spec = new WalkerTestSpec( "-T CombineVariants -NO_HEADER -L 1:902000-903000 -o %s -R " + b37KGReference + " -V:v1 " + b37dbSNP132, 1, - Arrays.asList("")); + Arrays.asList("5969446769cb8377daa2db29304ae6b5")); executeTest("combineDBSNPDuplicateSites:", spec); } } \ No newline at end of file From a05c959e5a9729a023260ffc019dd2df44ce14b8 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 11:20:07 -0400 Subject: [PATCH 069/363] Empty unit tests for VariantContextUtils -- will be expanded over the day --- .../sting/utils/variantcontext/Genotype.java | 19 ++++- .../VariantContextUnitTest.java | 6 +- .../VariantContextUtilsUnitTest.java | 84 +++++++++++++++++++ 3 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java 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 85d7520035..5771685781 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -25,15 +25,32 @@ 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) { if ( alleles != null ) this.alleles = Collections.unmodifiableList(alleles); + if ( log10Likelihoods != null ) + attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); commonInfo = new InferredGeneticContext(sampleName, negLog10PError, filters, attributes); 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); } 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 f8e6da20af..663eb9ef6d 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -5,6 +5,7 @@ // the imports for unit testing. +import org.broadinstitute.sting.BaseTest; import org.testng.Assert; import org.testng.annotations.BeforeSuite; import org.testng.annotations.BeforeTest; @@ -14,10 +15,7 @@ import java.util.List; -/** - * Basic unit test for RecalData - */ -public class VariantContextUnitTest { +public class VariantContextUnitTest extends BaseTest { Allele A, Aref, T, Tref; Allele del, delRef, ATC, ATCref; diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java new file mode 100644 index 0000000000..9ca29d41c1 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -0,0 +1,84 @@ +/* + * 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 net.sf.picard.reference.IndexedFastaSequenceFile; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.gatk.refdata.tracks.FeatureManager; +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; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; + + +public class VariantContextUtilsUnitTest extends BaseTest { + Allele Aref, T, delRef, ATC; + Genotype snp1, snp2, indel1; + private GenomeLocParser genomeLocParser; + + @BeforeSuite + public void setup() { + final File referenceFile = new File(b37KGReference); + try { + IndexedFastaSequenceFile seq = new CachingIndexedFastaSequenceFile(referenceFile); + genomeLocParser = new GenomeLocParser(seq); + } + catch(FileNotFoundException ex) { + throw new UserException.CouldNotReadInputFile(referenceFile,ex); + } + + // alleles + Aref = Allele.create("A", true); + delRef = Allele.create("-", true); + T = Allele.create("T"); + ATC = Allele.create("ATC"); + + snp1 = new Genotype("snp1", Arrays.asList(Aref,T), 10, new double[]{10, 0, 20}); + snp2 = new Genotype("snp2", Arrays.asList(T,T), 15, new double[]{25, 15, 0}); + indel1 = new Genotype("indel1", Arrays.asList(delRef,ATC), 20, new double[]{20, 0, 30}); + } + + private VariantContext makeVC(String source, List alleles) { + return makeVC(source, alleles, null, null); + } + + private VariantContext makeVC(String source, List alleles, Collection genotypes, Set filters) { + int start = 10; + int stop = alleles.contains(ATC) ? start + 3 : start; + return new VariantContext(source, "1", start, stop, alleles, genotypes, 1.0, filters, null); + } +} From ba5f83fee2487f06d3969f8a55b9ee0214986c5f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 12:10:39 -0400 Subject: [PATCH 070/363] start of VariantContextUtils UnitTest -- tests rsID merging --- .../sting/utils/variantcontext/Genotype.java | 8 +- .../VariantContextUtilsUnitTest.java | 124 +++++++++++++++++- 2 files changed, 125 insertions(+), 7 deletions(-) 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 5771685781..2e233c18e8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -25,16 +25,16 @@ 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); - if ( log10Likelihoods != null ) - attributes.put(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, GenotypeLikelihoods.fromLog10Likelihoods(log10Likelihoods)); 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(); 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 9ca29d41c1..81007f9ffb 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -30,6 +30,7 @@ import net.sf.picard.reference.IndexedFastaSequenceFile; +import org.apache.log4j.Priority; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.refdata.tracks.FeatureManager; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -39,6 +40,7 @@ import org.testng.Assert; import org.testng.annotations.BeforeSuite; import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; import java.io.File; import java.io.FileNotFoundException; @@ -47,8 +49,9 @@ public class VariantContextUtilsUnitTest extends BaseTest { Allele Aref, T, delRef, ATC; - Genotype snp1, snp2, indel1; + Genotype ref1, snp1, snp2, indel1, indelref; private GenomeLocParser genomeLocParser; + VariantContext refVC, snpVC1, snpVC2, snpVC3, snpVC4, indelVC1, indelVC2, indelVC3; @BeforeSuite public void setup() { @@ -67,18 +70,133 @@ public void setup() { T = Allele.create("T"); ATC = Allele.create("ATC"); + ref1 = new Genotype("ref1", Arrays.asList(Aref, Aref), 5, new double[]{0, 5, 10}); snp1 = new Genotype("snp1", Arrays.asList(Aref,T), 10, new double[]{10, 0, 20}); snp2 = new Genotype("snp2", Arrays.asList(T,T), 15, new double[]{25, 15, 0}); + indelref = new Genotype("indelref", Arrays.asList(delRef,delRef), 25, new double[]{0, 25, 30}); indel1 = new Genotype("indel1", Arrays.asList(delRef,ATC), 20, new double[]{20, 0, 30}); + + refVC = makeVC("refvc", Arrays.asList(Aref), Arrays.asList(ref1)); + snpVC1 = makeVC("snpvc1", Arrays.asList(Aref, T), Arrays.asList(snp1)); + snpVC2 = makeVC("snpvc2", Arrays.asList(Aref, T), Arrays.asList(snp1, snp2)); + snpVC3 = makeVC("snpvc3", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1)); + snpVC4 = makeVC("snpvc4", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1, snp2)); + indelVC1 = makeVC("indelvc1", Arrays.asList(delRef), Arrays.asList(indelref)); + indelVC2 = makeVC("indelvc2", Arrays.asList(delRef, ATC), Arrays.asList(indel1)); + indelVC3 = makeVC("indelvc3", Arrays.asList(delRef, ATC), Arrays.asList(indelref, indel1)); } private VariantContext makeVC(String source, List alleles) { return makeVC(source, alleles, null, null); } + private VariantContext makeVC(String source, List alleles, Collection genotypes) { + return makeVC(source, alleles, genotypes, null); + } + private VariantContext makeVC(String source, List alleles, Collection genotypes, Set filters) { int start = 10; - int stop = alleles.contains(ATC) ? start + 3 : start; - return new VariantContext(source, "1", start, stop, alleles, genotypes, 1.0, filters, null); + int stop = start; // alleles.contains(ATC) ? start + 3 : start; + return new VariantContext(source, "1", start, stop, alleles, + VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes), + 1.0, filters, null, (byte)'C'); + } + + private class SimpleMergeTest extends TestDataProvider { + List inputVCs; + VariantContext expectedVC; + + private SimpleMergeTest(VariantContext... vcsArg) { + super(SimpleMergeTest.class); + LinkedList allVCs = new LinkedList(Arrays.asList(vcsArg)); + expectedVC = allVCs.pollLast(); + inputVCs = allVCs; + } + + public String toString() { + return String.format("SimpleMergeTest vc=%s expected=%s", inputVCs, expectedVC); + } + } + + @DataProvider(name = "simplemergedata") + public Object[][] createSimpleMergeData() { + // first, do no harm + new SimpleMergeTest(refVC, refVC); + new SimpleMergeTest(snpVC1, snpVC1); + new SimpleMergeTest(indelVC1, indelVC1); + new SimpleMergeTest(indelVC3, indelVC3); + + new SimpleMergeTest(refVC, snpVC1, snpVC3); + new SimpleMergeTest(snpVC1, snpVC2, snpVC2); + new SimpleMergeTest(refVC, snpVC2, snpVC4); + + new SimpleMergeTest(indelVC1, indelVC2, indelVC3); + new SimpleMergeTest(indelVC1, indelVC3, indelVC3); + new SimpleMergeTest(indelVC2, indelVC3, indelVC3); + + return SimpleMergeTest.getTests(SimpleMergeTest.class); + } + + private class SimpleMergeRSIDTest extends TestDataProvider { + List inputs; + String expected; + + private SimpleMergeRSIDTest(String... arg) { + super(SimpleMergeRSIDTest.class); + LinkedList allStrings = new LinkedList(Arrays.asList(arg)); + expected = allStrings.pollLast(); + inputs = allStrings; + } + + public String toString() { + return String.format("SimpleMergeRSIDTest vc=%s expected=%s", inputs, expected); + } + } + + @DataProvider(name = "simplemergersiddata") + public Object[][] createSimpleMergeRSIDData() { + new SimpleMergeRSIDTest(".", "."); + new SimpleMergeRSIDTest("rs1", "rs1"); + new SimpleMergeRSIDTest(".", "rs1", "rs1"); + new SimpleMergeRSIDTest("rs1", ".", "rs1"); + new SimpleMergeRSIDTest("rs1", "rs2", "rs1,rs2"); + new SimpleMergeRSIDTest("rs2", "rs1", "rs2,rs1"); + new SimpleMergeRSIDTest("rs2", "rs1", ".", "rs2,rs1"); + new SimpleMergeRSIDTest("rs2", ".", "rs1", "rs2,rs1"); + new SimpleMergeRSIDTest("rs1", ".", ".", "rs1"); + new SimpleMergeRSIDTest("rs1", "rs2", "rs3", "rs1,rs2,rs3"); + + return SimpleMergeRSIDTest.getTests(SimpleMergeRSIDTest.class); + } + + @Test(dataProvider = "simplemergersiddata") + public void testRSIDMerge(SimpleMergeRSIDTest cfg) { + List inputs = new ArrayList(); + for ( String id : cfg.inputs ) { + MutableVariantContext vc = new MutableVariantContext(snpVC1); + if ( ! id.equals(".") ) vc.setID(id); + inputs.add(vc); + + } + + VariantContext merged = myMerge(inputs); + Assert.assertEquals(merged.getID(), cfg.expected.equals(".") ? null : cfg.expected); } + + private VariantContext myMerge(List inputs) { + List priority = new ArrayList(); + for ( VariantContext vc : inputs ) priority.add(vc.getSource()); + + return VariantContextUtils.simpleMerge(genomeLocParser, + inputs, priority, + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.PRIORITIZE, true, false, "set", false, false); + } + + // todo -- add tests for subset merging, especially with correct PLs + // todo -- test priority list + // todo -- test FilteredRecordMergeType + // todo -- no annotate origin + // todo -- test set key + // todo -- test filtered are uncalled } From 9c1728416cf6773e510fc8c8843055189e1b03a4 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 22 Sep 2011 13:16:42 -0400 Subject: [PATCH 071/363] Revert "Updating md5 for fixed file" because this was fixed properly in unstable (but will break SnpEff if put into Stable). This reverts commit 6b4182c6ab3e214da4c73bc6f3687ac6d1c0b72c. --- .../sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java index 1b2a6e82e9..b4a8498e1c 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java @@ -33,7 +33,7 @@ public void test2() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString(b36KGReference, "symbolic_alleles_2.vcf"), 1, - Arrays.asList("3008d6f5044bc14801e5c58d985dec72")); + Arrays.asList("6645babc8c7d46be0da223477c7b1291")); executeTest("Test symbolic alleles mixed in with non-symbolic alleles", spec); } } From 4e9020c9f7bd6fc429090213ad06c9a6b46ecc70 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 22 Sep 2011 13:28:25 -0400 Subject: [PATCH 072/363] Fixed alignment start for hard clipping insertions --- .../sting/utils/clipreads/ClippingOp.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 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 951ab265c8..0c2def1c67 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -432,25 +432,37 @@ else if ( !readHasStarted && cigarElement.getOperator() == CigarOperator.INSERTI } private int calculateAlignmentStartShift(Cigar oldCigar, Cigar newCigar) { - int shift = 0; + int newShift = 0; + int oldShift = 0; + int deletionShift = 0; - // Rewind to previous start (by counting everything that was already clipped in this read) - for (CigarElement cigarElement : oldCigar.getCigarElements()) { - if (!cigarElement.getOperator().consumesReferenceBases()) - shift -= cigarElement.getLength(); + for (CigarElement cigarElement : newCigar.getCigarElements()) { + if (cigarElement.getOperator() == CigarOperator.HARD_CLIP || cigarElement.getOperator() == CigarOperator.SOFT_CLIP) + newShift += cigarElement.getLength(); else break; } - // Advance to new start (by counting everything new that has been clipped ) - for (CigarElement cigarElement : newCigar.getCigarElements()) { - if (!cigarElement.getOperator().consumesReferenceBases()) - shift += cigarElement.getLength(); + for (CigarElement cigarElement : oldCigar.getCigarElements()) { + if (cigarElement.getOperator() == CigarOperator.HARD_CLIP || cigarElement.getOperator() == CigarOperator.SOFT_CLIP ) + oldShift += Math.min(cigarElement.getLength(), newShift - oldShift); else break; } - return shift; + int basesClipped = 0; + for (CigarElement cigarElement : oldCigar.getCigarElements()) { + if (basesClipped > newShift) // are we beyond the clipped region? + break; + + else if (cigarElement.getOperator() == CigarOperator.DELETION) // if this is a deletion, we have to adjust the starting shift + deletionShift += cigarElement.getLength(); + + else if (cigarElement.getOperator() != CigarOperator.INSERTION) // if it's not an insertion or deletion, than it counts as hard clipped base. + basesClipped += cigarElement.getLength(); + } + + return newShift - oldShift + deletionShift; } private int calculateHardClippingAlignmentShift(CigarElement cigarElement, int clippedLength) { From 80d7300de4acaa172e256e91f02a962d6479c6e0 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 22 Sep 2011 13:28:42 -0400 Subject: [PATCH 073/363] Unit test was passing in FORMAT as one of the sample names. There used to be a hack in the VCFHeader to check for this and remove it and I couldn't figure out why, but now I know. Hack was removed and now the unit test passes in only the sample names as per the contract. --- .../sting/utils/genotype/vcf/VCFWriterUnitTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 a8e6593b13..35c6a49932 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 @@ -105,7 +105,6 @@ public void testBasicWriteAndRead() { public static VCFHeader createFakeHeader(Set metaData, Set additionalColumns) { metaData.add(new VCFHeaderLine(VCFHeaderVersion.VCF4_0.getFormatString(), VCFHeaderVersion.VCF4_0.getVersionString())); metaData.add(new VCFHeaderLine("two", "2")); - additionalColumns.add("FORMAT"); additionalColumns.add("extra1"); additionalColumns.add("extra2"); return new VCFHeader(metaData, additionalColumns); @@ -159,6 +158,6 @@ public void validateHeader(VCFHeader header) { Assert.assertTrue(additionalColumns.contains(key)); index++; } - Assert.assertEquals(index+1, additionalColumns.size() /* for the header field we don't see */); + Assert.assertEquals(index, additionalColumns.size()); } } From 1acf7945c544a923c5024b06a8322d351c889584 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 22 Sep 2011 14:51:14 -0400 Subject: [PATCH 074/363] Fixed hard clipped cigar and alignment start * Hard clipped Cigar now includes all insertions that were hard clipped and not the deletions. * The alignment start is now recalculated according to the new hard clipped cigar representation --- .../sting/utils/clipreads/ClippingOp.java | 6 +++--- .../sting/utils/clipreads/ReadClipper.java | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 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 0c2def1c67..47ce8165c1 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -458,7 +458,7 @@ private int calculateAlignmentStartShift(Cigar oldCigar, Cigar newCigar) { else if (cigarElement.getOperator() == CigarOperator.DELETION) // if this is a deletion, we have to adjust the starting shift deletionShift += cigarElement.getLength(); - else if (cigarElement.getOperator() != CigarOperator.INSERTION) // if it's not an insertion or deletion, than it counts as hard clipped base. + else basesClipped += cigarElement.getLength(); } @@ -474,8 +474,8 @@ private int calculateHardClippingAlignmentShift(CigarElement cigarElement, int c return -clippedLength; } - if (cigarElement.getOperator() == CigarOperator.DELETION) - return cigarElement.getLength(); +// if (cigarElement.getOperator() == CigarOperator.DELETION) +// return cigarElement.getLength(); return 0; } 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 9b9ce4fdf6..e2ecbe46b9 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -71,21 +71,21 @@ private int numDeletions(SAMRecord read) { private SAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart); - int stop = (refStop < 0) ? read.getReadLength() - 1: ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop); + int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStop); - if (start < 0 || stop > read.getReadLength() - 1 + numDeletions(read)) + if (start < 0 || stop > read.getReadLength() - 1) throw new ReviewedStingException("Trying to clip before the start or after the end of a read"); - // TODO add check in the Hardclip function - if ( start > stop ) - stop = ReadUtils.getReadCoordinateForReferenceCoordinate(read, ReadUtils.getRefCoordSoftUnclippedEnd(read)); - + if ( start > stop ) { +// stop = ReadUtils.getReadCoordinateForReferenceCoordinate(read, ReadUtils.getRefCoordSoftUnclippedEnd(read)); + throw new ReviewedStingException("START > STOP -- this should never happen -- call Mauricio!"); + } //This tries to fix the bug where the deletion is counted a read base and as a result, the hardCLipper runs into //an endless loop when hard clipping the cigar string because the read coordinates are not covered by the read - stop -= numDeletions(read); - if ( start > stop ) - start -= numDeletions(read); +// stop -= numDeletions(read); +// if ( start > stop ) +// start -= numDeletions(read); //System.out.println("Clipping start/stop: " + start + "/" + stop); From 68da555932761567f79f0b3bfdbc808dcf72ca5e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 15:16:37 -0400 Subject: [PATCH 075/363] UnitTest for simpleMerge for alleles --- .../VariantContextUtilsUnitTest.java | 178 ++++++++++++------ 1 file changed, 119 insertions(+), 59 deletions(-) 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 81007f9ffb..01d398c1ad 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -48,10 +48,8 @@ public class VariantContextUtilsUnitTest extends BaseTest { - Allele Aref, T, delRef, ATC; - Genotype ref1, snp1, snp2, indel1, indelref; + Allele Aref, T, C, delRef, ATC, ATCATC; private GenomeLocParser genomeLocParser; - VariantContext refVC, snpVC1, snpVC2, snpVC3, snpVC4, indelVC1, indelVC2, indelVC3; @BeforeSuite public void setup() { @@ -68,22 +66,9 @@ public void setup() { Aref = Allele.create("A", true); delRef = Allele.create("-", true); T = Allele.create("T"); + C = Allele.create("C"); ATC = Allele.create("ATC"); - - ref1 = new Genotype("ref1", Arrays.asList(Aref, Aref), 5, new double[]{0, 5, 10}); - snp1 = new Genotype("snp1", Arrays.asList(Aref,T), 10, new double[]{10, 0, 20}); - snp2 = new Genotype("snp2", Arrays.asList(T,T), 15, new double[]{25, 15, 0}); - indelref = new Genotype("indelref", Arrays.asList(delRef,delRef), 25, new double[]{0, 25, 30}); - indel1 = new Genotype("indel1", Arrays.asList(delRef,ATC), 20, new double[]{20, 0, 30}); - - refVC = makeVC("refvc", Arrays.asList(Aref), Arrays.asList(ref1)); - snpVC1 = makeVC("snpvc1", Arrays.asList(Aref, T), Arrays.asList(snp1)); - snpVC2 = makeVC("snpvc2", Arrays.asList(Aref, T), Arrays.asList(snp1, snp2)); - snpVC3 = makeVC("snpvc3", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1)); - snpVC4 = makeVC("snpvc4", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1, snp2)); - indelVC1 = makeVC("indelvc1", Arrays.asList(delRef), Arrays.asList(indelref)); - indelVC2 = makeVC("indelvc2", Arrays.asList(delRef, ATC), Arrays.asList(indel1)); - indelVC3 = makeVC("indelvc3", Arrays.asList(delRef, ATC), Arrays.asList(indelref, indel1)); + ATCATC = Allele.create("ATCATC"); } private VariantContext makeVC(String source, List alleles) { @@ -98,45 +83,124 @@ private VariantContext makeVC(String source, List alleles, Collection(), genotypes), + genotypes == null ? null : VariantContext.genotypeCollectionToMap(new TreeMap(), genotypes), 1.0, filters, null, (byte)'C'); } - private class SimpleMergeTest extends TestDataProvider { - List inputVCs; - VariantContext expectedVC; - - private SimpleMergeTest(VariantContext... vcsArg) { - super(SimpleMergeTest.class); - LinkedList allVCs = new LinkedList(Arrays.asList(vcsArg)); - expectedVC = allVCs.pollLast(); - inputVCs = allVCs; + // -------------------------------------------------------------------------------- + // + // Test allele merging + // + // -------------------------------------------------------------------------------- + + private class MergeAllelesTest extends TestDataProvider { + List> inputs; + List expected; + + private MergeAllelesTest(List... arg) { + super(MergeAllelesTest.class); + LinkedList> all = new LinkedList>(Arrays.asList(arg)); + expected = all.pollLast(); + inputs = all; } public String toString() { - return String.format("SimpleMergeTest vc=%s expected=%s", inputVCs, expectedVC); + return String.format("MergeAllelesTest input=%s expected=%s", inputs, expected); } } - - @DataProvider(name = "simplemergedata") - public Object[][] createSimpleMergeData() { + @DataProvider(name = "mergeAlleles") + public Object[][] mergeAllelesData() { // first, do no harm - new SimpleMergeTest(refVC, refVC); - new SimpleMergeTest(snpVC1, snpVC1); - new SimpleMergeTest(indelVC1, indelVC1); - new SimpleMergeTest(indelVC3, indelVC3); + new MergeAllelesTest(Arrays.asList(Aref), + Arrays.asList(Aref)); + + new MergeAllelesTest(Arrays.asList(Aref), + Arrays.asList(Aref), + Arrays.asList(Aref)); + + new MergeAllelesTest(Arrays.asList(Aref), + Arrays.asList(Aref, T), + Arrays.asList(Aref, T)); + + new MergeAllelesTest(Arrays.asList(Aref, C), + Arrays.asList(Aref, T), + Arrays.asList(Aref, C, T)); + + new MergeAllelesTest(Arrays.asList(Aref, T), + Arrays.asList(Aref, C), + Arrays.asList(Aref, C, T)); // sorted by allele + + new MergeAllelesTest(Arrays.asList(Aref, C, T), + Arrays.asList(Aref, C), + Arrays.asList(Aref, C, T)); + + new MergeAllelesTest(Arrays.asList(Aref, T, C), + Arrays.asList(Aref, C), + Arrays.asList(Aref, C, T)); // sorted by allele + + new MergeAllelesTest(Arrays.asList(delRef), + Arrays.asList(delRef)); // todo -- FIXME me GdA + + new MergeAllelesTest(Arrays.asList(delRef), + Arrays.asList(delRef, ATC), + Arrays.asList(delRef, ATC)); + + new MergeAllelesTest(Arrays.asList(delRef), + Arrays.asList(delRef, ATC, ATCATC), + Arrays.asList(delRef, ATC, ATCATC)); + + new MergeAllelesTest(Arrays.asList(delRef, ATCATC), + Arrays.asList(delRef, ATC, ATCATC), + Arrays.asList(delRef, ATC, ATCATC)); + + new MergeAllelesTest(Arrays.asList(delRef, ATC), + Arrays.asList(delRef, ATCATC), + Arrays.asList(delRef, ATC, ATCATC)); - new SimpleMergeTest(refVC, snpVC1, snpVC3); - new SimpleMergeTest(snpVC1, snpVC2, snpVC2); - new SimpleMergeTest(refVC, snpVC2, snpVC4); + return MergeAllelesTest.getTests(MergeAllelesTest.class); + } + + @Test(dataProvider = "mergeAlleles") + public void testMergeAlleles(MergeAllelesTest cfg) { + final List priority = new ArrayList(); + final List inputs = new ArrayList(); - new SimpleMergeTest(indelVC1, indelVC2, indelVC3); - new SimpleMergeTest(indelVC1, indelVC3, indelVC3); - new SimpleMergeTest(indelVC2, indelVC3, indelVC3); + int i = 0; + for ( final List alleles : cfg.inputs ) { + final String name = "vcf" + ++i; + priority.add(name); + inputs.add(makeVC(name, alleles)); + } - return SimpleMergeTest.getTests(SimpleMergeTest.class); + final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, + inputs, priority, + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.PRIORITIZE, false, false, "set", false, false); + Assert.assertEquals(merged.getAlleles(), cfg.expected); } +// VariantContext refVC, snpVC1, snpVC2, snpVC3, snpVC4, indelVC1, indelVC2, indelVC3; +// ref1 = new Genotype("ref1", Arrays.asList(Aref, Aref), 5, new double[]{0, 5, 10}); +// snp1 = new Genotype("snp1", Arrays.asList(Aref,T), 10, new double[]{10, 0, 20}); +// snp2 = new Genotype("snp2", Arrays.asList(T,T), 15, new double[]{25, 15, 0}); +// indelref = new Genotype("indelref", Arrays.asList(delRef,delRef), 25, new double[]{0, 25, 30}); +// indel1 = new Genotype("indel1", Arrays.asList(delRef,ATC), 20, new double[]{20, 0, 30}); +// +// refVC = makeVC("refvc", Arrays.asList(Aref), Arrays.asList(ref1)); +// snpVC1 = makeVC("snpvc1", Arrays.asList(Aref, T), Arrays.asList(snp1)); +// snpVC2 = makeVC("snpvc2", Arrays.asList(Aref, T), Arrays.asList(snp1, snp2)); +// snpVC3 = makeVC("snpvc3", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1)); +// snpVC4 = makeVC("snpvc4", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1, snp2)); +// indelVC1 = makeVC("indelvc1", Arrays.asList(delRef), Arrays.asList(indelref)); +// indelVC2 = makeVC("indelvc2", Arrays.asList(delRef, ATC), Arrays.asList(indel1)); +// indelVC3 = makeVC("indelvc3", Arrays.asList(delRef, ATC), Arrays.asList(indelref, indel1)); + + // -------------------------------------------------------------------------------- + // + // Test rsID merging + // + // -------------------------------------------------------------------------------- + private class SimpleMergeRSIDTest extends TestDataProvider { List inputs; String expected; @@ -156,10 +220,13 @@ public String toString() { @DataProvider(name = "simplemergersiddata") public Object[][] createSimpleMergeRSIDData() { new SimpleMergeRSIDTest(".", "."); + new SimpleMergeRSIDTest(".", ".", "."); new SimpleMergeRSIDTest("rs1", "rs1"); + new SimpleMergeRSIDTest("rs1", "rs1", "rs1"); new SimpleMergeRSIDTest(".", "rs1", "rs1"); new SimpleMergeRSIDTest("rs1", ".", "rs1"); new SimpleMergeRSIDTest("rs1", "rs2", "rs1,rs2"); + new SimpleMergeRSIDTest("rs1", "rs2", "rs1", "rs1,rs2"); // duplicates new SimpleMergeRSIDTest("rs2", "rs1", "rs2,rs1"); new SimpleMergeRSIDTest("rs2", "rs1", ".", "rs2,rs1"); new SimpleMergeRSIDTest("rs2", ".", "rs1", "rs2,rs1"); @@ -171,32 +238,25 @@ public Object[][] createSimpleMergeRSIDData() { @Test(dataProvider = "simplemergersiddata") public void testRSIDMerge(SimpleMergeRSIDTest cfg) { - List inputs = new ArrayList(); - for ( String id : cfg.inputs ) { + final 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); - } - VariantContext merged = myMerge(inputs); - Assert.assertEquals(merged.getID(), cfg.expected.equals(".") ? null : cfg.expected); - } - - private VariantContext myMerge(List inputs) { - List priority = new ArrayList(); - for ( VariantContext vc : inputs ) priority.add(vc.getSource()); - - return VariantContextUtils.simpleMerge(genomeLocParser, - inputs, priority, + final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, + inputs, null, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, - VariantContextUtils.GenotypeMergeType.PRIORITIZE, true, false, "set", false, false); + VariantContextUtils.GenotypeMergeType.UNSORTED, false, false, "set", false, false); + Assert.assertEquals(merged.getID(), cfg.expected.equals(".") ? null : cfg.expected); } // todo -- add tests for subset merging, especially with correct PLs - // todo -- test priority list + // todo -- test priority list: intersection, filtered in all, reference in all, X-filteredInX, X // todo -- test FilteredRecordMergeType // todo -- no annotate origin // todo -- test set key - // todo -- test filtered are uncalled } From 5bca1f732f5d81102064d083434299637c161706 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Thu, 22 Sep 2011 15:18:40 -0400 Subject: [PATCH 076/363] Little bug fixes in preQC: - Filter out per-library information in per-sample metrics so that our aggregator doesn't crash out while we figure out how to handle data per-library rather than per-sample. - Compare preQC metrics for custom captures to all exome data. Note that not every metric makes sense when compared to every other bait set, so a warning message is emitted on the summary page. From 39b54211d079dc2c70a2a9b4a38b9e08880b24df Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 22 Sep 2011 15:46:55 -0400 Subject: [PATCH 077/363] Fixed hard clipping soft clipped bases after hard clips if soft clipped bases were after a hard clipped section of the read, the hard clip was clipping the left soft clip tail as if it were a right tail. Mayhem. --- .../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 e2ecbe46b9..d0e7f83713 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -145,7 +145,7 @@ public SAMRecord hardClipSoftClippedBases () { cutLeft = readIndex + cigarElement.getLength() - 1; } } - else + else if (cigarElement.getOperator() != CigarOperator.HARD_CLIP) rightTail = true; if (cigarElement.getOperator().consumesReadBases()) From 4040d1c7d622168fe3b378e009344903f5b4cf45 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 22 Sep 2011 15:53:23 -0400 Subject: [PATCH 078/363] More conservative defaults for reduce reads From f1f7335e7b8710a6cea77cdb60f069e297f9410e Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Thu, 22 Sep 2011 16:46:38 -0400 Subject: [PATCH 079/363] In the trim to 95% curation function, ensure that no filtering happens when the sd is zero (e.g. when BAD_CYCLES is uniformly zero across all relevant samples). From 46ca33dc047d6a52406142d9dc323185938d7dab Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 17:04:32 -0400 Subject: [PATCH 080/363] TestDataProvider now can be named --- .../test/org/broadinstitute/sting/BaseTest.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java index 63faf1ab91..35a81770d7 100755 --- a/public/java/test/org/broadinstitute/sting/BaseTest.java +++ b/public/java/test/org/broadinstitute/sting/BaseTest.java @@ -132,15 +132,21 @@ public abstract class BaseTest { */ public static class TestDataProvider { private static final Map> tests = new HashMap>(); + private final String name; /** * Create a new TestDataProvider instance bound to the class variable C * @param c */ - public TestDataProvider(Class c) { + public TestDataProvider(Class c, String name) { if ( ! tests.containsKey(c) ) tests.put(c, new ArrayList()); tests.get(c).add(this); + this.name = name; + } + + public TestDataProvider(Class c) { + this(c, ""); } /** @@ -153,6 +159,11 @@ public static Object[][] getTests(Class c) { for ( Object x : tests.get(c) ) params2.add(new Object[]{x}); return params2.toArray(new Object[][]{}); } + + @Override + public String toString() { + return "TestDataProvider("+name+")"; + } } /** From 5cf82f923615b2dae0dee1b80f043fadacdfbc05 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 17:05:12 -0400 Subject: [PATCH 081/363] simpleMerge UnitTest tests filtered VC merging --- .../variantcontext/VariantContextUtils.java | 14 +- .../VariantContextUtilsUnitTest.java | 141 ++++++++++++++++-- 2 files changed, 135 insertions(+), 20 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 ca9c71eba9..03fe969b5f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -44,6 +44,11 @@ import java.util.*; public class VariantContextUtils { + 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 @@ -609,19 +614,20 @@ public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, 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 = "Intersection"; + setValue = MERGE_INTERSECTION; else if ( nFiltered == VCs.size() ) // everything was filtered out - setValue = "FilteredInAll"; + setValue = MERGE_FILTER_IN_ALL; else if ( variantSources.isEmpty() ) // everyone was reference - setValue = "ReferenceInAll"; + setValue = MERGE_REF_IN_ALL; else { LinkedHashSet s = new LinkedHashSet(); for ( VariantContext vc : VCs ) if ( vc.isVariant() ) - s.add( vc.isFiltered() ? "filterIn" + vc.getSource() : vc.getSource() ); + s.add( vc.isFiltered() ? MERGE_FILTER_PREFIX + vc.getSource() : vc.getSource() ); setValue = Utils.join("-", s); } 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 01d398c1ad..ff26d7245c 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -41,6 +41,7 @@ import org.testng.annotations.BeforeSuite; import org.testng.annotations.Test; import org.testng.annotations.DataProvider; +import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileNotFoundException; @@ -75,6 +76,14 @@ private VariantContext makeVC(String source, List alleles) { return makeVC(source, alleles, null, null); } + private VariantContext makeVC(String source, List alleles, String filter) { + return makeVC(source, alleles, filter.equals(".") ? null : new HashSet(Arrays.asList(filter))); + } + + private VariantContext makeVC(String source, List alleles, Set filters) { + return makeVC(source, alleles, null, filters); + } + private VariantContext makeVC(String source, List alleles, Collection genotypes) { return makeVC(source, alleles, genotypes, null); } @@ -179,22 +188,6 @@ public void testMergeAlleles(MergeAllelesTest cfg) { Assert.assertEquals(merged.getAlleles(), cfg.expected); } -// VariantContext refVC, snpVC1, snpVC2, snpVC3, snpVC4, indelVC1, indelVC2, indelVC3; -// ref1 = new Genotype("ref1", Arrays.asList(Aref, Aref), 5, new double[]{0, 5, 10}); -// snp1 = new Genotype("snp1", Arrays.asList(Aref,T), 10, new double[]{10, 0, 20}); -// snp2 = new Genotype("snp2", Arrays.asList(T,T), 15, new double[]{25, 15, 0}); -// indelref = new Genotype("indelref", Arrays.asList(delRef,delRef), 25, new double[]{0, 25, 30}); -// indel1 = new Genotype("indel1", Arrays.asList(delRef,ATC), 20, new double[]{20, 0, 30}); -// -// refVC = makeVC("refvc", Arrays.asList(Aref), Arrays.asList(ref1)); -// snpVC1 = makeVC("snpvc1", Arrays.asList(Aref, T), Arrays.asList(snp1)); -// snpVC2 = makeVC("snpvc2", Arrays.asList(Aref, T), Arrays.asList(snp1, snp2)); -// snpVC3 = makeVC("snpvc3", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1)); -// snpVC4 = makeVC("snpvc4", Arrays.asList(Aref, T), Arrays.asList(ref1, snp1, snp2)); -// indelVC1 = makeVC("indelvc1", Arrays.asList(delRef), Arrays.asList(indelref)); -// indelVC2 = makeVC("indelvc2", Arrays.asList(delRef, ATC), Arrays.asList(indel1)); -// indelVC3 = makeVC("indelvc3", Arrays.asList(delRef, ATC), Arrays.asList(indelref, indel1)); - // -------------------------------------------------------------------------------- // // Test rsID merging @@ -254,6 +247,122 @@ public void testRSIDMerge(SimpleMergeRSIDTest cfg) { Assert.assertEquals(merged.getID(), cfg.expected.equals(".") ? null : cfg.expected); } + // -------------------------------------------------------------------------------- + // + // Test filtered merging + // + // -------------------------------------------------------------------------------- + + private class MergeFilteredTest extends TestDataProvider { + List inputs; + VariantContext expected; + String setExpected; + VariantContextUtils.FilteredRecordMergeType type; + + + private MergeFilteredTest(String name, VariantContext input1, VariantContext input2, VariantContext expected, String setExpected) { + this(name, input1, input2, expected, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, setExpected); + } + + private MergeFilteredTest(String name, VariantContext input1, VariantContext input2, VariantContext expected, VariantContextUtils.FilteredRecordMergeType type, String setExpected) { + super(MergeFilteredTest.class, name); + LinkedList all = new LinkedList(Arrays.asList(input1, input2)); + this.expected = expected; + this.type = type; + inputs = all; + this.setExpected = setExpected; + } + + public String toString() { + return String.format("%s input=%s expected=%s", super.toString(), inputs, expected); + } + } + + @DataProvider(name = "mergeFiltered") + public Object[][] mergeFilteredData() { + new MergeFilteredTest("AllPass", + makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + VariantContextUtils.MERGE_INTERSECTION); + + new MergeFilteredTest("noFilters", + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "."), + VariantContextUtils.MERGE_INTERSECTION); + + new MergeFilteredTest("oneFiltered", + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), "."), + String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); + + new MergeFilteredTest("onePassOneFail", + makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); + + new MergeFilteredTest("FailOneUnfiltered", + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "."), + String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); + + new MergeFilteredTest("AllFiltered", + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), "FAIL"), + VariantContextUtils.MERGE_FILTER_IN_ALL); + + // test ALL vs. ANY + new MergeFilteredTest("OneFailAllUnfilteredArg", + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "FAIL"), + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ALL_UNFILTERED, + String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); + + // test excluding allele in filtered record + new MergeFilteredTest("DontIncludeAlleleOfFilteredRecords", + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), "."), + String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); + + // promotion of site from unfiltered to PASSES + new MergeFilteredTest("UnfilteredPlusPassIsPass", + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + VariantContextUtils.MERGE_INTERSECTION); + + return MergeFilteredTest.getTests(MergeFilteredTest.class); + } + + @Test(dataProvider = "mergeFiltered") + public void testMergeFiltered(MergeFilteredTest cfg) { + final List priority = new ArrayList(); + + for ( final VariantContext vc : cfg.inputs ) { + priority.add(vc.getSource()); + } + + final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, + cfg.inputs, priority, cfg.type, VariantContextUtils.GenotypeMergeType.PRIORITIZE, true, false, "set", false, false); + + // test alleles are equal + Assert.assertEquals(merged.getAlleles(), cfg.expected.getAlleles()); + + // test set field + Assert.assertEquals(merged.getAttribute("set"), cfg.setExpected); + + // test filter field + Assert.assertEquals(merged.getFilters(), cfg.expected.getFilters()); + } + + // todo -- add tests for subset merging, especially with correct PLs // todo -- test priority list: intersection, filtered in all, reference in all, X-filteredInX, X // todo -- test FilteredRecordMergeType From 30ab3af0c88d4d22a6d894c8d5e45863b4957445 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 17:14:59 -0400 Subject: [PATCH 082/363] A few more simpleMerge UnitTest tests for filtered vcs --- .../VariantContextUtilsUnitTest.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) 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 ff26d7245c..070fdaca49 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -304,12 +304,6 @@ public Object[][] mergeFilteredData() { makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); - new MergeFilteredTest("FailOneUnfiltered", - makeVC("1", Arrays.asList(Aref, T), "FAIL"), - makeVC("2", Arrays.asList(Aref, T), "."), - makeVC("3", Arrays.asList(Aref, T), "."), - String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); - new MergeFilteredTest("AllFiltered", makeVC("1", Arrays.asList(Aref, T), "FAIL"), makeVC("2", Arrays.asList(Aref, T), "FAIL"), @@ -317,6 +311,13 @@ public Object[][] mergeFilteredData() { VariantContextUtils.MERGE_FILTER_IN_ALL); // test ALL vs. ANY + new MergeFilteredTest("FailOneUnfiltered", + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "."), + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); + new MergeFilteredTest("OneFailAllUnfilteredArg", makeVC("1", Arrays.asList(Aref, T), "FAIL"), makeVC("2", Arrays.asList(Aref, T), "."), @@ -338,6 +339,18 @@ public Object[][] mergeFilteredData() { makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), VariantContextUtils.MERGE_INTERSECTION); + new MergeFilteredTest("RefInAll", + makeVC("1", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + VariantContextUtils.MERGE_REF_IN_ALL); + + new MergeFilteredTest("RefInOne", + makeVC("1", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + "2"); + return MergeFilteredTest.getTests(MergeFilteredTest.class); } From dab7232e9af623a3d085988bac751ee4df229748 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 22 Sep 2011 17:26:11 -0400 Subject: [PATCH 083/363] simpleMerge UnitTest for not annotating and annotating to different info key --- .../VariantContextUtilsUnitTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 070fdaca49..77e4391a3c 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -375,10 +375,26 @@ public void testMergeFiltered(MergeFilteredTest cfg) { Assert.assertEquals(merged.getFilters(), cfg.expected.getFilters()); } + @Test + public void testAnnotationSet() { + for ( final boolean annotate : Arrays.asList(true, false)) { + for ( final String set : Arrays.asList("set", "combine", "x")) { + final List priority = Arrays.asList("1", "2"); + VariantContext vc1 = makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS); + VariantContext vc2 = makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS); + + final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, + Arrays.asList(vc1, vc2), priority, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.PRIORITIZE, annotate, false, set, false, false); + + if ( annotate ) + Assert.assertEquals(merged.getAttribute(set), VariantContextUtils.MERGE_INTERSECTION); + else + Assert.assertFalse(merged.hasAttribute(set)); + } + } + } // todo -- add tests for subset merging, especially with correct PLs - // todo -- test priority list: intersection, filtered in all, reference in all, X-filteredInX, X - // todo -- test FilteredRecordMergeType - // todo -- no annotate origin - // todo -- test set key + // todo -- test priority list } From a8e0fb26ea309a6620d540310f1224fb08ec7440 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 23 Sep 2011 07:33:20 -0400 Subject: [PATCH 084/363] Updating md5 because the file changed --- .../sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java index b4a8498e1c..1b2a6e82e9 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/CNV/SymbolicAllelesIntegrationTest.java @@ -33,7 +33,7 @@ public void test2() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString(b36KGReference, "symbolic_alleles_2.vcf"), 1, - Arrays.asList("6645babc8c7d46be0da223477c7b1291")); + Arrays.asList("3008d6f5044bc14801e5c58d985dec72")); executeTest("Test symbolic alleles mixed in with non-symbolic alleles", spec); } } From 054c475416ccc474cafbfeb013380d876b660428 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 23 Sep 2011 07:49:59 -0400 Subject: [PATCH 085/363] Updating md5 because the file changed From 4397ce8653eab67546e261faef5ebd5619057b79 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 23 Sep 2011 08:23:46 -0400 Subject: [PATCH 086/363] Moved removePLs to VariantContextUtils --- .../sting/utils/variantcontext/Genotype.java | 7 ------- .../sting/utils/variantcontext/VariantContextUtils.java | 9 ++++++++- 2 files changed, 8 insertions(+), 8 deletions(-) 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 2e233c18e8..fd8c70abe5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -74,13 +74,6 @@ public static Genotype modifyAttributes(Genotype g, Map attribut return new Genotype(g.getSampleName(), g.getAlleles(), g.getNegLog10PError(), g.filtersWereApplied() ? g.getFilters() : null, attributes, g.isPhased()); } - 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()); - } - 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()); } 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 03fe969b5f..93d4f6f5cf 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -159,6 +159,13 @@ private static String makePrecisionFormatStringFromDenominatorValue(double maxVa 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 */ @@ -740,7 +747,7 @@ 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() ? Genotype.removePLs(g.getValue()) : g.getValue()); + newGs.put(g.getKey(), g.getValue().hasLikelihoods() ? removePLs(g.getValue()) : g.getValue()); } return newGs; From a9f073fa68f500fbad4e068b75e3ff924a0d7d66 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 23 Sep 2011 08:24:49 -0400 Subject: [PATCH 087/363] Genotype merging unit tests for simpleMerge -- Remaining TODOs are all for GdA --- .../VariantContextUtilsUnitTest.java | 319 +++++++++++++----- 1 file changed, 234 insertions(+), 85 deletions(-) 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 77e4391a3c..c1025a7de3 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -21,14 +21,8 @@ * 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 net.sf.picard.reference.IndexedFastaSequenceFile; import org.apache.log4j.Priority; import org.broadinstitute.sting.BaseTest; @@ -47,7 +41,6 @@ import java.io.FileNotFoundException; import java.util.*; - public class VariantContextUtilsUnitTest extends BaseTest { Allele Aref, T, C, delRef, ATC, ATCATC; private GenomeLocParser genomeLocParser; @@ -72,10 +65,22 @@ public void setup() { ATCATC = Allele.create("ATCATC"); } + private Genotype makeG(String sample, Allele a1, Allele a2) { + return new Genotype(sample, Arrays.asList(a1, a2)); + } + + private Genotype makeG(String sample, Allele a1, Allele a2, double log10pError) { + return new Genotype(sample, Arrays.asList(a1, a2), log10pError); + } + private VariantContext makeVC(String source, List alleles) { return makeVC(source, alleles, null, null); } + private VariantContext makeVC(String source, List alleles, Genotype... g1) { + return makeVC(source, alleles, Arrays.asList(g1)); + } + private VariantContext makeVC(String source, List alleles, String filter) { return makeVC(source, alleles, filter.equals(".") ? null : new HashSet(Arrays.asList(filter))); } @@ -121,66 +126,66 @@ public String toString() { public Object[][] mergeAllelesData() { // first, do no harm new MergeAllelesTest(Arrays.asList(Aref), - Arrays.asList(Aref)); + Arrays.asList(Aref)); new MergeAllelesTest(Arrays.asList(Aref), - Arrays.asList(Aref), - Arrays.asList(Aref)); + Arrays.asList(Aref), + Arrays.asList(Aref)); new MergeAllelesTest(Arrays.asList(Aref), - Arrays.asList(Aref, T), - Arrays.asList(Aref, T)); + Arrays.asList(Aref, T), + Arrays.asList(Aref, T)); new MergeAllelesTest(Arrays.asList(Aref, C), - Arrays.asList(Aref, T), - Arrays.asList(Aref, C, T)); + Arrays.asList(Aref, T), + Arrays.asList(Aref, C, T)); new MergeAllelesTest(Arrays.asList(Aref, T), - Arrays.asList(Aref, C), - Arrays.asList(Aref, C, T)); // sorted by allele + Arrays.asList(Aref, C), + Arrays.asList(Aref, C, T)); // sorted by allele new MergeAllelesTest(Arrays.asList(Aref, C, T), - Arrays.asList(Aref, C), - Arrays.asList(Aref, C, T)); + Arrays.asList(Aref, C), + Arrays.asList(Aref, C, T)); new MergeAllelesTest(Arrays.asList(Aref, T, C), - Arrays.asList(Aref, C), - Arrays.asList(Aref, C, T)); // sorted by allele + Arrays.asList(Aref, C), + Arrays.asList(Aref, C, T)); // sorted by allele new MergeAllelesTest(Arrays.asList(delRef), - Arrays.asList(delRef)); // todo -- FIXME me GdA + Arrays.asList(delRef)); // todo -- FIXME me GdA new MergeAllelesTest(Arrays.asList(delRef), - Arrays.asList(delRef, ATC), - Arrays.asList(delRef, ATC)); + Arrays.asList(delRef, ATC), + Arrays.asList(delRef, ATC)); new MergeAllelesTest(Arrays.asList(delRef), - Arrays.asList(delRef, ATC, ATCATC), - Arrays.asList(delRef, ATC, ATCATC)); + Arrays.asList(delRef, ATC, ATCATC), + Arrays.asList(delRef, ATC, ATCATC)); new MergeAllelesTest(Arrays.asList(delRef, ATCATC), - Arrays.asList(delRef, ATC, ATCATC), - Arrays.asList(delRef, ATC, ATCATC)); + Arrays.asList(delRef, ATC, ATCATC), + Arrays.asList(delRef, ATC, ATCATC)); new MergeAllelesTest(Arrays.asList(delRef, ATC), - Arrays.asList(delRef, ATCATC), - Arrays.asList(delRef, ATC, ATCATC)); + Arrays.asList(delRef, ATCATC), + Arrays.asList(delRef, ATC, ATCATC)); return MergeAllelesTest.getTests(MergeAllelesTest.class); } @Test(dataProvider = "mergeAlleles") public void testMergeAlleles(MergeAllelesTest cfg) { - final List priority = new ArrayList(); final List inputs = new ArrayList(); int i = 0; for ( final List alleles : cfg.inputs ) { final String name = "vcf" + ++i; - priority.add(name); inputs.add(makeVC(name, alleles)); } + final List priority = vcs2priority(inputs); + final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, inputs, priority, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, @@ -281,87 +286,82 @@ public String toString() { @DataProvider(name = "mergeFiltered") public Object[][] mergeFilteredData() { new MergeFilteredTest("AllPass", - makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - VariantContextUtils.MERGE_INTERSECTION); + makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + VariantContextUtils.MERGE_INTERSECTION); new MergeFilteredTest("noFilters", - makeVC("1", Arrays.asList(Aref, T), "."), - makeVC("2", Arrays.asList(Aref, T), "."), - makeVC("3", Arrays.asList(Aref, T), "."), - VariantContextUtils.MERGE_INTERSECTION); + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "."), + VariantContextUtils.MERGE_INTERSECTION); new MergeFilteredTest("oneFiltered", - makeVC("1", Arrays.asList(Aref, T), "."), - makeVC("2", Arrays.asList(Aref, T), "FAIL"), - makeVC("3", Arrays.asList(Aref, T), "."), - String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), "."), + String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); new MergeFilteredTest("onePassOneFail", - makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - makeVC("2", Arrays.asList(Aref, T), "FAIL"), - makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); + makeVC("1", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); new MergeFilteredTest("AllFiltered", - makeVC("1", Arrays.asList(Aref, T), "FAIL"), - makeVC("2", Arrays.asList(Aref, T), "FAIL"), - makeVC("3", Arrays.asList(Aref, T), "FAIL"), - VariantContextUtils.MERGE_FILTER_IN_ALL); + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), "FAIL"), + VariantContextUtils.MERGE_FILTER_IN_ALL); // test ALL vs. ANY new MergeFilteredTest("FailOneUnfiltered", - makeVC("1", Arrays.asList(Aref, T), "FAIL"), - makeVC("2", Arrays.asList(Aref, T), "."), - makeVC("3", Arrays.asList(Aref, T), "."), - VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, - String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "."), + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); new MergeFilteredTest("OneFailAllUnfilteredArg", - makeVC("1", Arrays.asList(Aref, T), "FAIL"), - makeVC("2", Arrays.asList(Aref, T), "."), - makeVC("3", Arrays.asList(Aref, T), "FAIL"), - VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ALL_UNFILTERED, - String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); + makeVC("1", Arrays.asList(Aref, T), "FAIL"), + makeVC("2", Arrays.asList(Aref, T), "."), + makeVC("3", Arrays.asList(Aref, T), "FAIL"), + VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ALL_UNFILTERED, + String.format("%s1-2", VariantContextUtils.MERGE_FILTER_PREFIX)); // test excluding allele in filtered record new MergeFilteredTest("DontIncludeAlleleOfFilteredRecords", - makeVC("1", Arrays.asList(Aref, T), "."), - makeVC("2", Arrays.asList(Aref, T), "FAIL"), - makeVC("3", Arrays.asList(Aref, T), "."), - String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), "FAIL"), + makeVC("3", Arrays.asList(Aref, T), "."), + String.format("1-%s2", VariantContextUtils.MERGE_FILTER_PREFIX)); // promotion of site from unfiltered to PASSES new MergeFilteredTest("UnfilteredPlusPassIsPass", - makeVC("1", Arrays.asList(Aref, T), "."), - makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - VariantContextUtils.MERGE_INTERSECTION); + makeVC("1", Arrays.asList(Aref, T), "."), + makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + VariantContextUtils.MERGE_INTERSECTION); new MergeFilteredTest("RefInAll", - makeVC("1", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), - makeVC("2", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), - makeVC("3", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), - VariantContextUtils.MERGE_REF_IN_ALL); + makeVC("1", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + VariantContextUtils.MERGE_REF_IN_ALL); new MergeFilteredTest("RefInOne", - makeVC("1", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), - makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), - "2"); + makeVC("1", Arrays.asList(Aref), VariantContext.PASSES_FILTERS), + makeVC("2", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + makeVC("3", Arrays.asList(Aref, T), VariantContext.PASSES_FILTERS), + "2"); return MergeFilteredTest.getTests(MergeFilteredTest.class); } @Test(dataProvider = "mergeFiltered") public void testMergeFiltered(MergeFilteredTest cfg) { - final List priority = new ArrayList(); - - for ( final VariantContext vc : cfg.inputs ) { - priority.add(vc.getSource()); - } - + final List priority = vcs2priority(cfg.inputs); final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, cfg.inputs, priority, cfg.type, VariantContextUtils.GenotypeMergeType.PRIORITIZE, true, false, "set", false, false); @@ -375,6 +375,148 @@ public void testMergeFiltered(MergeFilteredTest cfg) { Assert.assertEquals(merged.getFilters(), cfg.expected.getFilters()); } + // -------------------------------------------------------------------------------- + // + // Test genotype merging + // + // -------------------------------------------------------------------------------- + + private class MergeGenotypesTest extends TestDataProvider { + List inputs; + VariantContext expected; + List priority; + + private MergeGenotypesTest(String name, String priority, VariantContext... arg) { + super(MergeGenotypesTest.class, name); + LinkedList all = new LinkedList(Arrays.asList(arg)); + this.expected = all.pollLast(); + inputs = all; + this.priority = Arrays.asList(priority.split(",")); + } + + public String toString() { + return String.format("%s input=%s expected=%s", super.toString(), inputs, expected); + } + } + + @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))); + + 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))); + + 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))); + + 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))); + + 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))); + + 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, C, T), 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))); + + 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))); + + // todo -- GDA -- add tests for merging correct PLs + + return MergeGenotypesTest.getTests(MergeGenotypesTest.class); + } + + // todo -- add test for GenotypeMergeType UNIQUIFY, REQUIRE_UNIQUE + + @Test(dataProvider = "mergeGenotypes") + public void testMergeGenotypes(MergeGenotypesTest cfg) { + final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, + cfg.inputs, cfg.priority, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.PRIORITIZE, true, false, "set", false, false); + + // test alleles are equal + Assert.assertEquals(merged.getAlleles(), cfg.expected.getAlleles()); + + // test genotypes + assertGenotypesAreMostlyEqual(merged.getGenotypes(), cfg.expected.getGenotypes()); + } + + // necessary to not overload equals for genotypes + private void assertGenotypesAreMostlyEqual(Map actual, Map expected) { + if (actual == expected) { + return; + } + + if (actual == null || expected == null) { + Assert.fail("Maps not equal: expected: " + expected + " and actual: " + actual); + } + + if (actual.size() != expected.size()) { + 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); + + Assert.assertEquals(value.alleles, expectedValue.alleles); + Assert.assertEquals(value.getNegLog10PError(), expectedValue.getNegLog10PError()); + Assert.assertEquals(value.hasLikelihoods(), expectedValue.hasLikelihoods()); + if ( value.hasLikelihoods() ) + Assert.assertEquals(value.getLikelihoods(), expectedValue.getLikelihoods()); + } + } + + @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 merged = VariantContextUtils.simpleMerge(genomeLocParser, + Arrays.asList(vc1, vc2), null, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.UNIQUIFY, false, false, "set", false, false); + + // test genotypes + Assert.assertEquals(merged.getGenotypes().keySet(), new HashSet(Arrays.asList("s1.1", "s1.2"))); + } + + @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 merged = VariantContextUtils.simpleMerge(genomeLocParser, + Arrays.asList(vc1, vc2), null, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, + VariantContextUtils.GenotypeMergeType.REQUIRE_UNIQUE, false, false, "set", false, false); + } + + // -------------------------------------------------------------------------------- + // + // Misc. tests + // + // -------------------------------------------------------------------------------- + @Test public void testAnnotationSet() { for ( final boolean annotate : Arrays.asList(true, false)) { @@ -395,6 +537,13 @@ public void testAnnotationSet() { } } - // todo -- add tests for subset merging, especially with correct PLs - // todo -- test priority list + private static final List vcs2priority(final Collection vcs) { + final List priority = new ArrayList(); + + for ( final VariantContext vc : vcs ) { + priority.add(vc.getSource()); + } + + return priority; + } } From 106a26c42da8780cd33d7186ec0a658830ad6819 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 23 Sep 2011 08:25:20 -0400 Subject: [PATCH 088/363] Minor file cleanup --- .../utils/variantcontext/VariantContextUtilsUnitTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 c1025a7de3..15a5549b2c 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -24,18 +24,14 @@ package org.broadinstitute.sting.utils.variantcontext; import net.sf.picard.reference.IndexedFastaSequenceFile; -import org.apache.log4j.Priority; import org.broadinstitute.sting.BaseTest; -import org.broadinstitute.sting.gatk.refdata.tracks.FeatureManager; 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; import org.testng.annotations.BeforeSuite; -import org.testng.annotations.Test; import org.testng.annotations.DataProvider; -import org.yaml.snakeyaml.Yaml; +import org.testng.annotations.Test; import java.io.File; import java.io.FileNotFoundException; @@ -446,8 +442,6 @@ public Object[][] mergeGenotypesData() { return MergeGenotypesTest.getTests(MergeGenotypesTest.class); } - // todo -- add test for GenotypeMergeType UNIQUIFY, REQUIRE_UNIQUE - @Test(dataProvider = "mergeGenotypes") public void testMergeGenotypes(MergeGenotypesTest cfg) { final VariantContext merged = VariantContextUtils.simpleMerge(genomeLocParser, From e3d4efb2834b6aa5049a36b3e7ad4ca9458140d2 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 23 Sep 2011 11:55:21 -0400 Subject: [PATCH 089/363] Remove N2 EXACT model code, which should never be used --- .../genotyper/ExactAFCalculationModel.java | 251 +----------------- .../genotyper/UnifiedArgumentCollection.java | 5 - 2 files changed, 9 insertions(+), 247 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 6ae437b271..8a3a978237 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 @@ -48,27 +48,12 @@ public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // code for testing purposes // private final static boolean DEBUG = false; - private final static boolean PRINT_LIKELIHOODS = false; - private final static int N_CYCLES = 1; - private SimpleTimer timerExpt = new SimpleTimer("linearExactBanded"); - private SimpleTimer timerGS = new SimpleTimer("linearExactGS"); - private final static boolean COMPARE_TO_GS = false; - - public enum ExactCalculation { - N2_GOLD_STANDARD, - LINEAR_EXPERIMENTAL - } - private final static double MAX_LOG10_ERROR_TO_STOP_EARLY = 6; // we want the calculation to be accurate to 1 / 10^6 - - private boolean SIMPLE_GREEDY_GENOTYPER = false; - + 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. - final private ExactCalculation calcToUse; protected ExactAFCalculationModel(UnifiedArgumentCollection UAC, int N, Logger logger, PrintStream verboseWriter) { super(UAC, N, logger, verboseWriter); - calcToUse = UAC.EXACT_CALCULATION_TYPE; } public void getLog10PNonRef(RefMetaDataTracker tracker, @@ -76,43 +61,12 @@ public void getLog10PNonRef(RefMetaDataTracker tracker, Map GLs, Setalleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { - // todo -- REMOVE ME AFTER TESTING - // todo -- REMOVE ME AFTER TESTING - // todo -- REMOVE ME AFTER TESTING - double[] gsPosteriors; - if ( COMPARE_TO_GS ) // due to annoying special values in incoming array, we have to clone up here - gsPosteriors = log10AlleleFrequencyPosteriors.clone(); - - int idxAA = GenotypeType.AA.ordinal(); - int idxAB = GenotypeType.AB.ordinal(); - int idxBB = GenotypeType.BB.ordinal(); - - // todo -- remove me after testing - if ( N_CYCLES > 1 ) { - for ( int i = 0; i < N_CYCLES; i++) { - timerGS.restart(); - linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors.clone(), idxAA, idxAB, idxBB); - timerGS.stop(); - - timerExpt.restart(); - linearExactBanded(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors.clone()); - timerExpt.stop(); - } - - System.out.printf("good = %.2f, expt = %.2f, delta = %.2f%n", - timerGS.getElapsedTime(), timerExpt.getElapsedTime(), timerExpt.getElapsedTime()-timerGS.getElapsedTime()); - } - - int lastK = -1; - - int numAlleles = alleles.size(); + 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; - - double[][] posteriorCache = new double[numAlleles-1][]; - double[] bestAFguess = new double[numAlleles-1]; - for (int k=1; k < numAlleles; k++) { // multi-allelic approximation, part 1: Ideally // for each alt allele compute marginal (suboptimal) posteriors - @@ -121,24 +75,17 @@ public void getLog10PNonRef(RefMetaDataTracker tracker, // 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 - idxAA = 0; - idxAB = k; + 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: - idxBB = idxDiag; + final int idxBB = idxDiag; idxDiag += incr--; - // todo - possible cleanup - switch ( calcToUse ) { - case N2_GOLD_STANDARD: - lastK = gdaN2GoldStandard(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, idxAA, idxAB, idxBB); - break; - case LINEAR_EXPERIMENTAL: - lastK = linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, idxAA, idxAB, idxBB); - break; - } + final int lastK = linearExact(GLs, log10AlleleFrequencyPriors, log10AlleleFrequencyPosteriors, idxAA, idxAB, idxBB); + if (numAlleles > 2) { posteriorCache[k-1] = log10AlleleFrequencyPosteriors.clone(); bestAFguess[k-1] = (double)MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors); @@ -153,39 +100,14 @@ public void getLog10PNonRef(RefMetaDataTracker tracker, log10AlleleFrequencyPosteriors[k] = (posteriorCache[mostLikelyAlleleIdx][k]); } - // todo -- REMOVE ME AFTER TESTING - // todo -- REMOVE ME AFTER TESTING - // todo -- REMOVE ME AFTER TESTING - if ( COMPARE_TO_GS ) { - gdaN2GoldStandard(GLs, log10AlleleFrequencyPriors, gsPosteriors, idxAA, idxAB, idxBB); - - double log10thisPVar = Math.log10(MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors)[0]); - double log10gsPVar = Math.log10(MathUtils.normalizeFromLog10(gsPosteriors)[0]); - boolean eq = (log10thisPVar == Double.NEGATIVE_INFINITY && log10gsPVar == Double.NEGATIVE_INFINITY) || MathUtils.compareDoubles(log10thisPVar, log10gsPVar, 1e-4) == 0; - - if ( ! eq || PRINT_LIKELIHOODS ) { - System.out.printf("----------------------------------------%n"); - for (int k=0; k < log10AlleleFrequencyPosteriors.length; k++) { - double x = log10AlleleFrequencyPosteriors[k]; - System.out.printf(" %d\t%.2f\t%.2f\t%b%n", k, - x < -1e10 ? Double.NEGATIVE_INFINITY : x, gsPosteriors[k], - log10AlleleFrequencyPosteriors[k] == gsPosteriors[k]); - } - System.out.printf("MAD_AC\t%d\t%d\t%.2f\t%.2f\t%.6f%n", - ref.getLocus().getStart(), lastK, log10thisPVar, log10gsPVar, log10thisPVar - log10gsPVar); - } - } - } private static final ArrayList getGLs(Map GLs) { ArrayList genotypeLikelihoods = new ArrayList(); - //int j = 0; genotypeLikelihoods.add(new double[]{0.0,0.0,0.0}); // dummy for ( Genotype sample : GLs.values() ) { if ( sample.hasLikelihoods() ) { - //double[] genotypeLikelihoods = MathUtils.normalizeFromLog10(GLs.get(sample).getLikelihoods()); double[] gls = sample.getLikelihoods().getAsVector(); if (MathUtils.sum(gls) < SUM_GL_THRESH_NOCALL) @@ -240,84 +162,6 @@ final public double[] getkMinus0() { } } - // now with banding - public int linearExactBanded(Map GLs, - double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors) { - throw new NotImplementedException(); -// final int numSamples = GLs.size(); -// final int numChr = 2*numSamples; -// final double[][] genotypeLikelihoods = getGLs(GLs); -// -// 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; -// final int BAND_SIZE = 10; -// -// for (int k=0; k <= numChr && ! done; k++ ) { -// final double[] kMinus0 = logY.getkMinus0(); -// int jStart = Math.max(k - BAND_SIZE, 1); -// int jStop = Math.min(k + BAND_SIZE, numSamples); -// -// if ( k == 0 ) { // special case for k = 0 -// for ( int j=1; j <= numSamples; j++ ) { -// kMinus0[j] = kMinus0[j-1] + genotypeLikelihoods[j][GenotypeType.AA.ordinal()]; -// } -// } else { // k > 0 -// final double[] kMinus1 = logY.getkMinus1(); -// final double[] kMinus2 = logY.getkMinus2(); -// Arrays.fill(kMinus0,0); -// -// for ( int j = jStart; j <= jStop; j++ ) { -// final double[] gl = genotypeLikelihoods[j]; -// final double logDenominator = log10Cache[2*j] + log10Cache[2*j-1]; -// -// double aa = Double.NEGATIVE_INFINITY; -// double ab = Double.NEGATIVE_INFINITY; -// if (k < 2*j-1) -// aa = log10Cache[2*j-k] + log10Cache[2*j-k-1] + kMinus0[j-1] + gl[GenotypeType.AA.ordinal()]; -// -// if (k < 2*j) -// ab = log10Cache[2*k] + log10Cache[2*j-k]+ kMinus1[j-1] + gl[GenotypeType.AB.ordinal()]; -// -// double log10Max; -// if (k > 1) { -// final double bb = log10Cache[k] + log10Cache[k-1] + kMinus2[j-1] + gl[GenotypeType.BB.ordinal()]; -// 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; -// -// String offset = Utils.dupString(' ',k); -// System.out.printf("%s%3d %3d %.2f%n", offset, k, j, kMinus0[j]); -// } -// } -// -// // update the posteriors vector -// final double log10LofK = kMinus0[jStop]; -// log10AlleleFrequencyPosteriors[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; - } - public int linearExact(Map GLs, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors, int idxAA, int idxAB, int idxBB) { @@ -605,82 +449,6 @@ public Map assignGenotypes(VariantContext vc, return calls; } - // ------------------------------------------------------------------------------------- - // - // Gold standard, but O(N^2), implementation. - // - // TODO -- remove me for clarity in this code - // - // ------------------------------------------------------------------------------------- - public int gdaN2GoldStandard(Map GLs, - double[] log10AlleleFrequencyPriors, - double[] log10AlleleFrequencyPosteriors, int idxAA, int idxAB, int idxBB) { - int numSamples = GLs.size(); - int numChr = 2*numSamples; - - double[][] logYMatrix = new double[1+numSamples][1+numChr]; - - for (int i=0; i <=numSamples; i++) - for (int j=0; j <=numChr; j++) - logYMatrix[i][j] = Double.NEGATIVE_INFINITY; - - //YMatrix[0][0] = 1.0; - logYMatrix[0][0] = 0.0; - int j=0; - - for ( Map.Entry sample : GLs.entrySet() ) { - j++; - - if ( !sample.getValue().hasLikelihoods() ) - continue; - - //double[] genotypeLikelihoods = MathUtils.normalizeFromLog10(GLs.get(sample).getLikelihoods()); - double[] genotypeLikelihoods = sample.getValue().getLikelihoods().getAsVector(); - //double logDenominator = Math.log10(2.0*j*(2.0*j-1)); - double logDenominator = MathUtils.log10Cache[2*j] + MathUtils.log10Cache[2*j-1]; - - // special treatment for k=0: iteration reduces to: - //YMatrix[j][0] = YMatrix[j-1][0]*genotypeLikelihoods[GenotypeType.AA.ordinal()]; - logYMatrix[j][0] = logYMatrix[j-1][0] + genotypeLikelihoods[idxAA]; - - for (int k=1; k <= 2*j; k++ ) { - - //double num = (2.0*j-k)*(2.0*j-k-1)*YMatrix[j-1][k] * genotypeLikelihoods[GenotypeType.AA.ordinal()]; - double logNumerator[]; - logNumerator = new double[3]; - if (k < 2*j-1) - logNumerator[0] = MathUtils.log10Cache[2*j-k] + MathUtils.log10Cache[2*j-k-1] + logYMatrix[j-1][k] + - genotypeLikelihoods[idxAA]; - else - logNumerator[0] = Double.NEGATIVE_INFINITY; - - - if (k < 2*j) - logNumerator[1] = MathUtils.log10Cache[2*k] + MathUtils.log10Cache[2*j-k]+ logYMatrix[j-1][k-1] + - genotypeLikelihoods[idxAB]; - else - logNumerator[1] = Double.NEGATIVE_INFINITY; - - if (k > 1) - logNumerator[2] = MathUtils.log10Cache[k] + MathUtils.log10Cache[k-1] + logYMatrix[j-1][k-2] + - genotypeLikelihoods[idxBB]; - else - logNumerator[2] = Double.NEGATIVE_INFINITY; - - double logNum = MathUtils.softMax(logNumerator); - - //YMatrix[j][k] = num/den; - logYMatrix[j][k] = logNum - logDenominator; - } - - } - - for (int k=0; k <= numChr; k++) - log10AlleleFrequencyPosteriors[k] = logYMatrix[j][k] + log10AlleleFrequencyPriors[k]; - - return numChr; - } - private final static void printLikelihoods(int numChr, double[][] logYMatrix, double[] log10AlleleFrequencyPriors) { int j = logYMatrix.length - 1; System.out.printf("-----------------------------------%n"); @@ -689,5 +457,4 @@ private final static void printLikelihoods(int numChr, double[][] logYMatrix, do 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/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedArgumentCollection.java index 7b8045581d..3c8fd44514 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 @@ -168,10 +168,6 @@ public class UnifiedArgumentCollection { @Argument(fullName = "GSA_PRODUCTION_ONLY", shortName = "GSA_PRODUCTION_ONLY", doc = "don't ever use me", required = false) public boolean GSA_PRODUCTION_ONLY = false; - @Hidden - @Argument(fullName = "exactCalculation", shortName = "exactCalculation", doc = "expt", required = false) - public ExactAFCalculationModel.ExactCalculation EXACT_CALCULATION_TYPE = ExactAFCalculationModel.ExactCalculation.LINEAR_EXPERIMENTAL; - @Hidden @Argument(fullName = "ignoreSNPAlleles", shortName = "ignoreSNPAlleles", doc = "expt", required = false) public boolean IGNORE_SNP_ALLELES = false; @@ -191,7 +187,6 @@ public UnifiedArgumentCollection clone() { uac.GLmodel = GLmodel; uac.AFmodel = AFmodel; - uac.EXACT_CALCULATION_TYPE = EXACT_CALCULATION_TYPE; uac.heterozygosity = heterozygosity; uac.PCR_error = PCR_error; uac.GenotypingMode = GenotypingMode; From 0d53e184a7cd425e34d59af25d4f4c134b225c23 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 23 Sep 2011 12:16:09 -0400 Subject: [PATCH 090/363] Mapping quality name change. The mapping quality of the consensus reads is the average of the RMS of all reads that contributed to the consensus, weighted according to the size of the contribution of each read. From 147c16c8c05d067eb05eebac78b28593dd72f2d5 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 23 Sep 2011 12:35:38 -0400 Subject: [PATCH 091/363] Added separate threshold for indels to trigger variant regions Insertions and deletions now have a separate threshold to trigger a variant region. The default is 0.01, meaning that every site with at least 1 in a 100 insertion/deletion will be triggered as a variant region. From bf94845801728ba703ec4333b997babe8d1b7974 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 23 Sep 2011 12:36:07 -0400 Subject: [PATCH 092/363] Consensus shall never have indels. And if it tries to, blow up! From cc23b0b8a9b184eee6908a2fcbfdf63318be11b1 Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Fri, 23 Sep 2011 14:52:31 -0400 Subject: [PATCH 093/363] Fix for recent change modelling unmapped shards: don't invoke optimization to combine mapped and unmapped shards. --- .../sting/gatk/datasources/reads/LowMemoryIntervalSharder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ba63211213..bf5f33dc34 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java @@ -59,7 +59,7 @@ public boolean hasNext() { */ public FilePointer next() { FilePointer current = wrappedIterator.next(); - while(wrappedIterator.hasNext() && current.minus(wrappedIterator.peek()) == 0) + while(wrappedIterator.hasNext() && current.isRegionUnmapped == wrappedIterator.peek().isRegionUnmapped && current.minus(wrappedIterator.peek()) == 0) current = current.combine(parser,wrappedIterator.next()); return current; } From 9ea40f2e416727564849b749c0746fb688f6f1be Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 23 Sep 2011 16:37:08 -0400 Subject: [PATCH 094/363] Deletions/Insertions in hard clip and bug fixes * Deletions now count as hard clipped bases in order to recover the original alignment start of a clipped read. * Insertions do not count as hard clipped bases for the same reason. * This created a bug in the previous cigar cleaning function. Fixed. --- .../sting/utils/clipreads/ClippingOp.java | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 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 47ce8165c1..81d00d9d78 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -398,7 +398,9 @@ private CigarShift cleanHardClippedCigar(Cigar cigar) { for (int i = 1; i <= 2; i++) { int shift = 0; + int totalHardClip = 0; boolean readHasStarted = false; + boolean addedHardClips = false; while(!cigarStack.empty()) { CigarElement cigarElement = cigarStack.pop(); @@ -408,14 +410,33 @@ private CigarShift cleanHardClippedCigar(Cigar cigar) { cigarElement.getOperator() != CigarOperator.DELETION && cigarElement.getOperator() != CigarOperator.HARD_CLIP) readHasStarted = true; + + else if ( !readHasStarted && cigarElement.getOperator() == CigarOperator.HARD_CLIP) + totalHardClip += cigarElement.getLength(); + else if ( !readHasStarted && cigarElement.getOperator() == CigarOperator.INSERTION) shift += cigarElement.getLength(); - if (readHasStarted || cigarElement.getOperator() == CigarOperator.HARD_CLIP) { - if (i==1) + else if ( !readHasStarted && cigarElement.getOperator() == CigarOperator.DELETION) + totalHardClip += cigarElement.getLength(); + + if (readHasStarted) { + if (i==1) { + if (!addedHardClips) { + if (totalHardClip > 0) + inverseCigarStack.push(new CigarElement(totalHardClip, CigarOperator.HARD_CLIP)); + addedHardClips = true; + } inverseCigarStack.push(cigarElement); - else + } + else { + if (!addedHardClips) { + if (totalHardClip > 0) + cleanCigar.add(new CigarElement(totalHardClip, CigarOperator.HARD_CLIP)); + addedHardClips = true; + } cleanCigar.add(cigarElement); + } } } // first pass (i=1) is from end to start of the cigar elements @@ -434,7 +455,6 @@ else if ( !readHasStarted && cigarElement.getOperator() == CigarOperator.INSERTI private int calculateAlignmentStartShift(Cigar oldCigar, Cigar newCigar) { int newShift = 0; int oldShift = 0; - int deletionShift = 0; for (CigarElement cigarElement : newCigar.getCigarElements()) { if (cigarElement.getOperator() == CigarOperator.HARD_CLIP || cigarElement.getOperator() == CigarOperator.SOFT_CLIP) @@ -449,34 +469,19 @@ private int calculateAlignmentStartShift(Cigar oldCigar, Cigar newCigar) { else break; } - - int basesClipped = 0; - for (CigarElement cigarElement : oldCigar.getCigarElements()) { - if (basesClipped > newShift) // are we beyond the clipped region? - break; - - else if (cigarElement.getOperator() == CigarOperator.DELETION) // if this is a deletion, we have to adjust the starting shift - deletionShift += cigarElement.getLength(); - - else - basesClipped += cigarElement.getLength(); - } - - return newShift - oldShift + deletionShift; + return newShift - oldShift; } private int calculateHardClippingAlignmentShift(CigarElement cigarElement, int clippedLength) { - if (cigarElement.getOperator() == CigarOperator.INSERTION) { - int cigarElementLength = cigarElement.getLength(); - if (clippedLength >= cigarElementLength) - return -cigarElement.getLength(); - else - return -clippedLength; - } + // Insertions should be discounted from the total hard clip count + if (cigarElement.getOperator() == CigarOperator.INSERTION) + return -clippedLength; -// if (cigarElement.getOperator() == CigarOperator.DELETION) -// return cigarElement.getLength(); + // Deletions should be added to the total hard clip count + else if (cigarElement.getOperator() == CigarOperator.DELETION) + return cigarElement.getLength(); + // There is no shift if we are not clipping an indel return 0; } From b66841f17941fcd2639c081e8b3d4eeec94b9721 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 23 Sep 2011 17:29:34 -0400 Subject: [PATCH 095/363] Static cache for binomial probability -- Very low level performance optimization --- .../genotyper/UnifiedGenotyperEngine.java | 17 ++++++++++++++++- 1 file changed, 16 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 87dd37bf64..9e8cc07a6b 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 @@ -544,6 +544,21 @@ protected static void clearAFarray(double[] AFs) { AFs[i] = AlleleFrequencyCalculationModel.VALUE_NOT_CALCULATED; } + private final static double[] binomialProbabilityDepthCache = new double[10000]; + static { + for ( int i = 1; i < binomialProbabilityDepthCache.length; i++ ) { + binomialProbabilityDepthCache[i] = MathUtils.binomialProbability(0, i, 0.5); + } + } + + private final double getRefBinomialProb(final int depth) { + if ( depth < binomialProbabilityDepthCache.length ) + return binomialProbabilityDepthCache[depth]; + else + return MathUtils.binomialProbability(0, depth, 0.5); + } + + private VariantCallContext estimateReferenceConfidence(VariantContext vc, Map contexts, double theta, boolean ignoreCoveredSamples, double initialPofRef) { if ( contexts == null ) return null; @@ -567,7 +582,7 @@ else if (context.hasExtendedEventPileup()) depth = context.getExtendedEventPileup().size(); } - P_of_ref *= 1.0 - (theta / 2.0) * MathUtils.binomialProbability(0, depth, 0.5); + P_of_ref *= 1.0 - (theta / 2.0) * getRefBinomialProb(depth); } return new VariantCallContext(vc, QualityUtils.phredScaleErrorRate(1.0 - P_of_ref) >= UAC.STANDARD_CONFIDENCE_FOR_CALLING, false); From 1f5ed1fbbafff613a82afb238f1194d3ceaa4e9a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 23 Sep 2011 17:30:45 -0400 Subject: [PATCH 096/363] Fixing Indels in the consensus If the original file only had 1 read and it contained an indel, that would be passed to the consensus. Fixed. From 0e74cc3c749b7dada37a4f26b6943666e6b4c743 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Fri, 23 Sep 2011 21:58:20 -0400 Subject: [PATCH 097/363] a) Treat SNP genotype likelihoods just as indels, in the sense that they're always normalized as PL's so one of them will always be zero. This creates minor numerical differences in Qual and annotations due to numerical approximations in AF computation. b) Intermediate CombineVariants fixes, not ready yet --- .../genotyper/SNPGenotypeLikelihoodsCalculationModel.java | 6 ++++++ .../sting/gatk/walkers/variantutils/CombineVariants.java | 2 +- .../sting/utils/variantcontext/VariantContextUtils.java | 4 +++- 3 files changed, 10 insertions(+), 2 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 6905ce4a4f..3fed4542c7 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,6 +31,7 @@ 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.MathUtils; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.genotype.DiploidGenotype; @@ -122,6 +123,11 @@ public Allele getLikelihoods(RefMetaDataTracker tracker, aList.add(refAllele); aList.add(altAllele); double[] dlike = new double[]{likelihoods[refGenotype.ordinal()],likelihoods[hetGenotype.ordinal()],likelihoods[homGenotype.ordinal()]} ; + double maxElement = MathUtils.max(dlike[AlleleFrequencyCalculationModel.GenotypeType.AA.ordinal()], + dlike[AlleleFrequencyCalculationModel.GenotypeType.AB.ordinal()],dlike[AlleleFrequencyCalculationModel.GenotypeType.BB.ordinal()]); + for (int i=0; i < dlike.length; i++) + dlike[i] -= maxElement; + GLs.put(sample.getKey(), new MultiallelicGenotypeLikelihoods(sample.getKey(), aList, dlike, getFilteredDepth(pileup))); } 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 3e3b29a7f3..40b704570d 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 @@ -274,11 +274,11 @@ public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentCo else { mergedVCs = preMergedVCs; } - for ( VariantContext mergedVC : mergedVCs ) { // only operate at the start of events if ( mergedVC == null ) continue; + System.out.println(mergedVC.toString()); HashMap attributes = new HashMap(mergedVC.getAttributes()); // re-compute chromosome counts 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 ca9c71eba9..806bd2013c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -594,7 +594,9 @@ public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, // 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) for ( VariantContext vc : VCs ) { - if ( vc.alleles.size() != alleles.size() ) { + if (vc.alleles.size() == 1) + continue; + if ( vc.alleles.size() != alleles.size()) { genotypes = stripPLs(genotypes); break; } From c0bb0cb465c6d0761d524e4b139f182325b7814d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 24 Sep 2011 08:48:33 -0400 Subject: [PATCH 098/363] Make DiploidGenotype enum private to walkers.genotyper --- .../genotype => gatk/walkers/genotyper}/DiploidGenotype.java | 4 ++-- .../gatk/walkers/genotyper/DiploidIndelGenotypePriors.java | 1 - .../gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java | 1 - .../gatk/walkers/genotyper/DiploidSNPGenotypePriors.java | 1 - .../genotyper/SNPGenotypeLikelihoodsCalculationModel.java | 1 - .../walkers/genotyper}/DiploidGenotypeUnitTest.java | 3 ++- .../gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java | 1 - 7 files changed, 4 insertions(+), 8 deletions(-) rename public/java/src/org/broadinstitute/sting/{utils/genotype => gatk/walkers/genotyper}/DiploidGenotype.java (98%) rename public/java/test/org/broadinstitute/sting/{utils/genotype => gatk/walkers/genotyper}/DiploidGenotypeUnitTest.java (95%) diff --git a/public/java/src/org/broadinstitute/sting/utils/genotype/DiploidGenotype.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java similarity index 98% rename from public/java/src/org/broadinstitute/sting/utils/genotype/DiploidGenotype.java rename to public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java index 1c2cfe2e1d..b5987963f2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/genotype/DiploidGenotype.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java @@ -23,7 +23,7 @@ * THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.genotype; +package org.broadinstitute.sting.gatk.walkers.genotyper; import org.broadinstitute.sting.utils.BaseUtils; @@ -34,7 +34,7 @@ * Time: 6:46:09 PM * To change this template use File | Settings | File Templates. */ -public enum DiploidGenotype { +enum DiploidGenotype { AA ('A', 'A'), AC ('A', 'C'), AG ('A', 'G'), diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidIndelGenotypePriors.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidIndelGenotypePriors.java index 696a74de8a..d8c9110927 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidIndelGenotypePriors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidIndelGenotypePriors.java @@ -2,7 +2,6 @@ import org.broadinstitute.sting.gatk.walkers.indels.HaplotypeIndelErrorModel; import org.broadinstitute.sting.utils.MathUtils; -import org.broadinstitute.sting.utils.genotype.DiploidGenotype; /** * Created by IntelliJ IDEA. 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 ec180f0cdf..71eea2467a 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 @@ -30,7 +30,6 @@ import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.QualityUtils; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.genotype.DiploidGenotype; import org.broadinstitute.sting.utils.pileup.FragmentPileup; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypePriors.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypePriors.java index b9ed17d3ee..71854591f1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypePriors.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypePriors.java @@ -26,7 +26,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.broadinstitute.sting.utils.MathUtils; -import org.broadinstitute.sting.utils.genotype.DiploidGenotype; import java.util.Arrays; 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 6905ce4a4f..30acd68961 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,7 +33,6 @@ import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.exceptions.StingException; -import org.broadinstitute.sting.utils.genotype.DiploidGenotype; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.pileup.ReadBackedPileupImpl; diff --git a/public/java/test/org/broadinstitute/sting/utils/genotype/DiploidGenotypeUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotypeUnitTest.java similarity index 95% rename from public/java/test/org/broadinstitute/sting/utils/genotype/DiploidGenotypeUnitTest.java rename to public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotypeUnitTest.java index e4f8b12e30..4e72b37a40 100644 --- a/public/java/test/org/broadinstitute/sting/utils/genotype/DiploidGenotypeUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotypeUnitTest.java @@ -1,5 +1,6 @@ -package org.broadinstitute.sting.utils.genotype; +package org.broadinstitute.sting.gatk.walkers.genotyper; +import org.broadinstitute.sting.gatk.walkers.genotyper.DiploidGenotype; import org.testng.Assert; import org.broadinstitute.sting.BaseTest; diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java index 9882ce8690..425b969e23 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsUnitTest.java @@ -1,7 +1,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.testng.Assert; -import org.broadinstitute.sting.utils.genotype.DiploidGenotype; import org.broadinstitute.sting.BaseTest; import org.testng.annotations.Test; From f792353dcd59948aab4da10521b32adeb990487d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 24 Sep 2011 08:56:45 -0400 Subject: [PATCH 099/363] Framework for genotype unit test --- .../sting/utils/variantcontext/Genotype.java | 3 +- .../variantcontext/GenotypeUnitTest.java | 84 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java 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 fd8c70abe5..7ab3f81f08 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -268,7 +268,8 @@ public boolean sameGenotype(Genotype other, boolean ignorePhase) { * @param the value type * @return a sting, enclosed in {}, with comma seperated key value pairs in order of the keys */ - public static , V> String sortedString(Map c) { + 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); diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java new file mode 100644 index 0000000000..c4f1efd041 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeUnitTest.java @@ -0,0 +1,84 @@ +/* + * 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.broadinstitute.sting.BaseTest; +import org.testng.Assert; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; + + +public class GenotypeUnitTest extends BaseTest { + Allele A, Aref, T; + + @BeforeSuite + public void before() { + A = Allele.create("A"); + Aref = Allele.create("A", true); + T = Allele.create("T"); + } + +// 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, double[] log10Likelihoods) { +// public Genotype(String sampleName, List alleles, double negLog10PError, double[] log10Likelihoods) +// public Genotype(String sampleName, List alleles, double negLog10PError) +// public Genotype(String sampleName, List alleles) +// public List getAlleles() +// public List getAlleles(Allele allele) +// public Allele getAllele(int i) +// public boolean isPhased() +// public int getPloidy() +// public Type getType() +// public boolean isHom() +// public boolean isHomRef() +// public boolean isHomVar() +// public boolean isHet() +// public boolean isNoCall() +// public boolean isCalled() +// public boolean isAvailable() +// public boolean hasLikelihoods() +// public GenotypeLikelihoods getLikelihoods() +// public boolean sameGenotype(Genotype other) +// public boolean sameGenotype(Genotype other, boolean ignorePhase) +// public String getSampleName() +// public boolean hasNegLog10PError() +// public double getNegLog10PError() +// public double getPhredScaledQual() +// public boolean hasAttribute(String key) +// public Object getAttribute(String key) +// public Object getAttribute(String key, Object defaultValue) +// public String getAttributeAsString(String key, String defaultValue) +// public int getAttributeAsInt(String key, int defaultValue) +// public double getAttributeAsDouble(String key, double defaultValue) +// public boolean getAttributeAsBoolean(String key, boolean defaultValue) +} From 92acff46e5e99a8875eafa626c45a83b3751d01b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 24 Sep 2011 09:14:05 -0400 Subject: [PATCH 100/363] Moved Haplotype into Utils root --- .../sting/gatk/walkers/annotator/HaplotypeScore.java | 2 +- .../IndelGenotypeLikelihoodsCalculationModel.java | 7 +++---- .../gatk/walkers/indels/HaplotypeIndelErrorModel.java | 2 +- .../sting/gatk/walkers/indels/PairHMMIndelErrorModel.java | 2 +- .../sting/utils/{genotype => }/Haplotype.java | 3 +-- 5 files changed, 7 insertions(+), 9 deletions(-) rename public/java/src/org/broadinstitute/sting/utils/{genotype => }/Haplotype.java (98%) 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 df6da3b852..c142109faa 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 @@ -34,12 +34,12 @@ import org.broadinstitute.sting.gatk.walkers.annotator.interfaces.StandardAnnotation; import org.broadinstitute.sting.gatk.walkers.genotyper.IndelGenotypeLikelihoodsCalculationModel; import org.broadinstitute.sting.utils.BaseUtils; +import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.MathUtils; 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.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.genotype.Haplotype; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.sam.AlignmentUtils; 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 ec5eefd606..11cde5ffe9 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,10 +34,9 @@ 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.MathUtils; +import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.exceptions.StingException; -import org.broadinstitute.sting.utils.genotype.Haplotype; import org.broadinstitute.sting.utils.pileup.ExtendedEventPileupElement; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedExtendedEventPileup; @@ -396,8 +395,8 @@ public Allele getLikelihoods(RefMetaDataTracker tracker, System.out.format("hsize: %d eventLength: %d refSize: %d, locStart: %d numpr: %d\n",hsize,eventLength, (int)ref.getWindow().size(), loc.getStart(), numPrefBases); //System.out.println(eventLength); - haplotypeMap = Haplotype.makeHaplotypeListFromAlleles( alleleList, loc.getStart(), - ref, hsize, numPrefBases); + haplotypeMap = Haplotype.makeHaplotypeListFromAlleles(alleleList, loc.getStart(), + ref, hsize, numPrefBases); // For each sample, get genotype likelihoods based on pileup // compute prior likelihoods on haplotypes, and initialize haplotype likelihood matrix with them. 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 232e468f96..3b3f54b05f 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 @@ -26,9 +26,9 @@ package org.broadinstitute.sting.gatk.walkers.indels; import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.QualityUtils; -import org.broadinstitute.sting.utils.genotype.Haplotype; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.sam.ReadUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; 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 2d7969230c..4f9bccc420 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 @@ -29,8 +29,8 @@ import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; +import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.MathUtils; -import org.broadinstitute.sting.utils.genotype.Haplotype; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; diff --git a/public/java/src/org/broadinstitute/sting/utils/genotype/Haplotype.java b/public/java/src/org/broadinstitute/sting/utils/Haplotype.java similarity index 98% rename from public/java/src/org/broadinstitute/sting/utils/genotype/Haplotype.java rename to public/java/src/org/broadinstitute/sting/utils/Haplotype.java index a17e814615..ce2ca2c287 100755 --- a/public/java/src/org/broadinstitute/sting/utils/genotype/Haplotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/Haplotype.java @@ -22,10 +22,9 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.genotype; +package org.broadinstitute.sting.utils; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.variantcontext.Allele; From cd058dd10f337ab83919eaaba1f525e1f619b9f7 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Sat, 24 Sep 2011 13:40:11 -0400 Subject: [PATCH 101/363] a) Fixed md5 for legit change in UG output that now also no-calls genotypes w/0,0,0 in PL's in SNP case. b) First reimplementation of new vc merger of different types. Previous version did it in two steps, first merging all vc's per type and then trying to see if resulting vc's would be merged if alleles of one type were a subset of another, but this won't work when uniquifying genotypes since sample names would be messed up and GT sample names wouldn't match VC sample names. Now, it's actually simpler: when splitting vc's by type before merging, we check for alleles of one vc being a subset of alleles of vc of another type and if so we put them together in same list. --- .../walkers/variantutils/CombineVariants.java | 36 ++-------------- .../variantcontext/VariantContextUtils.java | 42 +++++++++++++++++-- .../UnifiedGenotyperIntegrationTest.java | 2 +- 3 files changed, 43 insertions(+), 37 deletions(-) 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 40b704570d..707f940fe5 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 @@ -234,47 +234,17 @@ public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentCo if (minimumN > 1 && (vcs.size() - numFilteredRecords < minimumN)) return 0; - List preMergedVCs = new ArrayList(); + List mergedVCs = new ArrayList(); Map> VCsByType = VariantContextUtils.separateVariantContextsByType(vcs); // iterate over the types so that it's deterministic for ( VariantContext.Type type : VariantContext.Type.values() ) { if ( VCsByType.containsKey(type) ) - preMergedVCs.add(VariantContextUtils.simpleMerge(getToolkit().getGenomeLocParser(), VCsByType.get(type), + mergedVCs.add(VariantContextUtils.simpleMerge(getToolkit().getGenomeLocParser(), VCsByType.get(type), priority, filteredRecordsMergeType, genotypeMergeOption, true, printComplexMerges, SET_KEY, filteredAreUncalled, MERGE_INFO_WITH_MAX_AC)); } - List mergedVCs = new ArrayList(); - // se have records merged but separated by type. If a particular record is for example a snp but all alleles are a subset of an existing mixed record, - // we will still merge those records. - if (preMergedVCs.size() > 1) { - for (VariantContext vc1 : preMergedVCs) { - VariantContext newvc = vc1; - boolean merged = false; - for (int k=0; k < mergedVCs.size(); k++) { - VariantContext vc2 = mergedVCs.get(k); - - if (VariantContextUtils.allelesAreSubset(vc1,vc2) || VariantContextUtils.allelesAreSubset(vc2,vc1)) { - // all alleles of vc1 are contained in vc2 but they are of different type (say, vc1 is snp, vc2 is complex): try to merget v1 into v2 - List vcpair = new ArrayList(); - vcpair.add(vc1); - vcpair.add(vc2); - newvc = VariantContextUtils.simpleMerge(getToolkit().getGenomeLocParser(), vcpair, - priority, filteredRecordsMergeType, genotypeMergeOption, true, printComplexMerges, - SET_KEY, filteredAreUncalled, MERGE_INFO_WITH_MAX_AC); - mergedVCs.set(k,newvc); - merged = true; - break; - } - } - if (!merged) - mergedVCs.add(vc1); - } - } - else { - mergedVCs = preMergedVCs; - } - for ( VariantContext mergedVC : mergedVCs ) { + for ( VariantContext mergedVC : mergedVCs ) { // only operate at the start of events if ( mergedVC == null ) continue; 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 0ca067cec5..f4f03e8f01 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -758,9 +758,45 @@ public static Map stripPLs(Map genotypes) { public static Map> separateVariantContextsByType(Collection VCs) { HashMap> mappedVCs = new HashMap>(); for ( VariantContext vc : VCs ) { - if ( !mappedVCs.containsKey(vc.getType()) ) - mappedVCs.put(vc.getType(), new ArrayList()); - mappedVCs.get(vc.getType()).add(vc); + + // 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; + } + } + } + if (addtoOwnList) { + if ( !mappedVCs.containsKey(vc.getType()) ) + mappedVCs.put(vc.getType(), new ArrayList()); + mappedVCs.get(vc.getType()).add(vc); + } } return mappedVCs; 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 41496bdf1b..488b3ccd92 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 @@ -42,7 +42,7 @@ public void testWithAllelesPassedIn() { 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("ec43daadfb15b00b41aeb0017a45df0b")); + Arrays.asList("6458f3b8fe4954e2ffc2af972aaab19e")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } From c31f4cb2f6cee438719a62a6216603a591ca0829 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Sat, 24 Sep 2011 14:33:32 -0400 Subject: [PATCH 102/363] Cleaning leading insertions With the current implementation, a read cannot start with a deletion or an insertion. Maybe this will change in the future, but for now, chop the leading insertion off. --- .../sting/utils/clipreads/ReadClipper.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 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 d0e7f83713..3b83617cfc 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -1,7 +1,6 @@ package org.broadinstitute.sting.utils.clipreads; import com.google.java.contract.Requires; -import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; import net.sf.samtools.SAMRecord; @@ -9,7 +8,6 @@ import org.broadinstitute.sting.utils.sam.ReadUtils; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; /** @@ -185,4 +183,21 @@ public SAMRecord clipRead(ClippingRepresentation algorithm) { } } } + + public SAMRecord hardClipLeadingInsertions() { + 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) + break; + + 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); + } } From 466ffa3665472c6c175e1fe09f7d6d3a9e58f0dd Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Sat, 24 Sep 2011 14:39:42 -0400 Subject: [PATCH 103/363] Forgot to hide my (shameless) debug techinque From 203517fbb7c69f087c67b529e2bff8874c584e2d Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Sat, 24 Sep 2011 19:08:00 -0400 Subject: [PATCH 104/363] a) Cleanups/bug fixes to previous commit to CombineVariants. b) Change md5 to reflect records that are now merged correctly. c) Change unit merge alleles test to reflect the fact that a null non-variant vc object is not valid and not supported because there's no way to codify such object in a vcf. The code correctly converts this to a non-variant single-base event with whatever the reference is at that location. --- .../walkers/variantutils/CombineVariants.java | 3 +-- .../utils/variantcontext/VariantContext.java | 2 +- .../variantcontext/VariantContextUtils.java | 1 + .../CombineVariantsIntegrationTest.java | 2 +- .../VariantContextUtilsUnitTest.java | 17 ++++++++++++++--- 5 files changed, 18 insertions(+), 7 deletions(-) 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 707f940fe5..ce03dfffe4 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 @@ -233,7 +233,7 @@ public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentCo if (minimumN > 1 && (vcs.size() - numFilteredRecords < minimumN)) return 0; - + List mergedVCs = new ArrayList(); Map> VCsByType = VariantContextUtils.separateVariantContextsByType(vcs); // iterate over the types so that it's deterministic @@ -248,7 +248,6 @@ public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentCo // only operate at the start of events if ( mergedVC == null ) continue; - System.out.println(mergedVC.toString()); HashMap attributes = new HashMap(mergedVC.getAttributes()); // re-compute chromosome counts 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 c1623d2cb3..412cbd90b1 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -1495,7 +1495,7 @@ else throw new IllegalArgumentException("Badly formed variant context at locatio // 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()); + return new VariantContext(inputVC.getSource(), 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 f4f03e8f01..ae648d547b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -789,6 +789,7 @@ 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; } } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java index 0205fe0ebc..3b1a973695 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java @@ -110,7 +110,7 @@ public void combinePLs(String file1, String file2, String md5) { " -priority NA19240_BGI,NA19240_ILLUMINA,NA19240_WUGSC,denovoInfo" + " -genotypeMergeOptions UNIQUIFY -L 1"), 1, - Arrays.asList("212d9d3df10bb29e2c7fb226da422dc0")); + Arrays.asList("b14f8cbb5d03a2e613b12da4da9efd9a")); executeTest("threeWayWithRefs", spec); } 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 15a5549b2c..498f7c12c8 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -38,7 +38,7 @@ import java.util.*; public class VariantContextUtilsUnitTest extends BaseTest { - Allele Aref, T, C, delRef, ATC, ATCATC; + Allele Aref, T, C, delRef, Cref, ATC, ATCATC; private GenomeLocParser genomeLocParser; @BeforeSuite @@ -54,6 +54,7 @@ public void setup() { // alleles Aref = Allele.create("A", true); + Cref = Allele.create("C", true); delRef = Allele.create("-", true); T = Allele.create("T"); C = Allele.create("C"); @@ -94,7 +95,7 @@ private VariantContext makeVC(String source, List alleles, Collection(), genotypes), - 1.0, filters, null, (byte)'C'); + 1.0, filters, null, Cref.getBases()[0]); } // -------------------------------------------------------------------------------- @@ -148,8 +149,10 @@ public Object[][] mergeAllelesData() { Arrays.asList(Aref, C), Arrays.asList(Aref, C, T)); // sorted by allele + // The following is actually a pathological case - there's no way on a vcf to represent a null allele that's non-variant. + // The code converts this (correctly) to a single-base non-variant vc with whatever base was there as a reference. new MergeAllelesTest(Arrays.asList(delRef), - Arrays.asList(delRef)); // todo -- FIXME me GdA + Arrays.asList(Cref)); new MergeAllelesTest(Arrays.asList(delRef), Arrays.asList(delRef, ATC), @@ -186,6 +189,14 @@ public void testMergeAlleles(MergeAllelesTest cfg) { inputs, priority, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, VariantContextUtils.GenotypeMergeType.PRIORITIZE, false, false, "set", false, false); + System.out.println("expected:"); + System.out.println(cfg.expected.toString()); + System.out.println("inputs"); + for (VariantContext vc:inputs) + System.out.println(vc.toString()); + System.out.println("merged:"); + System.out.println(merged.toString()); + Assert.assertEquals(merged.getAlleles(), cfg.expected); } From 4707ab4a7d00168bb0ae4145bf4ebde0c5263cff Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Sat, 24 Sep 2011 21:17:15 -0400 Subject: [PATCH 105/363] Added unit tests to test genotype merges with PL's --- .../VariantContextUtilsUnitTest.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) 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 498f7c12c8..08db7bcde2 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -66,6 +66,11 @@ private Genotype makeG(String sample, Allele a1, Allele a2) { return new Genotype(sample, Arrays.asList(a1, a2)); } + private Genotype makeG(String sample, Allele a1, Allele a2, double log10pError, double l1, double l2, double l3) { + return new Genotype(sample, Arrays.asList(a1, a2), log10pError, new double[]{l1,l2,l3}); + } + + private Genotype makeG(String sample, Allele a1, Allele a2, double log10pError) { return new Genotype(sample, Arrays.asList(a1, a2), log10pError); } @@ -189,13 +194,6 @@ public void testMergeAlleles(MergeAllelesTest cfg) { inputs, priority, VariantContextUtils.FilteredRecordMergeType.KEEP_IF_ANY_UNFILTERED, VariantContextUtils.GenotypeMergeType.PRIORITIZE, false, false, "set", false, false); - System.out.println("expected:"); - System.out.println(cfg.expected.toString()); - System.out.println("inputs"); - for (VariantContext vc:inputs) - System.out.println(vc.toString()); - System.out.println("merged:"); - System.out.println(merged.toString()); Assert.assertEquals(merged.getAlleles(), cfg.expected); } @@ -448,7 +446,17 @@ public Object[][] mergeGenotypesData() { 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))); - // todo -- GDA -- add tests for merging correct PLs + // merging genothpes with PLs + 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))); + + 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)), + // 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))); return MergeGenotypesTest.getTests(MergeGenotypesTest.class); } @@ -489,7 +497,7 @@ private void assertGenotypesAreMostlyEqual(Map actual, Map Date: Sat, 24 Sep 2011 18:39:24 -0400 Subject: [PATCH 106/363] Cleaning up old code from ReduceReads From 8c4eb5827f2d45a5e8d61558f90a3144d8347fe3 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Sat, 24 Sep 2011 18:52:46 -0400 Subject: [PATCH 107/363] cleaning up parameter/variable names making things more uniform. From 9afccd11b1b52bbf37c639e7944f7d1b12ba0a20 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Sun, 25 Sep 2011 21:18:56 -0400 Subject: [PATCH 108/363] Minor refactoring: add ability to MathUtils.normalizeFromLog10 to not go to linear domain but just substract max value from log values and return. Use this function in snp and indel GL computation. --- .../SNPGenotypeLikelihoodsCalculationModel.java | 7 ++----- .../walkers/indels/PairHMMIndelErrorModel.java | 10 ++-------- .../broadinstitute/sting/utils/MathUtils.java | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 14 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 742d4aadfc..9bdc754e95 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 @@ -122,13 +122,10 @@ public Allele getLikelihoods(RefMetaDataTracker tracker, aList.add(refAllele); aList.add(altAllele); double[] dlike = new double[]{likelihoods[refGenotype.ordinal()],likelihoods[hetGenotype.ordinal()],likelihoods[homGenotype.ordinal()]} ; - double maxElement = MathUtils.max(dlike[AlleleFrequencyCalculationModel.GenotypeType.AA.ordinal()], - dlike[AlleleFrequencyCalculationModel.GenotypeType.AB.ordinal()],dlike[AlleleFrequencyCalculationModel.GenotypeType.BB.ordinal()]); - for (int i=0; i < dlike.length; i++) - dlike[i] -= maxElement; + // normalize in log space so that max element is zero. GLs.put(sample.getKey(), new MultiallelicGenotypeLikelihoods(sample.getKey(), - aList, dlike, getFilteredDepth(pileup))); + aList, MathUtils.normalizeFromLog10(dlike, false, true), getFilteredDepth(pileup))); } return refAllele; 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 4f9bccc420..31e9819abe 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 @@ -1041,20 +1041,14 @@ public static double[] getHaplotypeLikelihoods(double[][] haplotypeLikehoodMatri double[] genotypeLikelihoods = new double[hSize*(hSize+1)/2]; int k=0; - double maxElement = Double.NEGATIVE_INFINITY; for (int j=0; j < hSize; j++) { for (int i=0; i <= j; i++){ genotypeLikelihoods[k++] = haplotypeLikehoodMatrix[i][j]; - if (haplotypeLikehoodMatrix[i][j] > maxElement) - maxElement = haplotypeLikehoodMatrix[i][j]; } } - // renormalize - for (int i=0; i < genotypeLikelihoods.length; i++) - genotypeLikelihoods[i] -= maxElement; - - return genotypeLikelihoods; + // renormalize so that max element is zero. + return MathUtils.normalizeFromLog10(genotypeLikelihoods, false, true); } /** diff --git a/public/java/src/org/broadinstitute/sting/utils/MathUtils.java b/public/java/src/org/broadinstitute/sting/utils/MathUtils.java index 0d85f9606e..17e74c4f1c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/MathUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/MathUtils.java @@ -444,11 +444,25 @@ public static double round(double num, int digits) { * @return a newly allocated array corresponding the normalized values in array, maybe log10 transformed */ public static double[] normalizeFromLog10(double[] array, boolean takeLog10OfOutput) { - double[] normalized = new double[array.length]; + return normalizeFromLog10(array, takeLog10OfOutput, false); + } + + public static double[] normalizeFromLog10(double[] array, boolean takeLog10OfOutput, boolean keepInLogSpace) { // for precision purposes, we need to add (or really subtract, since they're // all negative) the largest value; also, we need to convert to normal-space. double maxValue = Utils.findMaxEntry(array); + + // we may decide to just normalize in log space with converting to linear space + if (keepInLogSpace) { + for (int i = 0; i < array.length; i++) + array[i] -= maxValue; + return array; + } + + // default case: go to linear space + double[] normalized = new double[array.length]; + for (int i = 0; i < array.length; i++) normalized[i] = Math.pow(10, array[i] - maxValue); From b76dbc72f0d856866689e8b150b0acc39b4ece59 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 26 Sep 2011 08:13:44 -0400 Subject: [PATCH 109/363] Fixed interval navigation bug. If a read was hard clipped away from the current interval, all subsequent reads within that interval (not hardclipped) would be filtered out. Fixed. --- .../sting/utils/sam/ReadUtils.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 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 c328bbc5a1..49d79a72cb 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -157,7 +157,7 @@ public enum OverlapType { NOT_OVERLAPPING, IN_ADAPTOR} * |----------------| (interval) * <--------> (read) */ - public enum ReadAndIntervalOverlap {NO_OVERLAP_CONTIG, NO_OVERLAP_LEFT, NO_OVERLAP_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} /** * God, there's a huge information asymmetry in SAM format: @@ -640,27 +640,35 @@ public final static int getLastInsertionOffset(SAMRecord read) { */ public static ReadAndIntervalOverlap getReadAndIntervalOverlapType(SAMRecord read, GenomeLoc interval) { - int start = getRefCoordSoftUnclippedStart(read); - int stop = getRefCoordSoftUnclippedEnd(read); + int sStart = getRefCoordSoftUnclippedStart(read); + int sStop = getRefCoordSoftUnclippedEnd(read); + int uStart = read.getUnclippedStart(); + int uStop = read.getUnclippedEnd(); if ( !read.getReferenceName().equals(interval.getContig()) ) return ReadAndIntervalOverlap.NO_OVERLAP_CONTIG; - else if ( stop < interval.getStart() ) + else if ( uStop < interval.getStart() ) return ReadAndIntervalOverlap.NO_OVERLAP_LEFT; - else if ( start > interval.getStop() ) + else if ( uStart > interval.getStop() ) return ReadAndIntervalOverlap.NO_OVERLAP_RIGHT; - else if ( (start >= interval.getStart()) && - (stop <= interval.getStop()) ) + else if ( sStop < interval.getStart() ) + return ReadAndIntervalOverlap.NO_OVERLAP_HARDCLIPPED_LEFT; + + else if ( sStart > interval.getStop() ) + return ReadAndIntervalOverlap.NO_OVERLAP_HARDCLIPPED_RIGHT; + + else if ( (sStart >= interval.getStart()) && + (sStop <= interval.getStop()) ) return ReadAndIntervalOverlap.OVERLAP_CONTAINED; - else if ( (start < interval.getStart()) && - (stop > interval.getStop()) ) + else if ( (sStart < interval.getStart()) && + (sStop > interval.getStop()) ) return ReadAndIntervalOverlap.OVERLAP_LEFT_AND_RIGHT; - else if ( (start < interval.getStart()) ) + else if ( (sStart < interval.getStart()) ) return ReadAndIntervalOverlap.OVERLAP_LEFT; else From 763fcfc96b24e2b149e165c1173d9663ab9d36ad Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 26 Sep 2011 09:24:40 -0400 Subject: [PATCH 110/363] Adding support to splitting one read into multiple reads This framework will be necessary for adding features like a read that spans multiple intervals. From 4f09453470d3f86cd985eb31215623ae8d62a1d8 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 26 Sep 2011 12:58:31 -0400 Subject: [PATCH 111/363] Refactored reduced read utilities -- UnitTests for key functions on reduced reads -- PileupElement calls static functions in ReadUtils -- Simple routine that takes a reduced read and fills in its quals with its reduced qual --- .../sting/utils/pileup/PileupElement.java | 9 ++-- .../sting/utils/sam/ReadUtils.java | 35 ++++++++++++++- .../sting/utils/ReadUtilsUnitTest.java | 45 ++++++++++++++++++- 3 files changed, 81 insertions(+), 8 deletions(-) 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 12899e898f..053864791b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java @@ -81,20 +81,17 @@ protected byte getQual(final int offset) { // // -------------------------------------------------------------------------- - private Integer getReducedReadQualityTagValue() { - return getRead().getIntegerAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); - } - public boolean isReducedRead() { - return getReducedReadQualityTagValue() != null; + return ReadUtils.isReducedRead(getRead()); } public int getReducedCount() { + if ( ! isReducedRead() ) throw new IllegalArgumentException("Cannot get reduced count for non-reduced read " + getRead().getReadName()); return (int)getQual(); } public byte getReducedQual() { - return (byte)(int)getReducedReadQualityTagValue(); + return (byte)(int)ReadUtils.getReducedReadQualityTagValue(getRead()); } } \ 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 49d79a72cb..8beb1b21a8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -43,10 +43,43 @@ * @version 0.1 */ public class ReadUtils { + private ReadUtils() { } + + // ---------------------------------------------------------------------------------------------------- + // + // Reduced read utilities + // + // ---------------------------------------------------------------------------------------------------- + public static final String REDUCED_READ_QUALITY_TAG = "RQ"; - private ReadUtils() { } + public final static Integer getReducedReadQualityTagValue(final SAMRecord read) { + return read.getIntegerAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); + } + public final static boolean isReducedRead(final SAMRecord read) { + return getReducedReadQualityTagValue(read) != null; + } + + public final static SAMRecord reducedReadWithReducedQuals(final SAMRecord read) { + if ( ! isReducedRead(read) ) throw new IllegalArgumentException("read must be a reduced read"); + try { + SAMRecord newRead = (SAMRecord)read.clone(); + byte reducedQual = (byte)(int)getReducedReadQualityTagValue(read); + byte[] newQuals = new byte[read.getBaseQualities().length]; + Arrays.fill(newQuals, reducedQual); + newRead.setBaseQualities(newQuals); + return newRead; + } catch ( CloneNotSupportedException e ) { + throw new ReviewedStingException("SAMRecord no longer supports clone", e); + } + } + + // ---------------------------------------------------------------------------------------------------- + // + // General utilities + // + // ---------------------------------------------------------------------------------------------------- public static SAMFileHeader copySAMFileHeader(SAMFileHeader toCopy) { SAMFileHeader copy = new SAMFileHeader(); diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java index 7cb7fec986..e1fdadadc7 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java @@ -3,6 +3,7 @@ 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.ReadUtils; import org.testng.Assert; @@ -12,9 +13,10 @@ public class ReadUtilsUnitTest extends BaseTest { - SAMRecord read; + SAMRecord read, reducedRead; final static String BASES = "ACTG"; final static String QUALS = "!+5?"; + final private static int REDUCED_READ_QUAL = 20; @BeforeTest public void init() { @@ -23,6 +25,11 @@ public void init() { read.setReadUnmappedFlag(true); read.setReadBases(new String(BASES).getBytes()); read.setBaseQualityString(new String(QUALS)); + + reducedRead = ArtificialSAMUtils.createArtificialRead(header, "reducedRead", 0, 1, BASES.length()); + reducedRead.setReadBases(BASES.getBytes()); + reducedRead.setBaseQualityString(QUALS); + reducedRead.setAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG, REDUCED_READ_QUAL); } private void testReadBasesAndQuals(SAMRecord read, int expectedStart, int expectedStop) { @@ -38,4 +45,40 @@ private void testReadBasesAndQuals(SAMRecord read, int expectedStart, int expect @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(ReadUtils.isReducedRead(read), "isReducedRead is false for normal read"); + Assert.assertEquals(ReadUtils.getReducedReadQualityTagValue(read), null, "No reduced read tag in normal read"); + + Assert.assertTrue(ReadUtils.isReducedRead(reducedRead), "isReducedRead is true for reduced read"); + Assert.assertEquals((int) ReadUtils.getReducedReadQualityTagValue(reducedRead), REDUCED_READ_QUAL, "Reduced read tag is set to expected value"); + } + + @Test + public void testreducedReadWithReducedQualsWithReducedRead() { + SAMRecord replacedRead = ReadUtils.reducedReadWithReducedQuals(reducedRead); + Assert.assertEquals(replacedRead.getReadBases(), reducedRead.getReadBases()); + Assert.assertEquals(replacedRead.getBaseQualities().length, reducedRead.getBaseQualities().length); + for ( int i = 0; i < replacedRead.getBaseQualities().length; i++) + Assert.assertEquals(replacedRead.getBaseQualities()[i], REDUCED_READ_QUAL); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testreducedReadWithReducedQualsWithNormalRead() { + ReadUtils.reducedReadWithReducedQuals(read); + } + + @Test + public void testReducedReadPileupElement() { + PileupElement readp = new PileupElement(read,0); + PileupElement reducedreadp = new PileupElement(reducedRead,0); + + Assert.assertFalse(readp.isReducedRead()); + + Assert.assertTrue(reducedreadp.isReducedRead()); + Assert.assertEquals(reducedreadp.getReducedCount(), 0); + Assert.assertEquals(reducedreadp.getReducedQual(), REDUCED_READ_QUAL); + + } } From fa0efbc4ca9b304e6e67326912e65263a996c56a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 26 Sep 2011 13:28:56 -0400 Subject: [PATCH 112/363] Refactoring of PairHMM to support reduced reads --- .../indels/PairHMMIndelErrorModel.java | 291 +++++++++--------- 1 file changed, 149 insertions(+), 142 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 31e9819abe..6e4db93035 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 @@ -28,6 +28,7 @@ import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; +import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.MathUtils; @@ -244,31 +245,31 @@ else if( COVARIATE_PATTERN.matcher(line).matches() ) { // The line string is eit /** * For each covariate read in a value and parse it. Associate those values with the data itself (num observation and num mismatches) */ - /* - private void addCSVData(final File file, final String line) { - final String[] vals = line.split(","); - - // Check if the data line is malformed, for example if the read group string contains a comma then it won't be parsed correctly - if( vals.length != requestedCovariates.size() + 3 ) { // +3 because of nObservations, nMismatch, and Qempirical - throw new UserException.MalformedFile(file, "Malformed input recalibration file. Found data line with too many fields: " + line + - " --Perhaps the read group string contains a comma and isn't being parsed correctly."); - } + /* + private void addCSVData(final File file, final String line) { + final String[] vals = line.split(","); + + // Check if the data line is malformed, for example if the read group string contains a comma then it won't be parsed correctly + if( vals.length != requestedCovariates.size() + 3 ) { // +3 because of nObservations, nMismatch, and Qempirical + throw new UserException.MalformedFile(file, "Malformed input recalibration file. Found data line with too many fields: " + line + + " --Perhaps the read group string contains a comma and isn't being parsed correctly."); + } - final Object[] key = new Object[requestedCovariates.size()]; - Covariate cov; - int iii; - for( iii = 0; iii < requestedCovariates.size(); iii++ ) { - cov = requestedCovariates.get( iii ); - key[iii] = cov.getValue( vals[iii] ); - } + final Object[] key = new Object[requestedCovariates.size()]; + Covariate cov; + int iii; + for( iii = 0; iii < requestedCovariates.size(); iii++ ) { + cov = requestedCovariates.get( iii ); + key[iii] = cov.getValue( vals[iii] ); + } - // Create a new datum using the number of observations, number of mismatches, and reported quality score - final RecalDatum datum = new RecalDatum( Long.parseLong( vals[iii] ), Long.parseLong( vals[iii + 1] ), Double.parseDouble( vals[1] ), 0.0 ); - // Add that datum to all the collapsed tables which will be used in the sequential calculation - dataManager.addToAllTables( key, datum, PRESERVE_QSCORES_LESS_THAN ); - } + // Create a new datum using the number of observations, number of mismatches, and reported quality score + final RecalDatum datum = new RecalDatum( Long.parseLong( vals[iii] ), Long.parseLong( vals[iii + 1] ), Double.parseDouble( vals[1] ), 0.0 ); + // Add that datum to all the collapsed tables which will be used in the sequential calculation + dataManager.addToAllTables( key, datum, PRESERVE_QSCORES_LESS_THAN ); + } -*/ + */ public PairHMMIndelErrorModel(double indelGOP, double indelGCP, boolean deb, boolean doCDP, boolean dovit) { this(indelGOP, indelGCP, deb, doCDP); this.doViterbi = dovit; @@ -588,7 +589,7 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas } else { c = currentGOP[jm1]; - d = currentGCP[jm1]; + d = currentGCP[jm1]; } if (indI == X_METRIC_LENGTH-1) c = d = END_GAP_COST; @@ -707,12 +708,12 @@ private void fillGapProbabilities(int[] hrunProfile, } } public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pileup, LinkedHashMap haplotypeMap, - ReferenceContext ref, int eventLength, - HashMap> indelLikelihoodMap){ + ReferenceContext ref, int eventLength, + HashMap> indelLikelihoodMap){ int numHaplotypes = haplotypeMap.size(); - double[][] haplotypeLikehoodMatrix = new double[numHaplotypes][numHaplotypes]; - double readLikelihoods[][] = new double[pileup.getReads().size()][numHaplotypes]; + final double readLikelihoods[][] = new double[pileup.size()][numHaplotypes]; + final int readCounts[] = new int[pileup.size()]; int readIdx=0; LinkedHashMap gapOpenProbabilityMap = new LinkedHashMap(); @@ -751,6 +752,9 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi } } for (PileupElement p: pileup) { + // > 1 when the read is a consensus read representing multiple independent observations + final boolean isReduced = ReadUtils.isReducedRead(p.getRead()); + readCounts[readIdx] = isReduced ? p.getReducedCount() : 1; // check if we've already computed likelihoods for this pileup element (i.e. for this read at this location) if (indelLikelihoodMap.containsKey(p)) { @@ -762,61 +766,65 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi } else { //System.out.format("%d %s\n",p.getRead().getAlignmentStart(), p.getRead().getClass().getName()); - GATKSAMRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); + SAMRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); if (read == null) continue; + if ( isReduced ) { + read = ReadUtils.reducedReadWithReducedQuals(read); + } + if(ReadUtils.is454Read(read) && !getGapPenaltiesFromFile) { continue; } double[] recalQuals = null; - /* - if (getGapPenaltiesFromFile) { - RecalDataManager.parseSAMRecord( read, RAC ); + /* + if (getGapPenaltiesFromFile) { + RecalDataManager.parseSAMRecord( read, RAC ); - recalQuals = new double[read.getReadLength()]; + recalQuals = new double[read.getReadLength()]; - //compute all covariate values for this read - final Comparable[][] covariateValues_offset_x_covar = - RecalDataManager.computeCovariates((GATKSAMRecord) read, requestedCovariates); - // For each base in the read - for( int offset = 0; offset < read.getReadLength(); offset++ ) { + //compute all covariate values for this read + final Comparable[][] covariateValues_offset_x_covar = + RecalDataManager.computeCovariates((GATKSAMRecord) read, requestedCovariates); + // For each base in the read + for( int offset = 0; offset < read.getReadLength(); offset++ ) { - final Object[] fullCovariateKey = covariateValues_offset_x_covar[offset]; + final Object[] fullCovariateKey = covariateValues_offset_x_covar[offset]; - Byte qualityScore = (Byte) qualityScoreByFullCovariateKey.get(fullCovariateKey); - if(qualityScore == null) - { - qualityScore = performSequentialQualityCalculation( fullCovariateKey ); - qualityScoreByFullCovariateKey.put(qualityScore, fullCovariateKey); - } + Byte qualityScore = (Byte) qualityScoreByFullCovariateKey.get(fullCovariateKey); + if(qualityScore == null) + { + qualityScore = performSequentialQualityCalculation( fullCovariateKey ); + qualityScoreByFullCovariateKey.put(qualityScore, fullCovariateKey); + } - recalQuals[offset] = -((double)qualityScore)/10.0; - } + recalQuals[offset] = -((double)qualityScore)/10.0; + } - // for each read/haplotype combination, compute likelihoods, ie -10*log10(Pr(R | Hi)) - // = sum_j(-10*log10(Pr(R_j | Hi) since reads are assumed to be independent - if (DEBUG) { - System.out.format("\n\nStarting read:%s S:%d US:%d E:%d UE:%d C:%s\n",read.getReadName(), - read.getAlignmentStart(), - read.getUnclippedStart(), read.getAlignmentEnd(), read.getUnclippedEnd(), - read.getCigarString()); - - byte[] bases = read.getReadBases(); - for (int k = 0; k < recalQuals.length; k++) { - System.out.format("%c",bases[k]); - } - System.out.println(); + // for each read/haplotype combination, compute likelihoods, ie -10*log10(Pr(R | Hi)) + // = sum_j(-10*log10(Pr(R_j | Hi) since reads are assumed to be independent + if (DEBUG) { + System.out.format("\n\nStarting read:%s S:%d US:%d E:%d UE:%d C:%s\n",read.getReadName(), + read.getAlignmentStart(), + read.getUnclippedStart(), read.getAlignmentEnd(), read.getUnclippedEnd(), + read.getCigarString()); + + byte[] bases = read.getReadBases(); + for (int k = 0; k < recalQuals.length; k++) { + System.out.format("%c",bases[k]); + } + System.out.println(); - for (int k = 0; k < recalQuals.length; k++) { - System.out.format("%.0f ",recalQuals[k]); - } - System.out.println(); - } - } */ + for (int k = 0; k < recalQuals.length; k++) { + System.out.format("%.0f ",recalQuals[k]); + } + System.out.println(); + } + } */ // get bases of candidate haplotypes that overlap with reads final int trailingBases = 3; @@ -971,7 +979,7 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi System.out.println(new String(haplotypeBases)); } - Double readLikelihood = 0.0; + double readLikelihood = 0.0; if (useAffineGapModel) { double[] currentContextGOP = null; @@ -979,14 +987,14 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi if (doContextDependentPenalties) { - if (getGapPenaltiesFromFile) { - readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, recalCDP, null); + if (getGapPenaltiesFromFile) { + readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, recalCDP, null); - } else { - currentContextGOP = Arrays.copyOfRange(gapOpenProbabilityMap.get(a), (int)indStart, (int)indStop); - currentContextGCP = Arrays.copyOfRange(gapContProbabilityMap.get(a), (int)indStart, (int)indStop); - readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, currentContextGOP, currentContextGCP); - } + } else { + currentContextGOP = Arrays.copyOfRange(gapOpenProbabilityMap.get(a), (int)indStart, (int)indStop); + currentContextGCP = Arrays.copyOfRange(gapContProbabilityMap.get(a), (int)indStart, (int)indStop); + readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, currentContextGOP, currentContextGCP); + } } } @@ -1004,7 +1012,7 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi if (DEBUG) { System.out.println("\nLikelihood summary"); - for (readIdx=0; readIdx < pileup.getReads().size(); readIdx++) { + for (readIdx=0; readIdx < pileup.size(); readIdx++) { System.out.format("Read Index: %d ",readIdx); for (int i=0; i < readLikelihoods[readIdx].length; i++) System.out.format("L%d: %f ",i,readLikelihoods[readIdx][i]); @@ -1012,36 +1020,35 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi } } + + return getHaplotypeLikelihoods(numHaplotypes, readCounts, readLikelihoods); + } + + private final static double[] getHaplotypeLikelihoods(final int numHaplotypes, final int readCounts[], final double readLikelihoods[][]) { + final double[][] haplotypeLikehoodMatrix = new double[numHaplotypes][numHaplotypes]; + + // todo: MAD 09/26/11 -- I'm almost certain this calculation can be simplied to just a single loop without the intermediate NxN matrix for (int i=0; i < numHaplotypes; i++) { for (int j=i; j < numHaplotypes; j++){ // combine likelihoods of haplotypeLikelihoods[i], haplotypeLikelihoods[j] // L(Hi, Hj) = sum_reads ( Pr(R|Hi)/2 + Pr(R|Hj)/2) //readLikelihoods[k][j] has log10(Pr(R_k) | H[j] ) - for (readIdx=0; readIdx < pileup.getReads().size(); readIdx++) { - + for (int readIdx = 0; readIdx < readLikelihoods.length; readIdx++) { // Compute log10(10^x1/2 + 10^x2/2) = log10(10^x1+10^x2)-log10(2) // First term is approximated by Jacobian log with table lookup. if (Double.isInfinite(readLikelihoods[readIdx][i]) && Double.isInfinite(readLikelihoods[readIdx][j])) continue; - haplotypeLikehoodMatrix[i][j] += ( MathUtils.softMax(readLikelihoods[readIdx][i], - readLikelihoods[readIdx][j]) + LOG_ONE_HALF); - + final double li = readLikelihoods[readIdx][i]; + final double lj = readLikelihoods[readIdx][j]; + final int readCount = readCounts[readIdx]; + haplotypeLikehoodMatrix[i][j] += readCount * (MathUtils.softMax(li, lj) + LOG_ONE_HALF); } - - } } - return getHaplotypeLikelihoods(haplotypeLikehoodMatrix); - - } - - public static double[] getHaplotypeLikelihoods(double[][] haplotypeLikehoodMatrix) { - int hSize = haplotypeLikehoodMatrix.length; - double[] genotypeLikelihoods = new double[hSize*(hSize+1)/2]; - + final double[] genotypeLikelihoods = new double[numHaplotypes*(numHaplotypes+1)/2]; int k=0; - for (int j=0; j < hSize; j++) { + for (int j=0; j < numHaplotypes; j++) { for (int i=0; i <= j; i++){ genotypeLikelihoods[k++] = haplotypeLikehoodMatrix[i][j]; } @@ -1066,63 +1073,63 @@ public static double[] getHaplotypeLikelihoods(double[][] haplotypeLikehoodMatri * @param key The list of Comparables that were calculated from the covariates * @return A recalibrated quality score as a byte */ - /* - private byte performSequentialQualityCalculation( final Object... key ) { - - final byte qualFromRead = (byte)Integer.parseInt(key[1].toString()); - final Object[] readGroupCollapsedKey = new Object[1]; - final Object[] qualityScoreCollapsedKey = new Object[2]; - final Object[] covariateCollapsedKey = new Object[3]; - - // The global quality shift (over the read group only) - readGroupCollapsedKey[0] = key[0]; - final RecalDatum globalRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(0).get( readGroupCollapsedKey )); - double globalDeltaQ = 0.0; - if( globalRecalDatum != null ) { - final double globalDeltaQEmpirical = globalRecalDatum.getEmpiricalQuality(); - final double aggregrateQReported = globalRecalDatum.getEstimatedQReported(); - globalDeltaQ = globalDeltaQEmpirical - aggregrateQReported; - } + /* + private byte performSequentialQualityCalculation( final Object... key ) { + + final byte qualFromRead = (byte)Integer.parseInt(key[1].toString()); + final Object[] readGroupCollapsedKey = new Object[1]; + final Object[] qualityScoreCollapsedKey = new Object[2]; + final Object[] covariateCollapsedKey = new Object[3]; + + // The global quality shift (over the read group only) + readGroupCollapsedKey[0] = key[0]; + final RecalDatum globalRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(0).get( readGroupCollapsedKey )); + double globalDeltaQ = 0.0; + if( globalRecalDatum != null ) { + final double globalDeltaQEmpirical = globalRecalDatum.getEmpiricalQuality(); + final double aggregrateQReported = globalRecalDatum.getEstimatedQReported(); + globalDeltaQ = globalDeltaQEmpirical - aggregrateQReported; + } - // The shift in quality between reported and empirical - qualityScoreCollapsedKey[0] = key[0]; - qualityScoreCollapsedKey[1] = key[1]; - final RecalDatum qReportedRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(1).get( qualityScoreCollapsedKey )); - double deltaQReported = 0.0; - if( qReportedRecalDatum != null ) { - final double deltaQReportedEmpirical = qReportedRecalDatum.getEmpiricalQuality(); - deltaQReported = deltaQReportedEmpirical - qualFromRead - globalDeltaQ; - } + // The shift in quality between reported and empirical + qualityScoreCollapsedKey[0] = key[0]; + qualityScoreCollapsedKey[1] = key[1]; + final RecalDatum qReportedRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(1).get( qualityScoreCollapsedKey )); + double deltaQReported = 0.0; + if( qReportedRecalDatum != null ) { + final double deltaQReportedEmpirical = qReportedRecalDatum.getEmpiricalQuality(); + deltaQReported = deltaQReportedEmpirical - qualFromRead - globalDeltaQ; + } - // The shift in quality due to each covariate by itself in turn - double deltaQCovariates = 0.0; - double deltaQCovariateEmpirical; - covariateCollapsedKey[0] = key[0]; - covariateCollapsedKey[1] = key[1]; - for( int iii = 2; iii < key.length; iii++ ) { - covariateCollapsedKey[2] = key[iii]; // The given covariate - final RecalDatum covariateRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(iii).get( covariateCollapsedKey )); - if( covariateRecalDatum != null ) { - deltaQCovariateEmpirical = covariateRecalDatum.getEmpiricalQuality(); - deltaQCovariates += ( deltaQCovariateEmpirical - qualFromRead - (globalDeltaQ + deltaQReported) ); + // The shift in quality due to each covariate by itself in turn + double deltaQCovariates = 0.0; + double deltaQCovariateEmpirical; + covariateCollapsedKey[0] = key[0]; + covariateCollapsedKey[1] = key[1]; + for( int iii = 2; iii < key.length; iii++ ) { + covariateCollapsedKey[2] = key[iii]; // The given covariate + final RecalDatum covariateRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(iii).get( covariateCollapsedKey )); + if( covariateRecalDatum != null ) { + deltaQCovariateEmpirical = covariateRecalDatum.getEmpiricalQuality(); + deltaQCovariates += ( deltaQCovariateEmpirical - qualFromRead - (globalDeltaQ + deltaQReported) ); + } } - } - final double newQuality = qualFromRead + globalDeltaQ + deltaQReported + deltaQCovariates; - return QualityUtils.boundQual( (int)Math.round(newQuality), (byte)MAX_QUALITY_SCORE ); + final double newQuality = qualFromRead + globalDeltaQ + deltaQReported + deltaQCovariates; + return QualityUtils.boundQual( (int)Math.round(newQuality), (byte)MAX_QUALITY_SCORE ); - // Verbose printouts used to validate with old recalibrator - //if(key.contains(null)) { - // System.out.println( key + String.format(" => %d + %.2f + %.2f + %.2f + %.2f = %d", - // qualFromRead, globalDeltaQ, deltaQReported, deltaQPos, deltaQDinuc, newQualityByte)); - //} - //else { - // System.out.println( String.format("%s %s %s %s => %d + %.2f + %.2f + %.2f + %.2f = %d", - // key.get(0).toString(), key.get(3).toString(), key.get(2).toString(), key.get(1).toString(), qualFromRead, globalDeltaQ, deltaQReported, deltaQPos, deltaQDinuc, newQualityByte) ); - //} + // Verbose printouts used to validate with old recalibrator + //if(key.contains(null)) { + // System.out.println( key + String.format(" => %d + %.2f + %.2f + %.2f + %.2f = %d", + // qualFromRead, globalDeltaQ, deltaQReported, deltaQPos, deltaQDinuc, newQualityByte)); + //} + //else { + // System.out.println( String.format("%s %s %s %s => %d + %.2f + %.2f + %.2f + %.2f = %d", + // key.get(0).toString(), key.get(3).toString(), key.get(2).toString(), key.get(1).toString(), qualFromRead, globalDeltaQ, deltaQReported, deltaQPos, deltaQDinuc, newQualityByte) ); + //} - //return newQualityByte; + //return newQualityByte; - } -*/ + } + */ } From 648b959361dc15247a1cb9b487f9093f96d2c967 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Tue, 27 Sep 2011 00:50:19 -0400 Subject: [PATCH 113/363] Minor change to log an info message when a signal such as Ctrl-C is caught. --- .../broadinstitute/sting/queue/QCommandLine.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) mode change 100755 => 100644 public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala diff --git a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala old mode 100755 new mode 100644 index 297da8cc97..a3e83871ef --- a/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/QCommandLine.scala @@ -37,7 +37,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException /** * Entry point of Queue. Compiles and runs QScripts passed in to the command line. */ -object QCommandLine { +object QCommandLine extends Logging { /** * Main. * @param argv Arguments. @@ -45,22 +45,23 @@ object QCommandLine { def main(argv: Array[String]) { val qCommandLine = new QCommandLine - Runtime.getRuntime.addShutdownHook(new Thread { - /** Cleanup as the JVM shuts down. */ + val shutdownHook = new Thread { override def run() { + logger.info("Shutting down jobs. Please wait...") ProcessController.shutdown() qCommandLine.shutdown() } - }) + } + + Runtime.getRuntime.addShutdownHook(shutdownHook) try { CommandLineProgram.start(qCommandLine, argv); + Runtime.getRuntime.removeShutdownHook(shutdownHook) if (CommandLineProgram.result != 0) System.exit(CommandLineProgram.result); } catch { case e: Exception => CommandLineProgram.exitSystemWithError(e) - } finally { - } } } From e99ff3caae467d562d3b1e00be022d34c410a189 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 27 Sep 2011 10:08:40 -0400 Subject: [PATCH 114/363] Removed lots of old, and not to be used, HMM options -- resulted in massive code cleanup -- GdA will integrate his new banded algorithm here -- Removed: DO_CONTEXT_DEPENDENT_PENALTIES, GET_GAP_PENALTIES_FROM_DATA, INDEL_RECAL_FILE, dovit, GSA_PRODUCTION_ONLY --- ...elGenotypeLikelihoodsCalculationModel.java | 43 +- .../genotyper/UnifiedArgumentCollection.java | 31 +- .../indels/PairHMMIndelErrorModel.java | 706 ++---------------- 3 files changed, 66 insertions(+), 714 deletions(-) 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 11cde5ffe9..6d917325e3 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 @@ -71,9 +71,6 @@ protected synchronized HashMap> initi // gdebug removeme // todo -cleanup - private HaplotypeIndelErrorModel model; - private boolean useOldWrongHorribleHackedUpLikelihoodModel = false; -// private GenomeLoc lastSiteVisited; private ArrayList alleleList; @@ -84,26 +81,7 @@ protected synchronized HashMap> initi protected IndelGenotypeLikelihoodsCalculationModel(UnifiedArgumentCollection UAC, Logger logger) { super(UAC, logger); - if (UAC.GSA_PRODUCTION_ONLY == false) { - pairModel = new PairHMMIndelErrorModel(UAC.INDEL_GAP_OPEN_PENALTY,UAC.INDEL_GAP_CONTINUATION_PENALTY, - UAC.OUTPUT_DEBUG_INDEL_INFO, UAC.DO_CONTEXT_DEPENDENT_PENALTIES, UAC.dovit, UAC.GET_GAP_PENALTIES_FROM_DATA, UAC.INDEL_RECAL_FILE); - useOldWrongHorribleHackedUpLikelihoodModel = false; - } - else { - useOldWrongHorribleHackedUpLikelihoodModel = true; - double INSERTION_START_PROBABILITY = 1e-3; - - double INSERTION_END_PROBABILITY = 0.5; - - double ALPHA_DELETION_PROBABILITY = 1e-3; - - - model = new HaplotypeIndelErrorModel(3, INSERTION_START_PROBABILITY, - INSERTION_END_PROBABILITY,ALPHA_DELETION_PROBABILITY,UAC.INDEL_HAPLOTYPE_SIZE, false, UAC.OUTPUT_DEBUG_INDEL_INFO); - } - - pairModel = new PairHMMIndelErrorModel(UAC.INDEL_GAP_OPEN_PENALTY,UAC.INDEL_GAP_CONTINUATION_PENALTY, - UAC.OUTPUT_DEBUG_INDEL_INFO, UAC.DO_CONTEXT_DEPENDENT_PENALTIES, UAC.dovit, UAC.GET_GAP_PENALTIES_FROM_DATA, UAC.INDEL_RECAL_FILE); + pairModel = new PairHMMIndelErrorModel(UAC.INDEL_GAP_OPEN_PENALTY,UAC.INDEL_GAP_CONTINUATION_PENALTY,UAC.OUTPUT_DEBUG_INDEL_INFO); alleleList = new ArrayList(); getAlleleListFromVCF = UAC.GenotypingMode == GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES; minIndelCountForGenotyping = UAC.MIN_INDEL_COUNT_FOR_GENOTYPING; @@ -383,14 +361,11 @@ public Allele getLikelihoods(RefMetaDataTracker tracker, } } } - int eventLength = altAllele.getBaseString().length() - refAllele.getBaseString().length(); - int hsize = (int)ref.getWindow().size()-Math.abs(eventLength)-1; - int numPrefBases= ref.getLocus().getStart()-ref.getWindow().getStart()+1; - if (useOldWrongHorribleHackedUpLikelihoodModel) { - numPrefBases = 20; - hsize=80; - } + final int eventLength = altAllele.getBaseString().length() - refAllele.getBaseString().length(); + final int hsize = (int)ref.getWindow().size()-Math.abs(eventLength)-1; + final int numPrefBases= ref.getLocus().getStart()-ref.getWindow().getStart()+1; + if (DEBUG) System.out.format("hsize: %d eventLength: %d refSize: %d, locStart: %d numpr: %d\n",hsize,eventLength, (int)ref.getWindow().size(), loc.getStart(), numPrefBases); @@ -413,13 +388,7 @@ else if (context.hasBasePileup()) pileup = context.getBasePileup(); if (pileup != null ) { - double[] genotypeLikelihoods; - - if (useOldWrongHorribleHackedUpLikelihoodModel) - genotypeLikelihoods = model.computeReadHaplotypeLikelihoods( pileup, haplotypeMap); - else - genotypeLikelihoods = pairModel.computeReadHaplotypeLikelihoods( pileup, haplotypeMap, ref, eventLength, getIndelLikelihoodMap()); - + final double[] genotypeLikelihoods = pairModel.computeReadHaplotypeLikelihoods( pileup, haplotypeMap, ref, eventLength, getIndelLikelihoodMap()); GLs.put(sample.getKey(), new MultiallelicGenotypeLikelihoods(sample.getKey(), alleleList, 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 3c8fd44514..f5a107ee71 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 @@ -143,31 +143,21 @@ public class UnifiedArgumentCollection { @Hidden @Argument(fullName = "indelHaplotypeSize", shortName = "indelHSize", doc = "Indel haplotype size", required = false) public int INDEL_HAPLOTYPE_SIZE = 80; - @Hidden - @Argument(fullName = "doContextDependentGapPenalties", shortName = "doCDP", doc = "Vary gap penalties by context", required = false) - public boolean DO_CONTEXT_DEPENDENT_PENALTIES = true; + //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 = "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 = "indelDebug", shortName = "indelDebug", doc = "Output indel debug info", required = false) public boolean OUTPUT_DEBUG_INDEL_INFO = false; - @Hidden - @Argument(fullName = "dovit", shortName = "dovit", doc = "Perform full Viterbi calculation when evaluating the HMM", required = false) - public boolean dovit = false; - - @Hidden - @Argument(fullName = "GSA_PRODUCTION_ONLY", shortName = "GSA_PRODUCTION_ONLY", doc = "don't ever use me", required = false) - public boolean GSA_PRODUCTION_ONLY = false; - @Hidden @Argument(fullName = "ignoreSNPAlleles", shortName = "ignoreSNPAlleles", doc = "expt", required = false) public boolean IGNORE_SNP_ALLELES = false; @@ -204,15 +194,10 @@ public UnifiedArgumentCollection clone() { uac.INDEL_GAP_CONTINUATION_PENALTY = INDEL_GAP_CONTINUATION_PENALTY; uac.OUTPUT_DEBUG_INDEL_INFO = OUTPUT_DEBUG_INDEL_INFO; uac.INDEL_HAPLOTYPE_SIZE = INDEL_HAPLOTYPE_SIZE; - uac.DO_CONTEXT_DEPENDENT_PENALTIES = DO_CONTEXT_DEPENDENT_PENALTIES; uac.alleles = alleles; - uac.GET_GAP_PENALTIES_FROM_DATA = GET_GAP_PENALTIES_FROM_DATA; - uac.INDEL_RECAL_FILE = INDEL_RECAL_FILE; // todo- arguments to remove uac.COVERAGE_AT_WHICH_TO_ABORT = COVERAGE_AT_WHICH_TO_ABORT; - uac.dovit = dovit; - uac.GSA_PRODUCTION_ONLY = GSA_PRODUCTION_ONLY; uac.IGNORE_SNP_ALLELES = IGNORE_SNP_ALLELES; return uac; 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 6e4db93035..68cbd4fb7a 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 @@ -51,36 +51,8 @@ public class PairHMMIndelErrorModel { - - public static final int BASE_QUAL_THRESHOLD = 20; - - private static final int MATCH_OFFSET = 0; - private static final int X_OFFSET = 1; - private static final int Y_OFFSET = 2; - - private static final int DIAG = 0; - private static final int UP = 1; - private static final int LEFT = 2; - - private static final int DIAG_GOTO_M = 0; - private static final int DIAG_GOTO_X = 1; - private static final int DIAG_GOTO_Y = 2; - - private static final int UP_GOTO_M = 4; - private static final int UP_GOTO_X = 5; - private static final int UP_GOTO_Y = 6; - - private static final int LEFT_GOTO_M = 8; - private static final int LEFT_GOTO_X = 9; - private static final int LEFT_GOTO_Y = 10; - - private static final int[] ACTIONS_M = {DIAG_GOTO_M, DIAG_GOTO_X, DIAG_GOTO_Y}; - private static final int[] ACTIONS_X = {UP_GOTO_M, UP_GOTO_X, UP_GOTO_Y}; - private static final int[] ACTIONS_Y = {LEFT_GOTO_M, LEFT_GOTO_X, LEFT_GOTO_Y}; - - private final double logGapOpenProbability; private final double logGapContinuationProbability; @@ -101,36 +73,13 @@ public class PairHMMIndelErrorModel { private static final double MIN_GAP_CONT_PENALTY = 10.0; private static final double GAP_PENALTY_HRUN_STEP = 1.0; // each increase in hrun decreases gap penalty by this. - - private boolean doViterbi = false; - - private final boolean useAffineGapModel = true; - private boolean doContextDependentPenalties = false; - private final double[] GAP_OPEN_PROB_TABLE; private final double[] GAP_CONT_PROB_TABLE; - private boolean getGapPenaltiesFromFile = false; - - private int SMOOTHING = 1; - private int MAX_QUALITY_SCORE = 50; - private int PRESERVE_QSCORES_LESS_THAN = 5; - ///////////////////////////// // Private Member Variables ///////////////////////////// -//copy+ -/* private RecalDataManager dataManager; // Holds the data HashMap, mostly used by TableRecalibrationWalker to create collapsed data hashmaps - private final ArrayList requestedCovariates = new ArrayList(); // List of covariates to be used in this calculation - private static final Pattern COMMENT_PATTERN = Pattern.compile("^#.*"); - private static final Pattern OLD_RECALIBRATOR_HEADER = Pattern.compile("^rg,.*"); - private static final Pattern COVARIATE_PATTERN = Pattern.compile("^ReadGroup,QualityScore,.*"); - protected static final String EOF_MARKER = "EOF"; - private long numReadsWithMalformedColorSpace = 0; - private RecalibrationArgumentCollection RAC = new RecalibrationArgumentCollection(); - private NestedHashMap qualityScoreByFullCovariateKey = new NestedHashMap(); // Caches the result of performSequentialQualityCalculation(..) for all sets of covariate values. - */ -//copy- + static { LOG_ONE_HALF= -Math.log10(2.0); END_GAP_COST = LOG_ONE_HALF; @@ -146,141 +95,9 @@ public class PairHMMIndelErrorModel { } } - public PairHMMIndelErrorModel(double indelGOP, double indelGCP, boolean deb, boolean doCDP, boolean dovit,boolean gpf, File RECAL_FILE) { - - this(indelGOP, indelGCP, deb, doCDP, dovit); - this.getGapPenaltiesFromFile = gpf; - - // read data from recal file - // gdebug - start copy from TableRecalibrationWalker -/* if (gpf) { - boolean sawEOF = false; - boolean REQUIRE_EOF = false; - - int lineNumber = 0; - boolean foundAllCovariates = false; - // Get a list of all available covariates - final List> classes = new PluginManager(Covariate.class).getPlugins(); - - try { - for ( String line : new XReadLines(RECAL_FILE) ) { - lineNumber++; - if ( EOF_MARKER.equals(line) ) { - sawEOF = true; - } else if( COMMENT_PATTERN.matcher(line).matches() || OLD_RECALIBRATOR_HEADER.matcher(line).matches() ) { - ; // Skip over the comment lines, (which start with '#') - } - // Read in the covariates that were used from the input file - else if( COVARIATE_PATTERN.matcher(line).matches() ) { // The line string is either specifying a covariate or is giving csv data - if( foundAllCovariates ) { - throw new UserException.MalformedFile( RECAL_FILE, "Malformed input recalibration file. Found covariate names intermingled with data in file: " + RECAL_FILE ); - } else { // Found the covariate list in input file, loop through all of them and instantiate them - String[] vals = line.split(","); - for( int iii = 0; iii < vals.length - 3; iii++ ) { // There are n-3 covariates. The last three items are nObservations, nMismatch, and Qempirical - boolean foundClass = false; - for( Class covClass : classes ) { - if( (vals[iii] + "Covariate").equalsIgnoreCase( covClass.getSimpleName() ) ) { - foundClass = true; - try { - Covariate covariate = (Covariate)covClass.newInstance(); - requestedCovariates.add( covariate ); - } catch (Exception e) { - throw new DynamicClassResolutionException(covClass, e); - } - - } - } - - if( !foundClass ) { - throw new UserException.MalformedFile(RECAL_FILE, "Malformed input recalibration file. The requested covariate type (" + (vals[iii] + "Covariate") + ") isn't a valid covariate option." ); - } - } - } - - } else { // Found a line of data - if( !foundAllCovariates ) { - foundAllCovariates = true; - - // At this point all the covariates should have been found and initialized - if( requestedCovariates.size() < 2 ) { - throw new UserException.MalformedFile(RECAL_FILE, "Malformed input recalibration csv file. Covariate names can't be found in file: " + RECAL_FILE ); - } - - final boolean createCollapsedTables = true; - - // Initialize any covariate member variables using the shared argument collection - for( Covariate cov : requestedCovariates ) { - cov.initialize( RAC ); - } - // Initialize the data hashMaps - dataManager = new RecalDataManager( createCollapsedTables, requestedCovariates.size() ); - - } - addCSVData(RECAL_FILE, line); // Parse the line and add the data to the HashMap - } - } - - } catch ( FileNotFoundException e ) { - throw new UserException.CouldNotReadInputFile(RECAL_FILE, "Can not find input file", e); - } catch ( NumberFormatException e ) { - throw new UserException.MalformedFile(RECAL_FILE, "Error parsing recalibration data at line " + lineNumber + ". Perhaps your table was generated by an older version of CovariateCounterWalker."); - } - - if ( !sawEOF ) { - final String errorMessage = "No EOF marker was present in the recal covariates table; this could mean that the file is corrupted or was generated with an old version of the CountCovariates tool."; - if ( REQUIRE_EOF ) - throw new UserException.MalformedFile(RECAL_FILE, errorMessage); - } - - if( dataManager == null ) { - throw new UserException.MalformedFile(RECAL_FILE, "Can't initialize the data manager. Perhaps the recal csv file contains no data?"); - } - - // Create the tables of empirical quality scores that will be used in the sequential calculation - dataManager.generateEmpiricalQualities( SMOOTHING, MAX_QUALITY_SCORE ); - } - // debug end copy - */ - } - /** - * For each covariate read in a value and parse it. Associate those values with the data itself (num observation and num mismatches) - */ - /* - private void addCSVData(final File file, final String line) { - final String[] vals = line.split(","); - - // Check if the data line is malformed, for example if the read group string contains a comma then it won't be parsed correctly - if( vals.length != requestedCovariates.size() + 3 ) { // +3 because of nObservations, nMismatch, and Qempirical - throw new UserException.MalformedFile(file, "Malformed input recalibration file. Found data line with too many fields: " + line + - " --Perhaps the read group string contains a comma and isn't being parsed correctly."); - } - - final Object[] key = new Object[requestedCovariates.size()]; - Covariate cov; - int iii; - for( iii = 0; iii < requestedCovariates.size(); iii++ ) { - cov = requestedCovariates.get( iii ); - key[iii] = cov.getValue( vals[iii] ); - } - - // Create a new datum using the number of observations, number of mismatches, and reported quality score - final RecalDatum datum = new RecalDatum( Long.parseLong( vals[iii] ), Long.parseLong( vals[iii + 1] ), Double.parseDouble( vals[1] ), 0.0 ); - // Add that datum to all the collapsed tables which will be used in the sequential calculation - dataManager.addToAllTables( key, datum, PRESERVE_QSCORES_LESS_THAN ); - } - - */ - public PairHMMIndelErrorModel(double indelGOP, double indelGCP, boolean deb, boolean doCDP, boolean dovit) { - this(indelGOP, indelGCP, deb, doCDP); - this.doViterbi = dovit; - } - - public PairHMMIndelErrorModel(double indelGOP, double indelGCP, boolean deb, boolean doCDP) { - - + public PairHMMIndelErrorModel(double indelGOP, double indelGCP, boolean deb) { this.logGapOpenProbability = -indelGOP/10.0; // QUAL to log prob this.logGapContinuationProbability = -indelGCP/10.0; // QUAL to log prob - this.doContextDependentPenalties = doCDP; this.DEBUG = deb; @@ -314,132 +131,6 @@ public PairHMMIndelErrorModel(double indelGOP, double indelGCP, boolean deb, boo } - private double computeReadLikelihoodGivenHaplotype(byte[] haplotypeBases, byte[] readBases, byte[] readQuals) { - final int X_METRIC_LENGTH = readBases.length+1; - final int Y_METRIC_LENGTH = haplotypeBases.length+1; - - // initialize path metric and traceback memories for likelihood computation - double[][] pathMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - int[][] bestMetricArray = new int[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - - pathMetricArray[0][0]= 0;//Double.NEGATIVE_INFINITY; - - for (int i=1; i < X_METRIC_LENGTH; i++) { - pathMetricArray[i][0] = 0; - bestMetricArray[i][0] = UP; - } - - for (int j=1; j < Y_METRIC_LENGTH; j++) { - pathMetricArray[0][j] = 0;//logGapOpenProbability + (j-1) * logGapContinuationProbability; - bestMetricArray[0][j] = LEFT; - } - - for (int indI=1; indI < X_METRIC_LENGTH; indI++) { - for (int indJ=1; indJ < Y_METRIC_LENGTH; indJ++) { - - byte x = readBases[indI-1]; - byte y = haplotypeBases[indJ-1]; - byte qual = readQuals[indI-1]; - - double bestMetric = 0.0; - int bestMetricIdx = 0; - - // compute metric for match/mismatch - // workaround for reads whose bases quality = 0, - if (qual < 1) - qual = 1; - - if (qual > MAX_CACHED_QUAL) - qual = MAX_CACHED_QUAL; - - double pBaseRead = (x == y)? baseMatchArray[(int)qual]:baseMismatchArray[(int)qual]; - double[] metrics = new double[3]; - - metrics[DIAG] = pathMetricArray[indI-1][indJ-1] + pBaseRead; - metrics[UP] = pathMetricArray[indI-1][indJ] + logGapOpenProbability;//(end?0.0:logGapOpenProbability); - metrics[LEFT] = pathMetricArray[indI][indJ-1] + logGapOpenProbability;//(end?0.0:logGapOpenProbability); - - if (doViterbi) { - bestMetricIdx = MathUtils.maxElementIndex(metrics); - bestMetric = metrics[bestMetricIdx]; - } - else - bestMetric = MathUtils.softMax(metrics); - - pathMetricArray[indI][indJ] = bestMetric; - bestMetricArray[indI][indJ] = bestMetricIdx; - - } - } - - - double bestMetric=0.0; - int bestMetricIdx=0,bestI=X_METRIC_LENGTH - 1, bestJ=Y_METRIC_LENGTH - 1; - - for (int i=0; i < X_METRIC_LENGTH; i ++ ) { - int j= Y_METRIC_LENGTH-1; - - if (pathMetricArray[i][j] > bestMetric) { - bestMetric = pathMetricArray[i][j]; - bestI = i; - bestJ = j; - } - } - for (int j=0; j < Y_METRIC_LENGTH; j++ ) { - int i= X_METRIC_LENGTH-1; - if (pathMetricArray[i][j] >= bestMetric) { - bestMetric = pathMetricArray[i][j]; - bestI = i; - bestJ = j; - } - } - - if (DEBUG && doViterbi) { - - String haplotypeString = new String (haplotypeBases); - String readString = new String(readBases); - - - int i = bestI; - int j = bestJ; - - - System.out.println("Simple NW"); - - while (i >0 || j >0) { - bestMetricIdx = bestMetricArray[i][j]; - System.out.print(bestMetricIdx); - if (bestMetricIdx == UP) { - // insert gap in Y - haplotypeString = haplotypeString.substring(0,j)+"-"+haplotypeString.substring(j); - i--; - } else if (bestMetricIdx == LEFT) { - readString = readString.substring(0,i)+"-"+readString.substring(i); - j--; - } - else { - i--; j--; - } - } - - - - - System.out.println("\nAlignment: "); - System.out.println("R:"+readString); - System.out.println("H:"+haplotypeString); - System.out.println(); - - - } - if (DEBUG) - System.out.format("Likelihood: %5.4f\n", bestMetric); - - return bestMetric; - - - } - static private void getContextHomopolymerLength(final byte[] refBytes, int[] hrunArray) { // compute forward hrun length, example: // AGGTGACCCCCCTGAGAG @@ -480,14 +171,10 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas final int Y_METRIC_LENGTH = haplotypeBases.length+1; // initialize path metric and traceback memories for likelihood computation - double[][] matchMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - double[][] XMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - double[][] YMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - int[][] bestActionArrayM = new int[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - int[][] bestActionArrayX = new int[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - int[][] bestActionArrayY = new int[X_METRIC_LENGTH][Y_METRIC_LENGTH]; - - double c,d; + final double[][] matchMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + final double[][] XMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + final double[][] YMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + matchMetricArray[0][0]= END_GAP_COST;//Double.NEGATIVE_INFINITY; for (int i=1; i < X_METRIC_LENGTH; i++) { @@ -495,8 +182,6 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas matchMetricArray[i][0] = Double.NEGATIVE_INFINITY; YMetricArray[i][0] = Double.NEGATIVE_INFINITY; XMetricArray[i][0] = END_GAP_COST*(i);//logGapOpenProbability + (i-1)*logGapContinuationProbability; - - bestActionArrayX[i][0] = bestActionArrayY[i][0] = bestActionArrayM[i][0] = UP_GOTO_X; } for (int j=1; j < Y_METRIC_LENGTH; j++) { @@ -504,188 +189,46 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas matchMetricArray[0][j] = Double.NEGATIVE_INFINITY; XMetricArray[0][j] = Double.NEGATIVE_INFINITY; YMetricArray[0][j] = END_GAP_COST*(j);//logGapOpenProbability + (j-1) * logGapContinuationProbability; - - bestActionArrayY[0][j] = bestActionArrayM[0][j] = bestActionArrayX[0][j] = LEFT_GOTO_Y; } for (int indI=1; indI < X_METRIC_LENGTH; indI++) { - int im1 = indI-1; + final int im1 = indI-1; for (int indJ=1; indJ < Y_METRIC_LENGTH; indJ++) { - int jm1 = indJ-1; - byte x = readBases[im1]; - byte y = haplotypeBases[jm1]; - byte qual = readQuals[im1]; - - double bestMetric = 0.0; - int bestMetricIdx = 0; - - // compute metric for match/mismatch - // workaround for reads whose bases quality = 0, - if (qual < 1) - qual = 1; - - if (qual > MAX_CACHED_QUAL) - qual = MAX_CACHED_QUAL; - - double pBaseRead = (x == y)? baseMatchArray[(int)qual]:baseMismatchArray[(int)qual]; - - - double[] metrics = new double[3]; - - - if (doViterbi) { - // update match array - metrics[MATCH_OFFSET] = matchMetricArray[im1][jm1] + pBaseRead; - metrics[X_OFFSET] = XMetricArray[im1][jm1] + pBaseRead; - metrics[Y_OFFSET] = YMetricArray[im1][jm1] + pBaseRead; - - bestMetricIdx = MathUtils.maxElementIndex(metrics); - bestMetric = metrics[bestMetricIdx]; - } - else - bestMetric = MathUtils.softMax(matchMetricArray[im1][jm1] + pBaseRead, XMetricArray[im1][jm1] + pBaseRead, - YMetricArray[im1][jm1] + pBaseRead); - + final int jm1 = indJ-1; + final byte x = readBases[im1]; + final byte y = haplotypeBases[jm1]; + final byte qual = readQuals[im1] < 1 ? 1 : (readQuals[im1] > MAX_CACHED_QUAL ? MAX_CACHED_QUAL : readQuals[im1]); + final double pBaseRead = (x == y)? baseMatchArray[(int)qual]:baseMismatchArray[(int)qual]; + + double bestMetric = MathUtils.softMax(matchMetricArray[im1][jm1] + pBaseRead, + XMetricArray[im1][jm1] + pBaseRead, + YMetricArray[im1][jm1] + pBaseRead); matchMetricArray[indI][indJ] = bestMetric; - bestActionArrayM[indI][indJ] = ACTIONS_M[bestMetricIdx]; // update X array // State X(i,j): X(1:i) aligned to a gap in Y(1:j). // When in last column of X, ie X(1:i) aligned to full Y, we don't want to penalize gaps - //c = (indJ==Y_METRIC_LENGTH-1? END_GAP_COST: currentGOP[jm1]); - //d = (indJ==Y_METRIC_LENGTH-1? END_GAP_COST: currentGCP[jm1]); - if (getGapPenaltiesFromFile) { - c = currentGOP[im1]; - d = logGapContinuationProbability; - - } else { - c = currentGOP[jm1]; - d = currentGCP[jm1]; - } - if (indJ == Y_METRIC_LENGTH-1) - c = d = END_GAP_COST; - - if (doViterbi) { - metrics[MATCH_OFFSET] = matchMetricArray[im1][indJ] + c; - metrics[X_OFFSET] = XMetricArray[im1][indJ] + d; - metrics[Y_OFFSET] = Double.NEGATIVE_INFINITY; //YMetricArray[indI-1][indJ] + logGapOpenProbability; - - bestMetricIdx = MathUtils.maxElementIndex(metrics); - bestMetric = metrics[bestMetricIdx]; - } - else - bestMetric = MathUtils.softMax(matchMetricArray[im1][indJ] + c, XMetricArray[im1][indJ] + d); - + final double c1 = indJ == Y_METRIC_LENGTH-1 ? END_GAP_COST : currentGOP[jm1]; + final double d1 = indJ == Y_METRIC_LENGTH-1 ? END_GAP_COST : currentGCP[jm1]; + bestMetric = MathUtils.softMax(matchMetricArray[im1][indJ] + c1, XMetricArray[im1][indJ] + d1); XMetricArray[indI][indJ] = bestMetric; - bestActionArrayX[indI][indJ] = ACTIONS_X[bestMetricIdx]; // update Y array //c = (indI==X_METRIC_LENGTH-1? END_GAP_COST: currentGOP[jm1]); //d = (indI==X_METRIC_LENGTH-1? END_GAP_COST: currentGCP[jm1]); - if (getGapPenaltiesFromFile) { - c = currentGOP[im1]; - d = logGapContinuationProbability; - } - else { - c = currentGOP[jm1]; - d = currentGCP[jm1]; - } - if (indI == X_METRIC_LENGTH-1) - c = d = END_GAP_COST; - - - - if (doViterbi) { - metrics[MATCH_OFFSET] = matchMetricArray[indI][jm1] + c; - metrics[X_OFFSET] = Double.NEGATIVE_INFINITY; //XMetricArray[indI][indJ-1] + logGapOpenProbability; - metrics[Y_OFFSET] = YMetricArray[indI][jm1] + d; - - bestMetricIdx = MathUtils.maxElementIndex(metrics); - bestMetric = metrics[bestMetricIdx]; - } - else - bestMetric = MathUtils.softMax(matchMetricArray[indI][jm1] + c, YMetricArray[indI][jm1] + d); - + final double c2 = indI == X_METRIC_LENGTH-1 ? END_GAP_COST : currentGOP[jm1]; + final double d2 = indI == X_METRIC_LENGTH-1 ? END_GAP_COST : currentGCP[jm1]; + bestMetric = MathUtils.softMax(matchMetricArray[indI][jm1] + c2, YMetricArray[indI][jm1] + d2); YMetricArray[indI][indJ] = bestMetric; - bestActionArrayY[indI][indJ] = ACTIONS_Y[bestMetricIdx]; - - - } } - double bestMetric; - double metrics[] = new double[3]; - int bestTable=0, bestI=X_METRIC_LENGTH - 1, bestJ=Y_METRIC_LENGTH - 1; - metrics[MATCH_OFFSET] = matchMetricArray[bestI][bestJ]; - metrics[X_OFFSET] = XMetricArray[bestI][bestJ]; - metrics[Y_OFFSET] = YMetricArray[bestI][bestJ]; - if (doViterbi) { - bestTable = MathUtils.maxElementIndex(metrics); - bestMetric = metrics[bestTable]; - } - else - bestMetric = MathUtils.softMax(metrics); - - // Do traceback (needed only for debugging!) - if (DEBUG && doViterbi) { - - int bestAction; - int i = bestI; - int j = bestJ; - - - System.out.println("Affine gap NW"); - - - String haplotypeString = new String (haplotypeBases); - String readString = new String(readBases); - - - while (i >0 || j >0) { - if (bestTable == X_OFFSET) { - // insert gap in Y - haplotypeString = haplotypeString.substring(0,j)+"-"+haplotypeString.substring(j); - bestAction = bestActionArrayX[i][j]; - } - else if (bestTable == Y_OFFSET) { - readString = readString.substring(0,i)+"-"+readString.substring(i); - bestAction = bestActionArrayY[i][j]; - - } - else { - bestAction = bestActionArrayM[i][j]; - } - System.out.print(bestAction); - - - // bestAction contains action to take at next step - // encoding of bestAction: upper 2 bits = direction, lower 2 bits = next table + final int bestI = X_METRIC_LENGTH - 1, bestJ = Y_METRIC_LENGTH - 1; + final double bestMetric = MathUtils.softMax(matchMetricArray[bestI][bestJ], + XMetricArray[bestI][bestJ], + YMetricArray[bestI][bestJ]); - // bestTable and nextDirection for next step - bestTable = bestAction & 0x3; - int nextDirection = bestAction >> 2; - if (nextDirection == UP) { - i--; - } else if (nextDirection == LEFT) { - j--; - } else { // if (nextDirection == DIAG) - i--; j--; - } - - } - - - - - System.out.println("\nAlignment: "); - System.out.println("R:"+readString); - System.out.println("H:"+haplotypeString); - System.out.println(); - - - } if (DEBUG) System.out.format("Likelihood: %5.4f\n", bestMetric); @@ -724,33 +267,31 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi System.out.println(new String(ref.getBases())); } - if (doContextDependentPenalties && !getGapPenaltiesFromFile) { - // will context dependent probabilities based on homopolymer run. Probabilities are filled based on total complete haplotypes. - - - for (Allele a: haplotypeMap.keySet()) { - Haplotype haplotype = haplotypeMap.get(a); - byte[] haplotypeBases = haplotype.getBasesAsBytes(); - double[] contextLogGapOpenProbabilities = new double[haplotypeBases.length]; - double[] contextLogGapContinuationProbabilities = new double[haplotypeBases.length]; - - // get homopolymer length profile for current haplotype - int[] hrunProfile = new int[haplotypeBases.length]; - getContextHomopolymerLength(haplotypeBases,hrunProfile); - if (DEBUG) { - System.out.println("Haplotype bases:"); - System.out.println(new String(haplotypeBases)); - for (int i=0; i < hrunProfile.length; i++) - System.out.format("%d",hrunProfile[i]); - System.out.println(); - } - fillGapProbabilities(hrunProfile, contextLogGapOpenProbabilities, contextLogGapContinuationProbabilities); + // will context dependent probabilities based on homopolymer run. Probabilities are filled based on total complete haplotypes. + // todo -- refactor into separate function + for (Allele a: haplotypeMap.keySet()) { + Haplotype haplotype = haplotypeMap.get(a); + byte[] haplotypeBases = haplotype.getBasesAsBytes(); + double[] contextLogGapOpenProbabilities = new double[haplotypeBases.length]; + double[] contextLogGapContinuationProbabilities = new double[haplotypeBases.length]; + + // get homopolymer length profile for current haplotype + int[] hrunProfile = new int[haplotypeBases.length]; + getContextHomopolymerLength(haplotypeBases,hrunProfile); + if (DEBUG) { + System.out.println("Haplotype bases:"); + System.out.println(new String(haplotypeBases)); + for (int i=0; i < hrunProfile.length; i++) + System.out.format("%d",hrunProfile[i]); + System.out.println(); + } + fillGapProbabilities(hrunProfile, contextLogGapOpenProbabilities, contextLogGapContinuationProbabilities); - gapOpenProbabilityMap.put(a,contextLogGapOpenProbabilities); - gapContProbabilityMap.put(a,contextLogGapContinuationProbabilities); + gapOpenProbabilityMap.put(a,contextLogGapOpenProbabilities); + gapContProbabilityMap.put(a,contextLogGapContinuationProbabilities); - } } + for (PileupElement p: pileup) { // > 1 when the read is a consensus read representing multiple independent observations final boolean isReduced = ReadUtils.isReducedRead(p.getRead()); @@ -774,57 +315,12 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi read = ReadUtils.reducedReadWithReducedQuals(read); } - if(ReadUtils.is454Read(read) && !getGapPenaltiesFromFile) { + if(ReadUtils.is454Read(read)) { continue; } double[] recalQuals = null; - /* - if (getGapPenaltiesFromFile) { - RecalDataManager.parseSAMRecord( read, RAC ); - - - recalQuals = new double[read.getReadLength()]; - - //compute all covariate values for this read - final Comparable[][] covariateValues_offset_x_covar = - RecalDataManager.computeCovariates((GATKSAMRecord) read, requestedCovariates); - // For each base in the read - for( int offset = 0; offset < read.getReadLength(); offset++ ) { - - final Object[] fullCovariateKey = covariateValues_offset_x_covar[offset]; - - Byte qualityScore = (Byte) qualityScoreByFullCovariateKey.get(fullCovariateKey); - if(qualityScore == null) - { - qualityScore = performSequentialQualityCalculation( fullCovariateKey ); - qualityScoreByFullCovariateKey.put(qualityScore, fullCovariateKey); - } - - recalQuals[offset] = -((double)qualityScore)/10.0; - } - - // for each read/haplotype combination, compute likelihoods, ie -10*log10(Pr(R | Hi)) - // = sum_j(-10*log10(Pr(R_j | Hi) since reads are assumed to be independent - if (DEBUG) { - System.out.format("\n\nStarting read:%s S:%d US:%d E:%d UE:%d C:%s\n",read.getReadName(), - read.getAlignmentStart(), - read.getUnclippedStart(), read.getAlignmentEnd(), read.getUnclippedEnd(), - read.getCigarString()); - - byte[] bases = read.getReadBases(); - for (int k = 0; k < recalQuals.length; k++) { - System.out.format("%c",bases[k]); - } - System.out.println(); - - for (int k = 0; k < recalQuals.length; k++) { - System.out.format("%.0f ",recalQuals[k]); - } - System.out.println(); - } - } */ // get bases of candidate haplotypes that overlap with reads final int trailingBases = 3; @@ -945,11 +441,6 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi unclippedReadBases.length-numEndClippedBases); double[] recalCDP = null; - if (getGapPenaltiesFromFile) { - recalCDP = Arrays.copyOfRange(recalQuals,numStartClippedBases, - unclippedReadBases.length-numEndClippedBases); - - } if (DEBUG) { System.out.println("Read bases:"); @@ -979,27 +470,9 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi System.out.println(new String(haplotypeBases)); } - double readLikelihood = 0.0; - if (useAffineGapModel) { - - double[] currentContextGOP = null; - double[] currentContextGCP = null; - - if (doContextDependentPenalties) { - - if (getGapPenaltiesFromFile) { - readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, recalCDP, null); - - } else { - currentContextGOP = Arrays.copyOfRange(gapOpenProbabilityMap.get(a), (int)indStart, (int)indStop); - currentContextGCP = Arrays.copyOfRange(gapContProbabilityMap.get(a), (int)indStart, (int)indStop); - readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, currentContextGOP, currentContextGCP); - } - } - - } - else - readLikelihood = computeReadLikelihoodGivenHaplotype(haplotypeBases, readBases, readQuals); + final double[] currentContextGOP = Arrays.copyOfRange(gapOpenProbabilityMap.get(a), (int)indStart, (int)indStop); + final double[] currentContextGCP = Arrays.copyOfRange(gapContProbabilityMap.get(a), (int)indStart, (int)indStop); + final double readLikelihood = computeReadLikelihoodGivenHaplotypeAffineGaps(haplotypeBases, readBases, readQuals, currentContextGOP, currentContextGCP); readEl.put(a,readLikelihood); readLikelihoods[readIdx][j++] = readLikelihood; @@ -1057,79 +530,4 @@ private final static double[] getHaplotypeLikelihoods(final int numHaplotypes, f // renormalize so that max element is zero. return MathUtils.normalizeFromLog10(genotypeLikelihoods, false, true); } - - /** - * Implements a serial recalibration of the reads using the combinational table. - * First, we perform a positional recalibration, and then a subsequent dinuc correction. - * - * Given the full recalibration table, we perform the following preprocessing steps: - * - * - calculate the global quality score shift across all data [DeltaQ] - * - calculate for each of cycle and dinuc the shift of the quality scores relative to the global shift - * -- i.e., DeltaQ(dinuc) = Sum(pos) Sum(Qual) Qempirical(pos, qual, dinuc) - Qreported(pos, qual, dinuc) / Npos * Nqual - * - The final shift equation is: - * - * Qrecal = Qreported + DeltaQ + DeltaQ(pos) + DeltaQ(dinuc) + DeltaQ( ... any other covariate ... ) - * @param key The list of Comparables that were calculated from the covariates - * @return A recalibrated quality score as a byte - */ - /* - private byte performSequentialQualityCalculation( final Object... key ) { - - final byte qualFromRead = (byte)Integer.parseInt(key[1].toString()); - final Object[] readGroupCollapsedKey = new Object[1]; - final Object[] qualityScoreCollapsedKey = new Object[2]; - final Object[] covariateCollapsedKey = new Object[3]; - - // The global quality shift (over the read group only) - readGroupCollapsedKey[0] = key[0]; - final RecalDatum globalRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(0).get( readGroupCollapsedKey )); - double globalDeltaQ = 0.0; - if( globalRecalDatum != null ) { - final double globalDeltaQEmpirical = globalRecalDatum.getEmpiricalQuality(); - final double aggregrateQReported = globalRecalDatum.getEstimatedQReported(); - globalDeltaQ = globalDeltaQEmpirical - aggregrateQReported; - } - - // The shift in quality between reported and empirical - qualityScoreCollapsedKey[0] = key[0]; - qualityScoreCollapsedKey[1] = key[1]; - final RecalDatum qReportedRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(1).get( qualityScoreCollapsedKey )); - double deltaQReported = 0.0; - if( qReportedRecalDatum != null ) { - final double deltaQReportedEmpirical = qReportedRecalDatum.getEmpiricalQuality(); - deltaQReported = deltaQReportedEmpirical - qualFromRead - globalDeltaQ; - } - - // The shift in quality due to each covariate by itself in turn - double deltaQCovariates = 0.0; - double deltaQCovariateEmpirical; - covariateCollapsedKey[0] = key[0]; - covariateCollapsedKey[1] = key[1]; - for( int iii = 2; iii < key.length; iii++ ) { - covariateCollapsedKey[2] = key[iii]; // The given covariate - final RecalDatum covariateRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(iii).get( covariateCollapsedKey )); - if( covariateRecalDatum != null ) { - deltaQCovariateEmpirical = covariateRecalDatum.getEmpiricalQuality(); - deltaQCovariates += ( deltaQCovariateEmpirical - qualFromRead - (globalDeltaQ + deltaQReported) ); - } - } - - final double newQuality = qualFromRead + globalDeltaQ + deltaQReported + deltaQCovariates; - return QualityUtils.boundQual( (int)Math.round(newQuality), (byte)MAX_QUALITY_SCORE ); - - // Verbose printouts used to validate with old recalibrator - //if(key.contains(null)) { - // System.out.println( key + String.format(" => %d + %.2f + %.2f + %.2f + %.2f = %d", - // qualFromRead, globalDeltaQ, deltaQReported, deltaQPos, deltaQDinuc, newQualityByte)); - //} - //else { - // System.out.println( String.format("%s %s %s %s => %d + %.2f + %.2f + %.2f + %.2f = %d", - // key.get(0).toString(), key.get(3).toString(), key.get(2).toString(), key.get(1).toString(), qualFromRead, globalDeltaQ, deltaQReported, deltaQPos, deltaQDinuc, newQualityByte) ); - //} - - //return newQualityByte; - - } - */ } From ceffefa6a6a4d470f27853017aea66678c60e685 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Tue, 27 Sep 2011 10:18:58 -0400 Subject: [PATCH 115/363] Intermediate version with banded pair HMM --- .../indels/PairHMMIndelErrorModel.java | 186 ++++++++++++------ 1 file changed, 122 insertions(+), 64 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 31e9819abe..706b3abf78 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 @@ -31,6 +31,7 @@ import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.utils.Haplotype; import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; @@ -38,6 +39,8 @@ import org.broadinstitute.sting.utils.variantcontext.Allele; import java.io.File; +import java.io.FileWriter; +import java.io.PrintStream; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; @@ -478,6 +481,8 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas final int X_METRIC_LENGTH = readBases.length+1; final int Y_METRIC_LENGTH = haplotypeBases.length+1; + final int BWIDTH = 10; + // initialize path metric and traceback memories for likelihood computation double[][] matchMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; double[][] XMetricArray = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; @@ -507,6 +512,96 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas bestActionArrayY[0][j] = bestActionArrayM[0][j] = bestActionArrayX[0][j] = LEFT_GOTO_Y; } + final int numDiags = X_METRIC_LENGTH + Y_METRIC_LENGTH -1; + final int elemsInDiag = Math.min(X_METRIC_LENGTH, Y_METRIC_LENGTH); + + int idxWithMaxElement = 0; + + for (int diag=0; diag < numDiags; diag++) { + + int indI = 0; + int indJ = diag; + if (diag >= Y_METRIC_LENGTH ) { + indI = diag-(Y_METRIC_LENGTH-1); + indJ = Y_METRIC_LENGTH-1; + } + + + double maxElementInDiag = Double.NEGATIVE_INFINITY; + + int idxLow = idxWithMaxElement - BWIDTH; + int idxHigh = idxWithMaxElement + BWIDTH; + + if (idxLow < 0) + idxLow = 0; + + if (idxHigh > elemsInDiag ) + idxHigh = elemsInDiag; + + + // for each diagonal, compute max element, and center band around it for next diagonal. + // + // start point is idxOfMax-BWIDTH, end pt is idxMax+BWIDTH + + for (int el = idxLow; el < idxHigh; el++) { + // first row and first column of all matrices had been pre-filled before + int im1 = indI -1; + int jm1 = indJ - 1; + if (indI > 0 && indJ > 0) { + // update current point + byte x = readBases[im1]; + byte y = haplotypeBases[jm1]; + byte qual = readQuals[im1]; + + double bestMetric = 0.0; + + // compute metric for match/mismatch + // workaround for reads whose bases quality = 0, + if (qual < 1) + qual = 1; + + if (qual > MAX_CACHED_QUAL) + qual = MAX_CACHED_QUAL; + + double pBaseRead = (x == y)? baseMatchArray[(int)qual]:baseMismatchArray[(int)qual]; + + matchMetricArray[indI][indJ] = MathUtils.softMax(matchMetricArray[im1][jm1] + pBaseRead, XMetricArray[im1][jm1] + pBaseRead, + YMetricArray[im1][jm1] + pBaseRead); + + c = currentGOP[jm1]; + d = currentGCP[jm1]; + if (indJ == Y_METRIC_LENGTH-1) + c = d = END_GAP_COST; + + + XMetricArray[indI][indJ] = MathUtils.softMax(matchMetricArray[im1][indJ] + c, XMetricArray[im1][indJ] + d); + + // update Y array + //c = (indI==X_METRIC_LENGTH-1? END_GAP_COST: currentGOP[jm1]); + //d = (indI==X_METRIC_LENGTH-1? END_GAP_COST: currentGCP[jm1]); + c = currentGOP[jm1]; + d = currentGCP[jm1]; + if (indI == X_METRIC_LENGTH-1) + c = d = END_GAP_COST; + + YMetricArray[indI][indJ] = MathUtils.softMax(matchMetricArray[indI][jm1] + c, YMetricArray[indI][jm1] + d); + bestMetric = MathUtils.softMax(matchMetricArray[indI][indJ], XMetricArray[indI][indJ], YMetricArray[indI][indJ]); + + if (bestMetric > maxElementInDiag) { + maxElementInDiag = bestMetric; + idxWithMaxElement = el ; + } + + } + indI++; + if (indI >=X_METRIC_LENGTH ) + break; + indJ--; + if (indJ < 0) + break; + } + } +/* for (int indI=1; indI < X_METRIC_LENGTH; indI++) { int im1 = indI-1; for (int indJ=1; indJ < Y_METRIC_LENGTH; indJ++) { @@ -526,6 +621,8 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas if (qual > MAX_CACHED_QUAL) qual = MAX_CACHED_QUAL; + //qual = 30; + double pBaseRead = (x == y)? baseMatchArray[(int)qual]:baseMismatchArray[(int)qual]; @@ -613,78 +710,39 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas } } - + */ double bestMetric; double metrics[] = new double[3]; - int bestTable=0, bestI=X_METRIC_LENGTH - 1, bestJ=Y_METRIC_LENGTH - 1; + final int bestI=X_METRIC_LENGTH - 1, bestJ=Y_METRIC_LENGTH - 1; metrics[MATCH_OFFSET] = matchMetricArray[bestI][bestJ]; metrics[X_OFFSET] = XMetricArray[bestI][bestJ]; metrics[Y_OFFSET] = YMetricArray[bestI][bestJ]; - if (doViterbi) { - bestTable = MathUtils.maxElementIndex(metrics); - bestMetric = metrics[bestTable]; - } - else - bestMetric = MathUtils.softMax(metrics); - - // Do traceback (needed only for debugging!) - if (DEBUG && doViterbi) { + bestMetric = MathUtils.softMax(metrics); - int bestAction; - int i = bestI; - int j = bestJ; - - - System.out.println("Affine gap NW"); - - - String haplotypeString = new String (haplotypeBases); - String readString = new String(readBases); - - - while (i >0 || j >0) { - if (bestTable == X_OFFSET) { - // insert gap in Y - haplotypeString = haplotypeString.substring(0,j)+"-"+haplotypeString.substring(j); - bestAction = bestActionArrayX[i][j]; - } - else if (bestTable == Y_OFFSET) { - readString = readString.substring(0,i)+"-"+readString.substring(i); - bestAction = bestActionArrayY[i][j]; - - } - else { - bestAction = bestActionArrayM[i][j]; - } - System.out.print(bestAction); - - - // bestAction contains action to take at next step - // encoding of bestAction: upper 2 bits = direction, lower 2 bits = next table - - // bestTable and nextDirection for next step - bestTable = bestAction & 0x3; - int nextDirection = bestAction >> 2; - if (nextDirection == UP) { - i--; - } else if (nextDirection == LEFT) { - j--; - } else { // if (nextDirection == DIAG) - i--; j--; + if (DEBUG) { + PrintStream outx, outy, outm; + double[][] sumMetrics = new double[X_METRIC_LENGTH][Y_METRIC_LENGTH]; + try { + outx = new PrintStream("../../UGOptim/datax.txt"); + outy = new PrintStream("../../UGOptim/datay.txt"); + outm = new PrintStream("../../UGOptim/datam.txt"); + + for (int indI=0; indI < X_METRIC_LENGTH; indI++) { + for (int indJ=0; indJ < Y_METRIC_LENGTH; indJ++) { + metrics[MATCH_OFFSET] = matchMetricArray[indI][indJ]; + metrics[X_OFFSET] = XMetricArray[indI][indJ]; + metrics[Y_OFFSET] = YMetricArray[indI][indJ]; + sumMetrics[indI][indJ] = MathUtils.softMax(metrics); + outx.format("%4.1f ", metrics[X_OFFSET]); + outy.format("%4.1f ", metrics[Y_OFFSET]); + outm.format("%4.1f ", metrics[MATCH_OFFSET]); + } + outx.println(); outm.println();outy.println(); } + outm.close(); outx.close(); outy.close(); + } catch (java.io.IOException e) { throw new UserException("bla");} + } - } - - - - - System.out.println("\nAlignment: "); - System.out.println("R:"+readString); - System.out.println("H:"+haplotypeString); - System.out.println(); - - - } if (DEBUG) System.out.format("Likelihood: %5.4f\n", bestMetric); From 26e71f6688781b4f4de4c9e13269b64f0409d13d Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 27 Sep 2011 11:03:17 -0400 Subject: [PATCH 116/363] The Omni files have multiple records (with the same ALT) at a particular location, with one PASSing and the other(s) filtered. Chris, this is why using this file as both eval and comp leads to ref/no-call cells in the GenotypeConcordance table. However, this led to non-determinism in VE because the VCs were placed in a HashSet; we use a LinkedHashMap instead to bring back determinism. --- .../sting/gatk/walkers/varianteval/util/VariantEvalUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 92e7c6554f..6a057a456c 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 @@ -354,7 +354,7 @@ public HashMap, HashMap>> private void addMapping(HashMap> mappings, String sample, VariantContext vc) { if ( !mappings.containsKey(sample) ) - mappings.put(sample, new HashSet()); + mappings.put(sample, new LinkedHashSet()); mappings.get(sample).add(vc); } From db785eb50d9115a87f141c283eed6b78ae6f21ec Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Tue, 27 Sep 2011 12:39:25 -0400 Subject: [PATCH 117/363] Fix a bug where no fingerprint LODs across an entire project would cause the R script to blow up. Also, correct the sample names displayed at the bottom of the fingerprint plot; previously, it displayed the order of the sample in a sequence sorted by last sequencing date. From 3b6e43b7c4c64df97dfc0fa8d5cdb1db9aeda87b Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 27 Sep 2011 16:49:29 -0400 Subject: [PATCH 118/363] Use reads that span multiple intervals * RR will now compress reads that span across multiple intervals correctly and output them in the correct order. * Fixed bug in getReadCoordinateForReferenceCoordinate where if the requested reference coordinate fell inside a deletion in the read the read would be clipped up to one element past the deletion. --- .../sting/utils/clipreads/ClippingOp.java | 2 +- .../sting/utils/clipreads/ReadClipper.java | 17 +-- .../AlignmentStartWithNoTiesComparator.java | 46 ++++++++ .../sting/utils/sam/ReadUtils.java | 108 ++++++++++++++++-- 4 files changed, 150 insertions(+), 23 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/utils/sam/AlignmentStartWithNoTiesComparator.java 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 81d00d9d78..be7dc52f7b 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ClippingOp.java @@ -249,7 +249,7 @@ private Cigar softClip(final Cigar __cigar, final int __startClipEnd, final int @Requires({"start <= stop", "start == 0 || stop == read.getReadLength() - 1", "!read.getReadUnmappedFlag()"}) private SAMRecord hardClip (SAMRecord read, int start, int stop) { - if (start == 0 && stop == read.getReadLength() -1) + if (start == 0 && stop == read.getReadLength() - 1) return new SAMRecord(read.getHeader()); // If the read is unmapped there is no Cigar string and neither should we create a new cigar string 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 3b83617cfc..83db202385 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -4,6 +4,7 @@ import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.sam.ReadUtils; @@ -68,25 +69,15 @@ private int numDeletions(SAMRecord read) { } private SAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { - int start = (refStart < 0) ? 0 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, refStart); - int stop = (refStop < 0) ? read.getReadLength() - 1 : ReadUtils.getReadCoordinateForReferenceCoordinate(read, 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 (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 ) { -// stop = ReadUtils.getReadCoordinateForReferenceCoordinate(read, ReadUtils.getRefCoordSoftUnclippedEnd(read)); + if ( start > stop ) throw new ReviewedStingException("START > STOP -- this should never happen -- call Mauricio!"); - } - - //This tries to fix the bug where the deletion is counted a read base and as a result, the hardCLipper runs into - //an endless loop when hard clipping the cigar string because the read coordinates are not covered by the read -// stop -= numDeletions(read); -// if ( start > stop ) -// start -= numDeletions(read); - - //System.out.println("Clipping start/stop: " + start + "/" + stop); this.addOp(new ClippingOp(start, stop)); SAMRecord clippedRead = clipRead(ClippingRepresentation.HARDCLIP_BASES); this.ops = null; diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/AlignmentStartWithNoTiesComparator.java b/public/java/src/org/broadinstitute/sting/utils/sam/AlignmentStartWithNoTiesComparator.java new file mode 100644 index 0000000000..02512c8dc9 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/sam/AlignmentStartWithNoTiesComparator.java @@ -0,0 +1,46 @@ +package org.broadinstitute.sting.utils.sam; + +import com.google.java.contract.Ensures; +import com.google.java.contract.Requires; +import net.sf.samtools.SAMRecord; + +import java.util.Comparator; + +public class AlignmentStartWithNoTiesComparator implements Comparator { + @Requires("c1 >= 0 && c2 >= 0") + @Ensures("result == 0 || result == 1 || result == -1") + private int compareContigs(int c1, int c2) { + if (c1 == c2) + return 0; + else if (c1 > c2) + return 1; + return -1; + } + + @Requires("r1 != null && r2 != null") + @Ensures("result == 0 || result == 1 || result == -1") + public int compare(SAMRecord r1, SAMRecord r2) { + int result; + + if (r1 == r2) + result = 0; + + else if (r1.getReadUnmappedFlag()) + result = 1; + else if (r2.getReadUnmappedFlag()) + result = -1; + else { + final int cmpContig = compareContigs(r1.getReferenceIndex(), r2.getReferenceIndex()); + + if (cmpContig != 0) + result = cmpContig; + + else { + if (r1.getAlignmentStart() < r2.getAlignmentStart()) result = -1; + else result = 1; + } + } + + return result; + } +} \ 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 8beb1b21a8..5fa410922e 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -780,11 +780,56 @@ private static int getReadCoordinateForReferenceCoordinateBeforeAlignmentEnd(SAM } + public enum ClippingTail { + LEFT_TAIL, + RIGHT_TAIL + } + + /** + * Pre-processes the results of getReadCoordinateForReferenceCoordinate(SAMRecord, int) in case it falls in + * a deletion following the typical clipping needs. If clipping the left tail (beginning of the read) returns + * the base prior to the deletion. If clipping the right tail (end of the read) returns the base after the + * deletion. + * + * @param read + * @param refCoord + * @param tail + * @return the read coordinate corresponding to the requested reference coordinate for clipping. + */ @Requires({"refCoord >= read.getUnclippedStart()", "refCoord <= read.getUnclippedEnd()"}) @Ensures({"result >= 0", "result < read.getReadLength()"}) - public static int getReadCoordinateForReferenceCoordinate(SAMRecord read, int refCoord) { + public static int getReadCoordinateForReferenceCoordinate(SAMRecord read, int refCoord, ClippingTail tail) { + Pair result = getReadCoordinateForReferenceCoordinate(read, refCoord); + int readCoord = result.getFirst(); + + if (result.getSecond() && tail == ClippingTail.RIGHT_TAIL) + readCoord++; + + return readCoord; + } + + /** + * 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(SAMRecord, 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. + * + * @param read + * @param refCoord + * @return the read coordinate corresponding to the requested reference coordinate. (see warning!) + */ + @Requires({"refCoord >= read.getUnclippedStart()", "refCoord <= read.getUnclippedEnd()"}) + @Ensures({"result >= 0", "result < read.getReadLength()"}) + public static Pair getReadCoordinateForReferenceCoordinate(SAMRecord read, int refCoord) { int readBases = 0; int refBases = 0; + boolean fallsInsideDeletion = false; if (refCoord < read.getAlignmentStart()) { readBases = getReadCoordinateForReferenceCoordinateBeforeAlignmentStart(read, refCoord); @@ -806,26 +851,56 @@ else if (refCoord > read.getAlignmentEnd()) { int shift = 0; if (cigarElement.getOperator().consumesReferenceBases()) { - if (refBases + cigarElement.getLength() < goal) { + if (refBases + cigarElement.getLength() < goal) shift = cigarElement.getLength(); - } - else { + else shift = goal - refBases; - } + refBases += shift; } goalReached = refBases == goal; - if (cigarElement.getOperator().consumesReadBases()) { - readBases += goalReached ? shift : cigarElement.getLength(); - } + if (!goalReached && cigarElement.getOperator().consumesReadBases()) + readBases += cigarElement.getLength(); + + if (goalReached) { + // Is this base's reference position within this cigar element? Or did we use it all? + 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. + if (!endsWithinCigar && !cigarElementIterator.hasNext()) + throw new ReviewedStingException("Reference coordinate corresponds to a non-existent base in the read. This should never happen -- call Mauricio"); + + CigarElement nextCigarElement; + + // if we end inside the current cigar element, we just have to check if it is a deletion + if (endsWithinCigar) + fallsInsideDeletion = cigarElement.getOperator() == CigarOperator.DELETION; + + // if we end outside the current cigar element, we need to check if the next element is a deletion. + else { + nextCigarElement = cigarElementIterator.next(); + fallsInsideDeletion = nextCigarElement.getOperator() == CigarOperator.DELETION; + } + + // If we reached our goal outside a deletion, add the shift + 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) + else if (fallsInsideDeletion && !endsWithinCigar) + readBases += shift - 1; + } } if (!goalReached) throw new ReviewedStingException("Somehow the requested coordinate is not covered by the read. Too many deletions?"); } - return readBases; + return new Pair(readBases, fallsInsideDeletion); } public static SAMRecord unclipSoftClippedBases(SAMRecord rec) { @@ -871,4 +946,19 @@ public static SAMRecord unclipSoftClippedBases(SAMRecord rec) { return rec; } + + /** + * 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. + */ + + @Requires({"read1 != null", "read2 != null"}) + @Ensures("result == 0 || result == 1 || result == -1") + public static int compareSAMRecords(SAMRecord read1, SAMRecord read2) { + AlignmentStartComparator comp = new AlignmentStartComparator(); + return comp.compare(read1, read2); + } } From 269b9826b65e9e375924ec0fb82c2b258cb33f2a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 27 Sep 2011 20:26:36 -0400 Subject: [PATCH 119/363] Add longhand form to the error message to prevent users from posting borderline dumb posts to GS. --- .../src/org/broadinstitute/sting/utils/text/ListFileUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java b/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java index 9d4b23a8b2..c146bf4d4c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java @@ -80,7 +80,7 @@ else if(inputFileName.endsWith("stdin")) { unpackedReads.add(new SAMReaderID(inputFileName,inputFileNameTags)); } else { - throw new UserException.CommandLineException(String.format("The GATK reads argument (-I) supports only BAM files with the .bam extension and lists of BAM files " + + throw new UserException.CommandLineException(String.format("The GATK reads argument (-I, --input_file) supports only BAM files with the .bam extension and lists of BAM files " + "with the .list extension, but the file %s has neither extension. Please ensure that your BAM file or list " + "of BAM files is in the correct format, update the extension, and try again.",inputFileName)); } From 1d6fcb6eb1a9f98d13a39aff1e7f578096269568 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 27 Sep 2011 20:27:00 -0400 Subject: [PATCH 120/363] Revert "Add longhand form to the error message to prevent users from posting borderline dumb posts to GS." This reverts commit 75b2600527cfce05ae683cb394290ff2a80e8552. --- .../src/org/broadinstitute/sting/utils/text/ListFileUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java b/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java index c146bf4d4c..9d4b23a8b2 100644 --- a/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java @@ -80,7 +80,7 @@ else if(inputFileName.endsWith("stdin")) { unpackedReads.add(new SAMReaderID(inputFileName,inputFileNameTags)); } else { - throw new UserException.CommandLineException(String.format("The GATK reads argument (-I, --input_file) supports only BAM files with the .bam extension and lists of BAM files " + + throw new UserException.CommandLineException(String.format("The GATK reads argument (-I) supports only BAM files with the .bam extension and lists of BAM files " + "with the .list extension, but the file %s has neither extension. Please ensure that your BAM file or list " + "of BAM files is in the correct format, update the extension, and try again.",inputFileName)); } From 232a6df11ce3e656b59556365782548d45128829 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 27 Sep 2011 20:29:31 -0400 Subject: [PATCH 121/363] Add longhand form to the error message. --- .../src/org/broadinstitute/sting/utils/text/ListFileUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java b/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java index 9d4b23a8b2..c146bf4d4c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/text/ListFileUtils.java @@ -80,7 +80,7 @@ else if(inputFileName.endsWith("stdin")) { unpackedReads.add(new SAMReaderID(inputFileName,inputFileNameTags)); } else { - throw new UserException.CommandLineException(String.format("The GATK reads argument (-I) supports only BAM files with the .bam extension and lists of BAM files " + + throw new UserException.CommandLineException(String.format("The GATK reads argument (-I, --input_file) supports only BAM files with the .bam extension and lists of BAM files " + "with the .list extension, but the file %s has neither extension. Please ensure that your BAM file or list " + "of BAM files is in the correct format, update the extension, and try again.",inputFileName)); } From af0d16e74124724f5d5e15d452b0ff3c63a43e8d Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Wed, 28 Sep 2011 08:51:50 -0400 Subject: [PATCH 122/363] Adding context dependent gap penalty code into the haplotype likelihood model. Adding ContextCovariate to indel CountCovariates for use in generating k-mer quality tables. Bug fix: make sure to clear the assembly graphs between active regions. From 2e2463633f998d6b732dc679aafd8ce76133e141 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 28 Sep 2011 11:17:25 -0400 Subject: [PATCH 123/363] Queue script to find missing calls between full and reduced bams From 89544c209c0344fbf9aca9f283daa0fa428f9398 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 11:19:17 -0400 Subject: [PATCH 124/363] Fixing contracts changed return type to Pair, changing contracts accordingly. --- .../java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5fa410922e..14ebbaa6f6 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -825,7 +825,7 @@ public static int getReadCoordinateForReferenceCoordinate(SAMRecord read, int re * @return the read coordinate corresponding to the requested reference coordinate. (see warning!) */ @Requires({"refCoord >= read.getUnclippedStart()", "refCoord <= read.getUnclippedEnd()"}) - @Ensures({"result >= 0", "result < read.getReadLength()"}) + @Ensures({"result.getFirst() >= 0", "result.getFirst() < read.getReadLength()"}) public static Pair getReadCoordinateForReferenceCoordinate(SAMRecord read, int refCoord) { int readBases = 0; int refBases = 0; From fd287da18946330f36d725544af5ba3c0b6fb0d0 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Wed, 28 Sep 2011 11:22:01 -0400 Subject: [PATCH 125/363] Final tweaking of Smith-Waterman parameters based on large-scale parameter search with Queue From bb619a9a3c946cef37ccbcadf8f00af57db5dd8e Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 28 Sep 2011 13:13:03 -0400 Subject: [PATCH 126/363] Fixing docs --- .../broadinstitute/sting/gatk/walkers/annotator/SampleList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ff409484d1..ee08cfa3b8 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 @@ -42,7 +42,7 @@ import java.util.Map; /** - * List all of the samples in the info field + * List all of the polymorphic samples. */ public class SampleList extends InfoFieldAnnotation { From 1b45f217749f84c1af6833886124bc3c6f071ebd Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 28 Sep 2011 13:18:32 -0400 Subject: [PATCH 127/363] Removing this command-line tool. Purposely not doing this in stable so that users who may still use it have time to find other options. But the docs are no longer on the wiki. --- .../gatk/refdata/indexer/RMDIndexer.java | 130 ------------------ 1 file changed, 130 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java b/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java deleted file mode 100644 index 9e5a95d10e..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/refdata/indexer/RMDIndexer.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.broadinstitute.sting.gatk.refdata.indexer; - -import net.sf.picard.reference.IndexedFastaSequenceFile; -import org.apache.log4j.Logger; -import org.broad.tribble.FeatureCodec; -import org.broad.tribble.Tribble; -import org.broad.tribble.index.Index; -import org.broad.tribble.index.IndexFactory; -import org.broad.tribble.util.LittleEndianOutputStream; -import org.broadinstitute.sting.commandline.Argument; -import org.broadinstitute.sting.commandline.CommandLineProgram; -import org.broadinstitute.sting.commandline.Input; -import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; -import org.broadinstitute.sting.gatk.refdata.ReferenceDependentFeatureCodec; -import org.broadinstitute.sting.gatk.refdata.tracks.FeatureManager; -import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrackBuilder; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; - -import java.io.File; -import java.io.FileOutputStream; - -/** - * a utility class that can create an index, written to a target location. This is useful when you're unable to write to the directory - * in which an index is located, or if you'd like to pre-index files to save time. - */ -public class RMDIndexer extends CommandLineProgram { - @Argument(shortName="in", fullName="inputFile", doc="The reference meta data file to index", required = true) - File inputFileSource = null; - - @Argument(shortName="t", fullName="type", doc="The reference meta data file format (e.g. vcf, bed)", required = true) - String inputFileType = null; - - @Input(fullName = "referenceSequence", shortName = "R", doc = "The reference to use when indexing; this sequence will be set in the index", required = true) - public File referenceFile = null; - - @Input(shortName = "i", fullName = "indexFile", doc = "Where to write the index to (as a file), if not supplied we write to .idx", required = false) - public File indexFile = null; - - @Argument(shortName = "ba", fullName = "balanceApproach", doc="the index balancing approach to take", required=false) - IndexFactory.IndexBalanceApproach approach = IndexFactory.IndexBalanceApproach.FOR_SEEK_TIME; - - private static Logger logger = Logger.getLogger(RMDIndexer.class); - private IndexedFastaSequenceFile ref = null; - private GenomeLocParser genomeLocParser = null; - - @Override - protected int execute() throws Exception { - - - // check parameters - // --------------------------------------------------------------------------------- - - // check the input parameters - if (referenceFile != null && !referenceFile.canRead()) - throw new IllegalArgumentException("We can't read the reference file: " - + referenceFile + ", check that it exists, and that you have permissions to read it"); - - - // set the index file to the default name if they didn't specify a file - if (indexFile == null && inputFileSource != null) - indexFile = new File(inputFileSource.getAbsolutePath() + Tribble.STANDARD_INDEX_EXTENSION); - - // check that we can create the output file - if (indexFile == null || indexFile.exists()) - throw new IllegalArgumentException("We can't write to the index file location: " - + indexFile + ", the index exists"); - - logger.info(String.format("attempting to index file: %s", inputFileSource)); - logger.info(String.format("using reference: %s", ((referenceFile != null) ? referenceFile.getAbsolutePath() : "(not supplied)"))); - logger.info(String.format("using type: %s", inputFileType)); - logger.info(String.format("writing to location: %s", indexFile.getAbsolutePath())); - - // try to index the file - // --------------------------------------------------------------------------------- - - // setup the reference - ref = new CachingIndexedFastaSequenceFile(referenceFile); - genomeLocParser = new GenomeLocParser(ref); - - // get a track builder - RMDTrackBuilder builder = new RMDTrackBuilder(ref.getSequenceDictionary(),genomeLocParser, ValidationExclusion.TYPE.ALL); - - // find the types available to the track builders - FeatureManager.FeatureDescriptor descriptor = builder.getFeatureManager().getByName(inputFileType); - - // check that the type is valid - if (descriptor == null) - throw new IllegalArgumentException("The type specified " + inputFileType + " is not a valid type. Valid type list: " + builder.getFeatureManager().userFriendlyListOfAvailableFeatures()); - - // create the codec - FeatureCodec codec = builder.getFeatureManager().createCodec(descriptor, "foo", genomeLocParser); - - // check if it's a reference dependent feature codec - if (codec instanceof ReferenceDependentFeatureCodec) - ((ReferenceDependentFeatureCodec)codec).setGenomeLocParser(genomeLocParser); - - // get some timing info - long currentTime = System.currentTimeMillis(); - - Index index = IndexFactory.createIndex(inputFileSource, codec, approach); - - // add writing of the sequence dictionary, if supplied - builder.validateAndUpdateIndexSequenceDictionary(inputFileSource, index, ref.getSequenceDictionary()); - - // create the output stream, and write the index - LittleEndianOutputStream stream = new LittleEndianOutputStream(new FileOutputStream(indexFile)); - index.write(stream); - stream.close(); - - // report and exit - logger.info("Successfully wrote the index to location: " + indexFile + " in " + ((System.currentTimeMillis() - currentTime)/1000) + " seconds"); - return 0; // return successfully - } - - - /** - * the generic call execute main - * @param argv the arguments from the command line - */ - public static void main(String[] argv) { - try { - RMDIndexer instance = new RMDIndexer(); - start(instance, argv); - System.exit(CommandLineProgram.result); - } catch (Exception e) { - exitSystemWithError(e); - } - } -} From 5c9b659c02aef41527ee07cd9c2e472a23de977b Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 12:10:47 -0400 Subject: [PATCH 128/363] clipping both ends of the reads was modifying the original read This goes against the ReadClipper contract, and was affecting the second part of the read that spans over multiple intervals. Fixed. --- .../broadinstitute/sting/utils/clipreads/ReadClipper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 83db202385..275f476dcb 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -93,8 +93,9 @@ public SAMRecord hardClipByReadCoordinates(int start, int stop) { public SAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { if (left == right) return new SAMRecord(read.getHeader()); - this.read = hardClipByReferenceCoordinates(right, -1); - return hardClipByReferenceCoordinates(-1, left); + SAMRecord leftTailRead = hardClipByReferenceCoordinates(right, -1); + ReadClipper clipper = new ReadClipper(leftTailRead); + return clipper.hardClipByReferenceCoordinatesLeftTail(left); } public SAMRecord hardClipLowQualEnds(byte lowQual) { From 3c7b7f74ef235ed9933c8227b2c89cb68b3374b0 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 14:41:30 -0400 Subject: [PATCH 129/363] Optimized interval iteration Using a TreedSet to manipulate getToolkit.getIntervals() and being smart about which intervals to test makes interval clipping O(1) instead of O(n). --- .../sting/utils/GenomeLocComparator.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 public/java/src/org/broadinstitute/sting/utils/GenomeLocComparator.java diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLocComparator.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLocComparator.java new file mode 100644 index 0000000000..7aa9fdd65d --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLocComparator.java @@ -0,0 +1,56 @@ +package org.broadinstitute.sting.utils; + +import com.google.java.contract.Ensures; +import com.google.java.contract.Requires; + +import java.util.Comparator; + +/** + * + * @author Mauricio Carneiro + * @since 9/28/11 + */ +public class GenomeLocComparator implements Comparator { + /** + * compares genomeLoc's contigs + * + * @param gl1 the genome loc to compare contigs + * @param gl2 the genome loc to compare contigs + * @return 0 if equal, -1 if gl2.contig is greater, 1 if gl1.contig is greater + */ + @Requires("gl2 != null") + @Ensures("result == 0 || result == 1 || result == -1") + public final int compareContigs( GenomeLoc gl1, GenomeLoc gl2 ) { + if (gl1.contigIndex == gl2.contigIndex) + return 0; + else if (gl1.contigIndex > gl2.contigIndex) + return 1; + return -1; + } + + @Requires("gl2 != null") + @Ensures("result == 0 || result == 1 || result == -1") + public int compare ( GenomeLoc gl1, GenomeLoc gl2 ) { + int result = 0; + + if ( gl1 == gl2 ) { + result = 0; + } + else if(GenomeLoc.isUnmapped(gl1)) + result = 1; + else if(GenomeLoc.isUnmapped(gl2)) + result = -1; + else { + final int cmpContig = compareContigs(gl1, gl2); + + if ( cmpContig != 0 ) { + result = cmpContig; + } else { + if ( gl1.getStart() < gl2.getStart() ) result = -1; + if ( gl1.getStart() > gl2.getStart() ) result = 1; + } + } + + return result; + } +} From ff2f4df043eae05bfc338898155a34bb50becd06 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 16:07:00 -0400 Subject: [PATCH 130/363] Fixed hardclipping inside indel (right tail) when hard clipping the right tail of a read falls inside a deletion, clipping should fall back to the last base before the deletion to follow the ReadClipper's contract. --- .../src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 4 ++++ 1 file changed, 4 insertions(+) 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 14ebbaa6f6..e0a3a5a533 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -893,6 +893,10 @@ else if (refCoord > read.getAlignmentEnd()) { // 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 + else if (fallsInsideDeletion && endsWithinCigar) + readBases--; } } From 3b73dc89fe94246eeb6f8884e1786f3e30c20678 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Wed, 28 Sep 2011 16:17:31 -0400 Subject: [PATCH 131/363] Making several esoteric arguments in the BQSR @Hidden. Adding basic support for Complete Genomics machine cycle. --- .../gatk/walkers/recalibration/CycleCovariate.java | 13 +++++++++---- .../walkers/recalibration/RecalDataManager.java | 6 ++---- .../RecalibrationArgumentCollection.java | 8 ++++++++ 3 files changed, 19 insertions(+), 8 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 945d02837f..5d07922a78 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 @@ -159,10 +159,11 @@ else if( read.getReadGroup().getPlatform().contains( "454" ) ) { // Some bams ha */ // todo -- this should be put into a common place in the code base - private static List PACBIO_NAMES = Arrays.asList("PACBIO"); private static List ILLUMINA_NAMES = Arrays.asList("ILLUMINA", "SLX", "SOLEXA"); private static List SOLID_NAMES = Arrays.asList("SOLID"); private static List LS454_NAMES = Arrays.asList("454"); + private static List COMPLETE_GENOMICS_NAMES = Arrays.asList("COMPLETE"); + private static List PACBIO_NAMES = Arrays.asList("PACBIO"); private static boolean isPlatform(SAMRecord read, List names) { String pl = read.getReadGroup().getPlatform().toUpperCase(); @@ -176,11 +177,10 @@ private static boolean isPlatform(SAMRecord read, List names) { public void getValues(SAMRecord read, Comparable[] comparable) { //----------------------------- - // ILLUMINA and SOLID + // Illumina, Solid, PacBio, and Complete Genomics //----------------------------- - - if( isPlatform(read, ILLUMINA_NAMES) || isPlatform(read, SOLID_NAMES) || isPlatform(read, PACBIO_NAMES)) { + if( isPlatform(read, ILLUMINA_NAMES) || isPlatform(read, SOLID_NAMES) || isPlatform(read, PACBIO_NAMES) || isPlatform(read, COMPLETE_GENOMICS_NAMES) ) { final int init; final int increment; if( !read.getReadNegativeStrandFlag() ) { @@ -222,6 +222,11 @@ public void getValues(SAMRecord read, Comparable[] comparable) { cycle += increment; } } + + //----------------------------- + // 454 + //----------------------------- + else if ( isPlatform(read, LS454_NAMES) ) { // Some bams have "LS454" and others have just "454" final int readLength = read.getReadLength(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java index ac25d4f131..2daa8c0254 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java @@ -245,8 +245,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu readGroup.setPlatform( RAC.DEFAULT_PLATFORM ); ((GATKSAMRecord)read).setReadGroup( readGroup ); } else { - throw new UserException.MalformedBAM(read, "The input .bam file contains reads with no read group. First observed at read with name = " + read.getReadName() + - " Users must set both the default read group using the --default_read_group argument and the default platform using the --default_platform argument." ); + throw new UserException.MalformedBAM(read, "The input .bam file contains reads with no read group. First observed at read with name = " + read.getReadName() ); } } @@ -271,8 +270,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu } readGroup.setPlatform( RAC.DEFAULT_PLATFORM ); } else { - throw new UserException.MalformedBAM(read, "The input .bam file contains reads with no platform information. First observed at read with name = " + read.getReadName() + - " Users must set the default platform using the --default_platform argument." ); + throw new UserException.MalformedBAM(read, "The input .bam file contains reads with no platform information. First observed at read with name = " + read.getReadName() ); } } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationArgumentCollection.java index f31e2fc5b5..75de84cb40 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalibrationArgumentCollection.java @@ -26,6 +26,7 @@ package org.broadinstitute.sting.gatk.walkers.recalibration; import org.broadinstitute.sting.commandline.Argument; +import org.broadinstitute.sting.commandline.Hidden; /** * Created by IntelliJ IDEA. @@ -41,22 +42,29 @@ public class RecalibrationArgumentCollection { ////////////////////////////////// // Shared Command Line Arguments ////////////////////////////////// + @Hidden @Argument(fullName="default_read_group", shortName="dRG", required=false, doc="If a read has no read group then default to the provided String.") public String DEFAULT_READ_GROUP = null; + @Hidden @Argument(fullName="default_platform", shortName="dP", required=false, doc="If a read has no platform then default to the provided String. Valid options are illumina, 454, and solid.") public String DEFAULT_PLATFORM = null; + @Hidden @Argument(fullName="force_read_group", shortName="fRG", required=false, doc="If provided, the read group ID of EVERY read will be forced to be the provided String. This is useful to collapse all data into a single read group.") public String FORCE_READ_GROUP = null; + @Hidden @Argument(fullName="force_platform", shortName="fP", required=false, doc="If provided, the platform of EVERY read will be forced to be the provided String. Valid options are illumina, 454, and solid.") public String FORCE_PLATFORM = null; + @Hidden @Argument(fullName = "window_size_nqs", shortName="nqs", doc="The window size used by MinimumNQSCovariate for its calculation", required=false) public int WINDOW_SIZE = 5; /** * This window size tells the module in how big of a neighborhood around the current base it should look for the minimum base quality score. */ + @Hidden @Argument(fullName = "homopolymer_nback", shortName="nback", doc="The number of previous bases to look at in HomopolymerCovariate", required=false) public int HOMOPOLYMER_NBACK = 7; + @Hidden @Argument(fullName = "exception_if_no_tile", shortName="throwTileException", doc="If provided, TileCovariate will throw an exception when no tile can be found. The default behavior is to use tile = -1", required=false) public boolean EXCEPTION_IF_NO_TILE = false; From 7e3cb45093f86a29537c3475b46ca3b919f95f96 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Wed, 28 Sep 2011 16:27:28 -0400 Subject: [PATCH 132/363] Further performance optim in banded hmm, about 60% speed improvement over current implementation now --- .../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 55cc38e049..c8328771b4 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 @@ -260,7 +260,7 @@ private double computeReadLikelihoodGivenHaplotypeAffineGaps(byte[] haplotypeBas currentGOP, currentGCP, matchMetricArray, XMetricArray, YMetricArray); // update max in diagonal if (bandedLikelihoods) { - final double bestMetric = MathUtils.softMax(matchMetricArray[indI][indJ], XMetricArray[indI][indJ], YMetricArray[indI][indJ]); + final double bestMetric = MathUtils.max(matchMetricArray[indI][indJ], XMetricArray[indI][indJ], YMetricArray[indI][indJ]); // check if we've fallen off diagonal value by threshold if (bestMetric > maxElementInDiag) { @@ -294,7 +294,7 @@ else if (bestMetric < maxElementInDiag - DIAG_TOL) updateCell(indI, indJ, X_METRIC_LENGTH, Y_METRIC_LENGTH, readBases, readQuals, haplotypeBases, currentGOP, currentGCP, matchMetricArray, XMetricArray, YMetricArray); // update max in diagonal - final double bestMetric = MathUtils.softMax(matchMetricArray[indI][indJ], XMetricArray[indI][indJ], YMetricArray[indI][indJ]); + final double bestMetric = MathUtils.max(matchMetricArray[indI][indJ], XMetricArray[indI][indJ], YMetricArray[indI][indJ]); // check if we've fallen off diagonal value by threshold if (bestMetric > maxElementInDiag) { From a93ece07e3fe5da816fa98fc5b9d7a3d03812501 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 17:01:32 -0400 Subject: [PATCH 133/363] ScatterGatherable reduce reads script Get your reduce read in a matter of seconds... From 64e7b3000c82e11430c453d4ad361e5157832146 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 17:28:52 -0400 Subject: [PATCH 134/363] Fix read spans deletion through the entire interval if the read has a deletion that spans the entire length of the interval, it should not be added to mapped reads. From edf852d47dc74823201102ac7387c2f6eec3659c Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 28 Sep 2011 18:40:03 -0400 Subject: [PATCH 135/363] Adding lists to ReduceReads script script can handle single file or list of files separately now. Always scatter/gathering. From c5f1a4325f2d0a0d2fbdc6be4fbb9fd31e82d6c6 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Wed, 28 Sep 2011 20:23:30 -0400 Subject: [PATCH 136/363] Updated preQC: - full 8.5x11 - concating multiple initiatives / bait_sets - Using NA instead of python None when WR dates are unavailable - In new aggregations where the sample may have per library metrics, only using the sample level metrics, i.e. library is null Updated postQC: - Renamed some variables to assist with traceback() - Fixed crashes on batches with two alleles or two samples such as Seminara_MC_1_09222011 or Engle_MC_2_09222011 - Added dependency tracking to PostCallingQC.scala so that the R script does try to run before the evals are complete Other minor cleanup. Tried to use R 2.13 compactPDF but a few issues to work out with fingerprint boxplots in preQC and geom_text font size in postQC. From 07b0a75d9615ffb7205f2cf831c0bdf3691cf002 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Wed, 28 Sep 2011 21:22:57 -0400 Subject: [PATCH 137/363] Added SlidingRead Unit Test Includes test clipStart and trimToVariableRegion From 4fd5630f6a65c75eaf1a80facfc55cb86b42c390 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Wed, 28 Sep 2011 23:13:50 -0400 Subject: [PATCH 138/363] Added ReadClipper Unit Test * Includes tests that include HardClip to Read and Reference Coords. * Changed ReadUtils.HardClipByReferenceCoordinates from private to protected to allow for testing --- .../sting/utils/clipreads/ReadClipper.java | 2 +- .../utils/clipreads/ReadClipperUnitTest.java | 225 ++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java 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 83db202385..4c07f15f6a 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -68,7 +68,7 @@ private int numDeletions(SAMRecord read) { return result; } - private SAMRecord hardClipByReferenceCoordinates(int refStart, int refStop) { + protected SAMRecord 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); diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java new file mode 100644 index 0000000000..1415379db2 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -0,0 +1,225 @@ +/* + * 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.clipreads; + +import net.sf.samtools.*; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.LinkedList; +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 { + + // TODO: Add error messages on failed tests + + SAMRecord read, expected; + ReadClipper readClipper; + final static String BASES = "ACTG"; + final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 + + @BeforeClass + 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); + } + + @Test + public void testHardClipBothEndsByReferenceCoordinates() { + logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(0,0), new SAMRecord(read.getHeader())); + //clip 1 base + expected = readClipper.hardClipBothEndsByReferenceCoordinates(0,3); + Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); + Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); + Assert.assertEquals(expected.getCigarString(), "1H2M1H"); + + } + + @Test + public void testHardClipByReadCoordinates() { + logger.warn("Executing testHardClipByReadCoordinates"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new SAMRecord(read.getHeader())); + + //clip 1 base at start + 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"); + + } + + @Test + public void testHardClipByReferenceCoordinates() { + logger.warn("Executing testHardClipByReferenceCoordinates"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipByReferenceCoordinates(1,4), new SAMRecord(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"); + + //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"); + + } + + @Test + public void testHardClipByReferenceCoordinatesLeftTail() { + logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(4), new SAMRecord(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"); + + //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"); + + } + + @Test + public void testHardClipByReferenceCoordinatesRightTail() { + logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(1), new SAMRecord(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"); + + //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"); + + } + + @Test + public void testHardClipLowQualEnds() { + logger.warn("Executing testHardClipByReferenceCoordinates"); + + + //Clip whole read + Assert.assertEquals(readClipper.hardClipLowQualEnds((byte)64), new SAMRecord(read.getHeader())); + + //clip 1 base at start + expected = readClipper.hardClipLowQualEnds((byte)34); + 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"); + + // Reverse Quals sequence + readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + + //clip 1 base at end + expected = readClipper.hardClipLowQualEnds((byte)34); + 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 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"); + + // revert Qual sequence + readClipper.getRead().setBaseQualityString(QUALS); + } +} From e366ee18bcc90027f6309fab360aa6b55c63c041 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Thu, 29 Sep 2011 07:46:19 -0400 Subject: [PATCH 139/363] Adding ability to read in and make use of kmer quality tables during HMM evaluation --- .../walkers/recalibration/TableRecalibrationWalker.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java index 174e810c26..e04f5bc4b4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java @@ -170,9 +170,9 @@ public class TableRecalibrationWalker extends ReadWalker requestedCovariates = new ArrayList(); // List of covariates to be used in this calculation - private static final Pattern COMMENT_PATTERN = Pattern.compile("^#.*"); - private static final Pattern OLD_RECALIBRATOR_HEADER = Pattern.compile("^rg,.*"); - private static final Pattern COVARIATE_PATTERN = Pattern.compile("^ReadGroup,QualityScore,.*"); + public static final Pattern COMMENT_PATTERN = Pattern.compile("^#.*"); + public static final Pattern OLD_RECALIBRATOR_HEADER = Pattern.compile("^rg,.*"); + public static final Pattern COVARIATE_PATTERN = Pattern.compile("^ReadGroup,QualityScore,.*"); public static final String EOF_MARKER = "EOF"; private long numReadsWithMalformedColorSpace = 0; From 4d31673cc5980f8ffdd78c78e5f312514dcc9d3b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 09:43:31 -0400 Subject: [PATCH 140/363] No longer supporting YAML file allows us to delete 75% of the sample's codebase --- .../sample/PropertyDefinition.java | 30 --------- .../gatk/datasources/sample/SampleAlias.java | 31 --------- .../datasources/sample/SampleFileParser.java | 65 ------------------- .../gatk/datasources/sample/SampleParser.java | 43 ------------ 4 files changed, 169 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/sample/PropertyDefinition.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleAlias.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleFileParser.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleParser.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/PropertyDefinition.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/PropertyDefinition.java deleted file mode 100644 index 433e0af40f..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/PropertyDefinition.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.sample; - -/** - * Created by IntelliJ IDEA. - * User: brett - * Date: Aug 12, 2010 - * Time: 2:09:16 PM - */ -public class PropertyDefinition { - - String property; - - String[] values; - - public String getProperty() { - return property; - } - - public void setProperty(String property) { - this.property = property; - } - - public String[] getValues() { - return values; - } - - public void setValues(String[] values) { - this.values = values; - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleAlias.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleAlias.java deleted file mode 100644 index ce749cb838..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleAlias.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.sample; - -/** - * Created by IntelliJ IDEA. - * User: brett - * Date: Aug 13, 2010 - * Time: 5:13:46 PM - */ -public class SampleAlias { - - String mainId; - - String[] otherIds; - - public String getMainId() { - return mainId; - } - - public void setMainId(String mainId) { - this.mainId = mainId; - } - - public String[] getOtherIds() { - return otherIds; - } - - public void setOtherIds(String[] otherIds) { - this.otherIds = otherIds; - } - -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleFileParser.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleFileParser.java deleted file mode 100644 index a362af6632..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleFileParser.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.sample; - -/** - * Created by IntelliJ IDEA. - * User: brett - * Date: Aug 12, 2010 - * Time: 1:30:44 PM - */ -public class SampleFileParser { - - private SampleAlias[] sampleAliases; - - private String[] allowedProperties; - - private String[] allowedRelationships; - - private PropertyDefinition[] propertyDefinitions; - - private SampleParser[] samples; - - public PropertyDefinition[] getPropertyDefinitions() { - return propertyDefinitions; - } - - public void setPropertyDefinitions(PropertyDefinition[] propertyDefinitions) { - this.propertyDefinitions = propertyDefinitions; - } - - public SampleFileParser() { - - } - - public String[] getAllowedProperties() { - return allowedProperties; - } - - public void setAllowedProperties(String[] allowedProperties) { - this.allowedProperties = allowedProperties; - } - - public SampleParser[] getSamples() { - return samples; - } - - public void setSamples(SampleParser[] samples) { - this.samples = samples; - } - - public String[] getAllowedRelationships() { - return allowedRelationships; - } - - public void setAllowedRelationships(String[] allowedRelationships) { - this.allowedRelationships = allowedRelationships; - } - - public SampleAlias[] getSampleAliases() { - return sampleAliases; - } - - public void setSampleAliases(SampleAlias[] sampleAliases) { - this.sampleAliases = sampleAliases; - } - -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleParser.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleParser.java deleted file mode 100644 index f5e07ca29b..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleParser.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.sample; - -import java.util.HashMap; - -/** - * Created by IntelliJ IDEA. - * User: brett - * Date: Aug 13, 2010 - * Time: 2:09:43 PM - */ -public class SampleParser { - - private String id; - - private HashMap properties; - - private HashMap relationships; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public HashMap getProperties() { - return properties; - } - - public void setProperties(HashMap properties) { - this.properties = properties; - } - - public HashMap getRelationships() { - return relationships; - } - - public void setRelationships(HashMap relationships) { - this.relationships = relationships; - } - -} From e197dcd1f3bf87d8d55831215fa19ff700c383b2 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 09:44:18 -0400 Subject: [PATCH 141/363] Pre-cleanup commit of Sample and SampleDataSource -- SampleDataSource has all reader functionality disabled --- .../sting/gatk/datasources/sample/Sample.java | 52 +- .../datasources/sample/SampleDataSource.java | 519 +++++++++--------- 2 files changed, 290 insertions(+), 281 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java index ca8756684a..db53d1236c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java @@ -14,6 +14,13 @@ * Time: 3:31:38 PM */ public class Sample implements java.io.Serializable { + private final static String MOTHER = "mother"; + private final static String FATHER = "father"; + private final static String GENDER = "gender"; + private final static String POPULATION = "population"; + private final static String FAMILY = "familyId"; + private final static String AFFECTION = "affection"; + private final static String QUANT_TRAIT = "quantTrait"; private final String id; @@ -31,6 +38,18 @@ public enum Gender { UNKNOWN } + public enum Affection { + /** Status is unknown */ + UNKNOWN, + /** Suffers from the disease */ + AFFECTED, + /** Unaffected by the disease */ + UNAFFECTED, + /** A quantitative trait: value of the trait is stored elsewhere */ + QUANTITATIVE + } + public final static double UNSET_QUANTITIATIVE_TRAIT_VALUE = Double.NaN; + public Sample(String id) { /* if (id == null) { throw new StingException("Error creating sample: sample ID cannot be null"); @@ -46,30 +65,21 @@ public Map getProperties() { return properties; } - public void setProperties(Map properties) { - this.properties = (HashMap) properties; - } - - public Map getRelationships() { - return Collections.unmodifiableMap(this.relationships); - } - + @Deprecated public void setSampleFileEntry(boolean value) { this.hasSampleFileEntry = value; } + @Deprecated public boolean hasSAMFileEntry() { return this.hasSAMFileEntry; } + @Deprecated public void setSAMFileEntry(boolean value) { this.hasSAMFileEntry = value; } - public boolean hasSampleFileEntry() { - return this.hasSampleFileEntry; - } - /** * Get one property * @param key key of property @@ -91,11 +101,11 @@ public void setProperty(String key, Object value) { throw new StingException("The same key cannot exist as a property and a relationship"); } - if (key.equals("gender") && value.getClass() != Gender.class) { + if (key.equals(GENDER) && value.getClass() != Gender.class) { throw new StingException("'gender' property must be of type Sample.Gender"); } - if (key.equals("population") && value.getClass() != String.class) { + if (key.equals(POPULATION) && value.getClass() != String.class) { throw new StingException("'population' property must be of type String"); } @@ -129,7 +139,7 @@ public void setRelationship(String key, Sample value) { * @return sample object with relationship mother, if exists, or null */ public Sample getMother() { - return getRelationship("mother"); + return getRelationship(MOTHER); } /** @@ -137,7 +147,7 @@ public Sample getMother() { * @return sample object with relationship father, if exists, or null */ public Sample getFather() { - return getRelationship("father"); + return getRelationship(FATHER); } /** @@ -145,29 +155,29 @@ public Sample getFather() { * @return property of key "gender" - must be of type Gender */ public Gender getGender() { - return (Gender) properties.get("gender"); + return (Gender) properties.get(GENDER); } public String getPopulation() { - return (String) properties.get("population"); + return (String) properties.get(POPULATION); } public String getFamilyId() { - return (String) properties.get("familyId"); + return (String) properties.get(FAMILY); } /** * @return True if sample is male, false if female, unknown, or null */ public boolean isMale() { - return properties.get("gender") == Gender.MALE; + return properties.get(GENDER) == Gender.MALE; } /** * @return True if sample is female, false if male, unknown or null */ public boolean isFemale() { - return properties.get("gender") == Gender.MALE; + return properties.get(GENDER) == Gender.MALE; } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java index 067bf3f720..5b2c060610 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java @@ -104,266 +104,265 @@ public void addSamplesFromSAMHeader(SAMFileHeader header) { * Parse one sample file and integrate it with samples that are already there * Fail quickly if we find any errors in the file */ - public void addFile(File sampleFile) { - - BufferedReader reader; - try { - reader = new BufferedReader(new FileReader(sampleFile)); - } - catch (IOException e) { - throw new StingException("Could not open sample file " + sampleFile.getAbsolutePath(), e); - } - - // set up YAML reader - a "Constructor" creates java object from YAML and "Loader" loads the file - Constructor con = new Constructor(SampleFileParser.class); - TypeDescription desc = new TypeDescription(SampleFileParser.class); - desc.putListPropertyType("propertyDefinitions", PropertyDefinition.class); - desc.putListPropertyType("sampleAliases", SampleAlias.class); - con.addTypeDescription(desc); - Yaml yaml = new Yaml(con); - - // SampleFileParser stores an object representation of a sample file - this is what we'll parse - SampleFileParser parser; - try { - parser = (SampleFileParser) yaml.load(reader); - } - catch (Exception e) { - throw new StingException("There was a syntactic error with the YAML in sample file " + sampleFile.getAbsolutePath(), e); - } - - // check to see which validation options were built into the file - boolean restrictProperties = parser.getAllowedProperties() != null; - boolean restrictRelationships = parser.getAllowedRelationships() != null; - boolean restrictPropertyValues = parser.getPropertyDefinitions() != null; - - // propertyValues stores the values that are allowed for a given property - HashMap propertyValues = null; - if (restrictPropertyValues) { - propertyValues = new HashMap(); - for (PropertyDefinition def : parser.getPropertyDefinitions()) { - HashSet set = new HashSet(); - for (String value : def.getValues()) { - set.add(value); - } - propertyValues.put(def.getProperty(), set); - } - } - - // make sure the aliases are valid - validateAliases(parser); - - // loop through each sample in the file - a SampleParser stores an object that will become a Sample - for (SampleParser sampleParser : parser.getSamples()) { - - try { - // step 1: add the sample if it doesn't already exist - Sample sample = getSampleById(sampleParser.getId()); - if (sample == null) { - sample = new Sample(sampleParser.getId()); - } - addSample(sample); - sample.setSampleFileEntry(true); - - // step 2: add the properties - if (sampleParser.getProperties() != null) { - for (String property : sampleParser.getProperties().keySet()) { - - // check that property is allowed - if (restrictProperties) { - if (!isPropertyValid(property, parser.getAllowedProperties())) { - throw new StingException(property + " is an invalid property. It is not included in the list " + - "of allowed properties."); - } - } - - // next check that the value is allowed - if (restrictPropertyValues) { - if (!isValueAllowed(property, sampleParser.getProperties().get(property), propertyValues)) { - throw new StingException("The value of property '" + property + "' is invalid. " + - "It is not included in the list of allowed values for this property."); - } - } - - // next check that there isn't already a conflicting property there - if (sample.getProperty(property) != null && - sample.getProperty(property) != sampleParser.getProperties().get(property)) - { - throw new StingException(property + " is a conflicting property!"); - } - - // checks are passed - now add the property! - saveProperty(sample, property, sampleParser.getProperties().get(property)); - } - } - - // step 3: add the relationships - if (sampleParser.getRelationships() != null) { - for (String relationship : sampleParser.getRelationships().keySet()) { - String relativeId = sampleParser.getRelationships().get(relationship); - if (relativeId == null) { - throw new StingException("The relationship cannot be null"); - } - - // first check that it's not invalid - if (restrictRelationships) { - if (!isRelationshipValid(relationship, parser.getAllowedRelationships())) { - throw new StingException(relationship + " is an invalid relationship"); - } - } - - // next check that there isn't already a conflicting property there - if (sample.getRelationship(relationship) != null) { - if (sample.getRelationship(relationship).getId() != sampleParser.getProperties().get(relationship)) { - throw new StingException(relationship + " is a conflicting relationship!"); - } - // if the relationship is already set - and consistent with what we're reading now - no need to continue - else { - continue; - } - } - - // checks are passed - now save the relationship - saveRelationship(sample, relationship, relativeId); - } - } - } catch (Exception e) { - throw new StingException("An error occurred while loading this sample from the sample file: " + - sampleParser.getId(), e); - } - } - - } - - private boolean isValueAllowed(String key, Object value, HashMap valuesList) { - - // if the property values weren't specified for this property, then any value is okay - if (!valuesList.containsKey(key)) { - return true; - } - - // if this property has enumerated values, it must be a string - else if (value.getClass() != String.class) - return false; - - // is the value specified or not? - else if (!valuesList.get(key).contains(value)) - return false; - - return true; - } - - /** - * Makes sure that the aliases are valid - * Checks that 1) no string is used as both a main ID and an alias; - * 2) no alias is used more than once - * @param parser - */ - private void validateAliases(SampleFileParser parser) { - - // no aliases sure validate - if (parser.getSampleAliases() == null) - return; - - HashSet mainIds = new HashSet(); - HashSet otherIds = new HashSet(); - - for (SampleAlias sampleAlias : parser.getSampleAliases()) { - mainIds.add(sampleAlias.getMainId()); - for (String otherId : sampleAlias.getOtherIds()) { - if (mainIds.contains(otherId)) - throw new StingException(String.format("The aliases in your sample file are invalid - the alias %s cannot " + - "be both a main ID and an other ID", otherId)); - - if (!otherIds.add(otherId)) - throw new StingException(String.format("The aliases in your sample file are invalid - %s is listed as an " + - "alias more than once.", otherId)); - } - } - } - - private boolean isPropertyValid(String property, String[] allowedProperties) { - - // is it a special property that is always allowed? - for (String allowedProperty : specialProperties) { - if (property.equals(allowedProperty)) - return true; - } - - // is it in the allowed properties list? - for (String allowedProperty : allowedProperties) { - if (property.equals(allowedProperty)) - return true; - } - - return false; - } - - private boolean isRelationshipValid(String relationship, String[] allowedRelationships) { - - // is it a special relationship that is always allowed? - for (String allowedRelationship : specialRelationships) { - if (relationship.equals(allowedRelationship)) - return true; - } - - // is it in the allowed properties list? - for (String allowedRelationship : allowedRelationships) { - if (relationship.equals(allowedRelationship)) - return true; - } - - return false; - } - - /** - * Saves a property as the correct type - * @param key property key - * @param value property value, as read from YAML parser - * @return property value to be stored - */ - private void saveProperty(Sample sample, String key, Object value) { - - // convert gender to the right type, if it was stored as a String - if (key.equals("gender")) { - if (((String) value).toLowerCase().equals("male")) { - value = Sample.Gender.MALE; - } - else if (((String) value).toLowerCase().equals("female")) { - value = Sample.Gender.FEMALE; - } - else if (((String) value).toLowerCase().equals("unknown")) { - value = Sample.Gender.UNKNOWN; - } - else if (value != null) { - throw new StingException("'gender' property must be male, female, or unknown."); - } - } - try { - sample.setProperty(key, value); - } - catch (Exception e) { - throw new StingException("Could not save property " + key, e); - } - } - - /** - * Saves a relationship as the correct type - * @param key relationship key - * @param relativeId sample ID string of the relative - * @return relationship value to be stored - */ - private void saveRelationship(Sample sample, String key, String relativeId) { - - // get the reference that we'll store as the value - Sample relative = getSampleById(relativeId); - - // create sample object for the relative, if necessary - if (relative == null) { - relative = new Sample(relativeId); - addSample(relative); - } - sample.setRelationship(key, relative); - } + public void addFile(File sampleFile) {} +// +// BufferedReader reader; +// try { +// reader = new BufferedReader(new FileReader(sampleFile)); +// } +// catch (IOException e) { +// throw new StingException("Could not open sample file " + sampleFile.getAbsolutePath(), e); +// } +// +// // set up YAML reader - a "Constructor" creates java object from YAML and "Loader" loads the file +// Constructor con = new Constructor(SampleFileParser.class); +// TypeDescription desc = new TypeDescription(SampleFileParser.class); +// desc.putListPropertyType("propertyDefinitions", PropertyDefinition.class); +// desc.putListPropertyType("sampleAliases", SampleAlias.class); +// con.addTypeDescription(desc); +// Yaml yaml = new Yaml(con); +// +// // SampleFileParser stores an object representation of a sample file - this is what we'll parse +// SampleFileParser parser; +// try { +// parser = (SampleFileParser) yaml.load(reader); +// } +// catch (Exception e) { +// throw new StingException("There was a syntactic error with the YAML in sample file " + sampleFile.getAbsolutePath(), e); +// } +// +// // check to see which validation options were built into the file +// boolean restrictProperties = parser.getAllowedProperties() != null; +// boolean restrictRelationships = parser.getAllowedRelationships() != null; +// boolean restrictPropertyValues = parser.getPropertyDefinitions() != null; +// +// // propertyValues stores the values that are allowed for a given property +// HashMap propertyValues = null; +// if (restrictPropertyValues) { +// propertyValues = new HashMap(); +// for (PropertyDefinition def : parser.getPropertyDefinitions()) { +// HashSet set = new HashSet(); +// for (String value : def.getValues()) { +// set.add(value); +// } +// propertyValues.put(def.getProperty(), set); +// } +// } +// +// // make sure the aliases are valid +// validateAliases(parser); +// +// // loop through each sample in the file - a SampleParser stores an object that will become a Sample +// for (SampleParser sampleParser : parser.getSamples()) { +// +// try { +// // step 1: add the sample if it doesn't already exist +// Sample sample = getSampleById(sampleParser.getId()); +// if (sample == null) { +// sample = new Sample(sampleParser.getId()); +// } +// addSample(sample); +// sample.setSampleFileEntry(true); +// +// // step 2: add the properties +// if (sampleParser.getProperties() != null) { +// for (String property : sampleParser.getProperties().keySet()) { +// +// // check that property is allowed +// if (restrictProperties) { +// if (!isPropertyValid(property, parser.getAllowedProperties())) { +// throw new StingException(property + " is an invalid property. It is not included in the list " + +// "of allowed properties."); +// } +// } +// +// // next check that the value is allowed +// if (restrictPropertyValues) { +// if (!isValueAllowed(property, sampleParser.getProperties().get(property), propertyValues)) { +// throw new StingException("The value of property '" + property + "' is invalid. " + +// "It is not included in the list of allowed values for this property."); +// } +// } +// +// // next check that there isn't already a conflicting property there +// if (sample.getProperty(property) != null && +// sample.getProperty(property) != sampleParser.getProperties().get(property)) +// { +// throw new StingException(property + " is a conflicting property!"); +// } +// +// // checks are passed - now add the property! +// saveProperty(sample, property, sampleParser.getProperties().get(property)); +// } +// } +// +// // step 3: add the relationships +// if (sampleParser.getRelationships() != null) { +// for (String relationship : sampleParser.getRelationships().keySet()) { +// String relativeId = sampleParser.getRelationships().get(relationship); +// if (relativeId == null) { +// throw new StingException("The relationship cannot be null"); +// } +// +// // first check that it's not invalid +// if (restrictRelationships) { +// if (!isRelationshipValid(relationship, parser.getAllowedRelationships())) { +// throw new StingException(relationship + " is an invalid relationship"); +// } +// } +// +// // next check that there isn't already a conflicting property there +// if (sample.getRelationship(relationship) != null) { +// if (sample.getRelationship(relationship).getId() != sampleParser.getProperties().get(relationship)) { +// throw new StingException(relationship + " is a conflicting relationship!"); +// } +// // if the relationship is already set - and consistent with what we're reading now - no need to continue +// else { +// continue; +// } +// } +// +// // checks are passed - now save the relationship +// saveRelationship(sample, relationship, relativeId); +// } +// } +// } catch (Exception e) { +// throw new StingException("An error occurred while loading this sample from the sample file: " + +// sampleParser.getId(), e); +// } +// } +// } +// +// private boolean isValueAllowed(String key, Object value, HashMap valuesList) { +// +// // if the property values weren't specified for this property, then any value is okay +// if (!valuesList.containsKey(key)) { +// return true; +// } +// +// // if this property has enumerated values, it must be a string +// else if (value.getClass() != String.class) +// return false; +// +// // is the value specified or not? +// else if (!valuesList.get(key).contains(value)) +// return false; +// +// return true; +// } +// +// /** +// * Makes sure that the aliases are valid +// * Checks that 1) no string is used as both a main ID and an alias; +// * 2) no alias is used more than once +// * @param parser +// */ +// private void validateAliases(SampleFileParser parser) { +// +// // no aliases sure validate +// if (parser.getSampleAliases() == null) +// return; +// +// HashSet mainIds = new HashSet(); +// HashSet otherIds = new HashSet(); +// +// for (SampleAlias sampleAlias : parser.getSampleAliases()) { +// mainIds.add(sampleAlias.getMainId()); +// for (String otherId : sampleAlias.getOtherIds()) { +// if (mainIds.contains(otherId)) +// throw new StingException(String.format("The aliases in your sample file are invalid - the alias %s cannot " + +// "be both a main ID and an other ID", otherId)); +// +// if (!otherIds.add(otherId)) +// throw new StingException(String.format("The aliases in your sample file are invalid - %s is listed as an " + +// "alias more than once.", otherId)); +// } +// } +// } +// +// private boolean isPropertyValid(String property, String[] allowedProperties) { +// +// // is it a special property that is always allowed? +// for (String allowedProperty : specialProperties) { +// if (property.equals(allowedProperty)) +// return true; +// } +// +// // is it in the allowed properties list? +// for (String allowedProperty : allowedProperties) { +// if (property.equals(allowedProperty)) +// return true; +// } +// +// return false; +// } +// +// private boolean isRelationshipValid(String relationship, String[] allowedRelationships) { +// +// // is it a special relationship that is always allowed? +// for (String allowedRelationship : specialRelationships) { +// if (relationship.equals(allowedRelationship)) +// return true; +// } +// +// // is it in the allowed properties list? +// for (String allowedRelationship : allowedRelationships) { +// if (relationship.equals(allowedRelationship)) +// return true; +// } +// +// return false; +// } +// +// /** +// * Saves a property as the correct type +// * @param key property key +// * @param value property value, as read from YAML parser +// * @return property value to be stored +// */ +// private void saveProperty(Sample sample, String key, Object value) { +// +// // convert gender to the right type, if it was stored as a String +// if (key.equals("gender")) { +// if (((String) value).toLowerCase().equals("male")) { +// value = Sample.Gender.MALE; +// } +// else if (((String) value).toLowerCase().equals("female")) { +// value = Sample.Gender.FEMALE; +// } +// else if (((String) value).toLowerCase().equals("unknown")) { +// value = Sample.Gender.UNKNOWN; +// } +// else if (value != null) { +// throw new StingException("'gender' property must be male, female, or unknown."); +// } +// } +// try { +// sample.setProperty(key, value); +// } +// catch (Exception e) { +// throw new StingException("Could not save property " + key, e); +// } +// } +// +// /** +// * Saves a relationship as the correct type +// * @param key relationship key +// * @param relativeId sample ID string of the relative +// * @return relationship value to be stored +// */ +// private void saveRelationship(Sample sample, String key, String relativeId) { +// +// // get the reference that we'll store as the value +// Sample relative = getSampleById(relativeId); +// +// // create sample object for the relative, if necessary +// if (relative == null) { +// relative = new Sample(relativeId); +// addSample(relative); +// } +// sample.setRelationship(key, relative); +// } From e76f3816289954bb2d5d2b31bb4ddb9dbce1ead5 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 09:57:15 -0400 Subject: [PATCH 142/363] Moved sample package from DataSources to gatk, and renamed it samples -- All associated changes to the codebase are just header updates --- .../sting/gatk/GenomeAnalysisEngine.java | 5 +- .../gatk/contexts/AlignmentContextUtils.java | 2 +- .../sting/gatk/executive/WindowMaker.java | 2 +- .../gatk/iterators/LocusIteratorByState.java | 4 +- .../sample => samples}/Sample.java | 3 +- .../sample => samples}/SampleDataSource.java | 8 +- .../phasing/ReadBackedPhasingWalker.java | 2 +- .../qc/CountLociByPopulationWalker.java | 59 ++++ .../gatk/walkers/qc/CountMalesWalker.java | 28 ++ .../sting/utils/MendelianViolation.java | 4 +- .../sting/utils/ped/PedReader.java | 254 ++++++++++++++++++ .../pileup/AbstractReadBackedPileup.java | 2 +- .../pileup/MergingPileupElementIterator.java | 2 +- .../utils/pileup/PileupElementTracker.java | 2 +- .../pileup/ReadBackedExtendedEventPileup.java | 2 +- .../ReadBackedExtendedEventPileupImpl.java | 2 +- .../sting/utils/pileup/ReadBackedPileup.java | 2 +- .../utils/pileup/ReadBackedPileupImpl.java | 2 +- .../providers/LocusViewTemplate.java | 3 +- .../reads/DownsamplerBenchmark.java | 5 +- .../LocusIteratorByStateUnitTest.java | 3 +- .../SampleDataSourceUnitTest.java | 2 +- .../sample => samples}/SampleUnitTest.java | 2 +- .../pileup/ReadBackedPileupUnitTest.java | 2 +- 24 files changed, 364 insertions(+), 38 deletions(-) rename public/java/src/org/broadinstitute/sting/gatk/{datasources/sample => samples}/Sample.java (98%) rename public/java/src/org/broadinstitute/sting/gatk/{datasources/sample => samples}/SampleDataSource.java (98%) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java rename public/java/test/org/broadinstitute/sting/gatk/{datasources/sample => samples}/SampleDataSourceUnitTest.java (99%) rename public/java/test/org/broadinstitute/sting/gatk/{datasources/sample => samples}/SampleUnitTest.java (96%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 972943e263..f5590b708d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -34,15 +34,14 @@ 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.datasources.sample.Sample; -import org.broadinstitute.sting.gatk.datasources.sample.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.executive.MicroScheduler; import org.broadinstitute.sting.gatk.filters.FilterManager; import org.broadinstitute.sting.gatk.filters.ReadFilter; import org.broadinstitute.sting.gatk.filters.ReadGroupBlackListFilter; import org.broadinstitute.sting.gatk.io.OutputTracker; import org.broadinstitute.sting.gatk.io.stubs.Stub; -import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrack; import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrackBuilder; import org.broadinstitute.sting.gatk.refdata.utils.RMDIntervalGenerator; import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet; 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 1f9a7d705a..707f4e97c0 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java @@ -26,7 +26,7 @@ package org.broadinstitute.sting.gatk.contexts; import net.sf.samtools.SAMReadGroupRecord; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java index cfbce58ee9..42fe89dc91 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java @@ -4,7 +4,7 @@ import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.sample.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.iterators.LocusIterator; import org.broadinstitute.sting.gatk.iterators.LocusIteratorByState; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; 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 e13c5a7641..2f25bf7b16 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -35,8 +35,8 @@ import org.broadinstitute.sting.gatk.DownsamplingMethod; import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; -import org.broadinstitute.sting.gatk.datasources.sample.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.MathUtils; diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java similarity index 98% rename from public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java rename to public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java index db53d1236c..c37796bb61 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -1,9 +1,8 @@ -package org.broadinstitute.sting.gatk.datasources.sample; +package org.broadinstitute.sting.gatk.samples; import org.broadinstitute.sting.utils.exceptions.StingException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java similarity index 98% rename from public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java rename to public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index 5b2c060610..d3c59d9f46 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.gatk.datasources.sample; +package org.broadinstitute.sting.gatk.samples; import net.sf.samtools.SAMFileHeader; import net.sf.samtools.SAMReadGroupRecord; @@ -7,14 +7,8 @@ import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.yaml.snakeyaml.TypeDescription; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.util.*; /** 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 17a6e20f11..ccbcca4f52 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 @@ -30,7 +30,7 @@ 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.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.gatk.filters.MappingQualityZeroFilter; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.*; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java new file mode 100644 index 0000000000..6802e9c8d7 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java @@ -0,0 +1,59 @@ +package org.broadinstitute.sting.gatk.walkers.qc; + +import net.sf.samtools.SAMRecord; +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.LocusWalker; +import org.broadinstitute.sting.gatk.walkers.TreeReducible; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Extends locus walker to print how many reads there are at each locus, by population + */ +public class CountLociByPopulationWalker extends LocusWalker implements TreeReducible { + public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + + // in this HashMap, we'll keep count of how many + HashMap count = new HashMap(); + + ArrayList reads = (ArrayList) context.getBasePileup().getReads(); + + for (SAMRecord read : reads) { + + // get the sample + Sample sample = getToolkit().getSampleByRead(read); + if (sample == null) + return 1; + + if (!count.containsKey(sample.getPopulation())) { + count.put(sample.getPopulation(), 1); + } + count.put(sample.getPopulation(), count.get(sample.getPopulation()) + 1); + } + + System.out.println("\n\n\n***** LOCUS: " + ref.getLocus().toString() + " *****"); + for (String population : count.keySet()) { + System.out.println(String.format("%s | %d", population, count.get(population))); + } + + return 1; + } + + public Long reduceInit() { return 0l; } + + public Long reduce(Integer value, Long sum) { + return value + sum; + } + + /** + * Reduces two subtrees together. In this case, the implementation of the tree reduce + * is exactly the same as the implementation of the single reduce. + */ + public Long treeReduce(Long lhs, Long rhs) { + return lhs + rhs; + } +} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java new file mode 100644 index 0000000000..3c93f07866 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java @@ -0,0 +1,28 @@ +package org.broadinstitute.sting.gatk.walkers.qc; + +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.gatk.contexts.ReferenceContext; +import org.broadinstitute.sting.gatk.refdata.ReadMetaDataTracker; +import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.gatk.walkers.DataSource; +import org.broadinstitute.sting.gatk.walkers.ReadWalker; +import org.broadinstitute.sting.gatk.walkers.Requires; + +/** + * Walks over the input data set, calculating the number of reads seen for diagnostic purposes. + * Can also count the number of reads matching a given criterion using read filters (see the + * --read-filter command line argument). Simplest example of a read-backed analysis. + */ +@Requires({DataSource.READS, DataSource.REFERENCE}) +public class CountMalesWalker extends ReadWalker { + public Integer map(ReferenceContext ref, SAMRecord read, ReadMetaDataTracker tracker) { + Sample sample = getToolkit().getSampleByRead(read); + return sample.isMale() ? 1 : 0; + } + + public Integer reduceInit() { return 0; } + + public Integer reduce(Integer value, Integer sum) { + return value + sum; + } +} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index 8da118174c..7a044e4d1b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -1,13 +1,11 @@ package org.broadinstitute.sting.utils; -import org.apache.commons.lang.ArrayUtils; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; diff --git a/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java b/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java new file mode 100644 index 0000000000..524ba74952 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java @@ -0,0 +1,254 @@ +/* + * 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.ped; + +import org.apache.log4j.Logger; +import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.text.XReadLines; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; + +/** + * Reads PED file-formatted tabular text files + * + * See http://www.broadinstitute.org/mpg/tagger/faq.html + * See http://pngu.mgh.harvard.edu/~purcell/plink/data.shtml#ped + * + * The "ped" file format refers to the widely-used format for linkage pedigree data. + * Each line describes a single (diploid) individual in the following format: + * + * family_ID individual_ID father_ID mother_ID gender phenotype genotype_1 genotype_2 ... + * + * If your data lacks pedigree information (for example, unrelated case/control individuals), + * set the father_ID and mother_ID to 0. sex denotes the individual's gender with 1=male and 2=female. + * phenotype refers to the affected status (for association studies) where 0=unknown, 1=unaffected, 2=affected. + * Finally, each genotype is written as two (=diploid) integer numbers (separated by whitespace), + * where 1=A, 2=C, 3=G, 4=T. No header lines are allowed and all columns must be separated by whitespace. + * Check out the information at the PLINK website on the "ped" file format. + * + * The PED file is a white-space (space or tab) delimited file: the first six columns are mandatory: + * Family ID + * Individual ID + * Paternal ID + * Maternal ID + * Sex (1=male; 2=female; other=unknown) + * Phenotype + * + * The IDs are alphanumeric: the combination of family and individual ID should uniquely identify a person. + * A PED file must have 1 and only 1 phenotype in the sixth column. The phenotype can be either a + * quantitative trait or an affection status column: PLINK will automatically detect which type + * (i.e. based on whether a value other than 0, 1, 2 or the missing genotype code is observed). + * + * NOTE Quantitative traits with decimal points must be coded with a period/full-stop character and + * not a comma, i.e. 2.394 not 2,394 + * + * If an individual's sex is unknown, then any character other than 1 or 2 can be used. + * When new files are created (PED, FAM, or other which contain sex) then the original coding will be + * preserved. However, these individuals will be dropped from any analyses (i.e. phenotype set to missing also) + * and an error message will arise if an analysis that uses family information is requested and an + * individual of 'unknown' sex is specified as a father or mother. + * + * + * HINT You can add a comment to a PED or MAP file by starting the line with a # character. The rest of that + * line will be ignored. Do not start any family IDs with this character therefore. + * + * Affection status, by default, should be coded: + * -9 missing + * 0 missing + * 1 unaffected + * 2 affected + * + * If your file is coded 0/1 to represent unaffected/affected, then use the --1 flag: + * plink --file mydata --1 which will specify a disease phenotype coded: + * + * -9 missing + * 0 unaffected + * 1 affected + * + * The missing phenotype value for quantitative traits is, by default, -9 (this can also be used for + * disease traits as well as 0). It can be reset by including the --missing-phenotype option: + * + * Genotypes (column 7 onwards) should also be white-space delimited; they can be any character + * (e.g. 1,2,3,4 or A,C,G,T or anything else) except 0 which is, by default, the missing genotype + * character. All markers should be biallelic. All SNPs (whether haploid or not) must have two + * alleles specified. Either Both alleles should be missing (i.e. 0) or neither. + * + * No header row should be given. For example, here are two individuals typed for 3 SNPs (one row = one person): + * + * FAM001 1 0 0 1 2 A A G G A C + * FAM001 2 0 0 1 2 A A A G 0 0 + * ... + * + * Note that the GATK does not support genotypes in a PED file. + * + * @author Mark DePristo + * @since 2011 + */ +public class PedReader { + private static Logger logger = Logger.getLogger(PedReader.class); + final static private Set CATAGORICAL_TRAIT_VALUES = new HashSet(Arrays.asList("-9", "0", "1", "2")); + final static private String commentMarker = "#"; + + private final File source; + private final List records; + + + public enum MissingPedFields { + NO_FAMILY_ID, + NO_PARENTS, + NO_SEX, + NO_PHENOTYPE + } + + // phenotype + private final static String PHENOTYPE_MISSING_VALUE = "-9"; + private final static String PHENOTYPE_MISSING_VALUE_SECONDARY = "0"; + private final static String PHENOTYPE_UNAFFECTED = "1"; + private final static String PHENOTYPE_AFFECTED = "2"; + + // Sex + private final static String SEX_MALE = "1"; + private final static String SEX_FEMALE = "2"; + // other=unknown + + public PedReader(File source, EnumSet missingFields) throws FileNotFoundException { + this.source = source; + List lines = new XReadLines(source).readLines(); + this.records = parsePedLines(lines, missingFields); + } + + private final List parsePedLines(final List lines, EnumSet missingFields) { + logger.info("Reading PED file " + source + " with missing fields: " + missingFields); + + // What are the record offsets? + final int familyPos = missingFields.contains(MissingPedFields.NO_FAMILY_ID) ? -1 : 0; + final int samplePos = familyPos + 1; + final int paternalPos = missingFields.contains(MissingPedFields.NO_PARENTS) ? -1 : samplePos + 1; + final int maternalPos = missingFields.contains(MissingPedFields.NO_PARENTS) ? -1 : paternalPos + 1; + final int sexPos = missingFields.contains(MissingPedFields.NO_SEX) ? -1 : Math.max(maternalPos, samplePos) + 1; + final int phenotypePos = missingFields.contains(MissingPedFields.NO_PHENOTYPE) ? -1 : Math.max(sexPos, Math.max(maternalPos, samplePos)) + 1; + final int nExpectedFields = MathUtils.arrayMaxInt(Arrays.asList(samplePos, paternalPos, maternalPos, sexPos, phenotypePos)); + + // go through once and determine properties + int lineNo = 1; + boolean isQT = false; + final List splits = new ArrayList(lines.size()); + for ( final String line : lines ) { + if ( line.startsWith(commentMarker)) continue; + String[] parts = line.split("\\W+"); + + if ( parts.length != nExpectedFields ) + throw new UserException.MalformedFile(source, "Bad PED line " + lineNo + ": wrong number of fields"); + + if ( phenotypePos != -1 ) { + isQT = isQT || CATAGORICAL_TRAIT_VALUES.contains(parts[phenotypePos]); + } + + splits.add(parts); + lineNo++; + } + logger.info("Trait is quantitative? " + isQT); + + // now go through and parse each record + lineNo = 1; + final List recs = new ArrayList(splits.size()); + for ( final String[] parts : splits ) { + String familyID = null, individualID, paternalID = null, maternalID = null; + Sample.Gender sex = Sample.Gender.UNKNOWN; + double quantitativePhenotype = Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE; + Sample.Affection affection = Sample.Affection.UNKNOWN; + + if ( familyPos != -1 ) familyID = parts[familyPos]; + individualID = parts[samplePos]; + if ( paternalPos != -1 ) paternalID = parts[paternalPos]; + if ( maternalPos != -1 ) maternalID = parts[maternalPos]; + + if ( sexPos != -1 ) { + if ( parts[sexPos].equals(SEX_MALE) ) sex = Sample.Gender.MALE; + else if ( parts[sexPos].equals(SEX_FEMALE) ) sex = Sample.Gender.FEMALE; + else sex = Sample.Gender.UNKNOWN; + } + + if ( phenotypePos != -1 ) { + if ( isQT ) { + if ( parts[phenotypePos].equals(PHENOTYPE_MISSING_VALUE) ) + affection = Sample.Affection.UNKNOWN; + else { + affection = Sample.Affection.QUANTITATIVE; + quantitativePhenotype = Double.valueOf(parts[phenotypePos]); + } + } else { + if ( parts[phenotypePos].equals(PHENOTYPE_MISSING_VALUE) ) affection = Sample.Affection.UNKNOWN; + else if ( parts[phenotypePos].equals(PHENOTYPE_MISSING_VALUE_SECONDARY) ) affection = Sample.Affection.UNKNOWN; + else if ( parts[phenotypePos].equals(PHENOTYPE_UNAFFECTED) ) affection = Sample.Affection.UNAFFECTED; + else if ( parts[phenotypePos].equals(PHENOTYPE_AFFECTED) ) affection = Sample.Affection.AFFECTED; + else throw new ReviewedStingException("Unexpected phenotype type " + parts[phenotypePos] + " at line " + lineNo); + } + } + + recs.add(new PedRecord(familyID, individualID, paternalID, maternalID, sex, quantitativePhenotype, affection)); + + lineNo++; + } + + return Collections.unmodifiableList(recs); + } + + public List getRecords() { + return records; + } + + public void fillSampleDB(SampleDataSource db) { + for ( final PedRecord rec : getRecords() ) { + Sample s = db.getOrCreateSample(rec.individualID); + } + } +} + +class PedRecord { + final String familyID, individualID, paternalID, maternalID; + final Sample.Gender sex; + final double quantitativePhenotype; + final Sample.Affection affection; + + PedRecord(final String familyID, final String individualID, + final String paternalID, final String maternalID, + final Sample.Gender sex, + final double quantitativePhenotype, final Sample.Affection affection) { + this.familyID = familyID; + this.individualID = individualID; + this.paternalID = paternalID; + this.maternalID = maternalID; + this.sex = sex; + this.quantitativePhenotype = quantitativePhenotype; + this.affection = affection; + } +} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java index 3821c9c8ab..92915f5904 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java @@ -26,7 +26,7 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java b/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java index 7005cf8699..58afc35e94 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java @@ -25,7 +25,7 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.picard.util.PeekableIterator; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import java.util.Comparator; import java.util.Iterator; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java index 29e4316952..137167dfc0 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java @@ -24,7 +24,7 @@ package org.broadinstitute.sting.utils.pileup; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import java.util.*; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java index 8d43a368aa..4e29be9348 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java @@ -25,7 +25,7 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.collections.Pair; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java index 31d29430a9..6a3de5570c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java @@ -24,7 +24,7 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java index 36b8a8c654..449ead56e6 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java @@ -25,7 +25,7 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.HasGenomeLocation; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java index e5b0549612..7ebf6281b1 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java @@ -24,7 +24,7 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import java.util.List; 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 acfefd627f..61977c99ae 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 @@ -8,11 +8,10 @@ import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; import org.broadinstitute.sting.gatk.datasources.reads.Shard; import org.broadinstitute.sting.gatk.executive.WindowMaker; -import org.broadinstitute.sting.gatk.datasources.sample.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; 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.iterators.LocusIteratorByState; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.testng.annotations.BeforeClass; 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 2ecd757544..17700cf7c1 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 @@ -26,7 +26,6 @@ import com.google.caliper.Param; import net.sf.picard.filter.FilteringIterator; -import net.sf.picard.filter.SamRecordFilter; import net.sf.samtools.SAMFileReader; import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.commandline.Tags; @@ -34,15 +33,13 @@ import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.arguments.GATKArgumentCollection; import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; -import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; -import org.broadinstitute.sting.gatk.datasources.sample.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.filters.ReadFilter; import org.broadinstitute.sting.gatk.filters.UnmappedReadFilter; import org.broadinstitute.sting.gatk.iterators.LocusIteratorByState; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.baq.BAQ; -import java.io.File; import java.util.Collections; import java.util.Iterator; 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 32d3675b78..c8cfdac9aa 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java @@ -1,6 +1,5 @@ package org.broadinstitute.sting.gatk.iterators; -import net.sf.picard.filter.SamRecordFilter; import net.sf.samtools.SAMFileHeader; import net.sf.samtools.SAMFileReader; import net.sf.samtools.SAMRecord; @@ -12,7 +11,7 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; -import org.broadinstitute.sting.gatk.datasources.sample.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.utils.GenomeLocParser; diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java similarity index 99% rename from public/java/test/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSourceUnitTest.java rename to public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java index 59405c065d..390ed95aeb 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/sample/SampleDataSourceUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.gatk.datasources.sample; +package org.broadinstitute.sting.gatk.samples; import net.sf.samtools.SAMFileHeader; import org.broadinstitute.sting.utils.variantcontext.Allele; diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/sample/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java similarity index 96% rename from public/java/test/org/broadinstitute/sting/gatk/datasources/sample/SampleUnitTest.java rename to public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index 67e84cdd82..7f9a57f087 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/sample/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -1,4 +1,4 @@ -package org.broadinstitute.sting.gatk.datasources.sample; +package org.broadinstitute.sting.gatk.samples; import org.testng.Assert; import org.broadinstitute.sting.BaseTest; diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java index fb479ab47c..d982d54a24 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java @@ -28,7 +28,7 @@ import net.sf.samtools.SAMReadGroupRecord; import net.sf.samtools.SAMRecord; import org.testng.Assert; -import org.broadinstitute.sting.gatk.datasources.sample.Sample; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.testng.annotations.Test; From 2a0cd556d39fbe3a376f8c8e66a91f3004d79611 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 10:34:51 -0400 Subject: [PATCH 143/363] Further cleanup of Sample -- Cleaned up interface functions in GAE -- Added Walker.getSampleDB() function which is an easier option for tools to get the samples db --- .../sting/gatk/GenomeAnalysisEngine.java | 133 +------------- .../gatk/contexts/AlignmentContextUtils.java | 9 - .../gatk/executive/LinearMicroScheduler.java | 2 +- .../sting/gatk/executive/ShardTraverser.java | 2 +- .../gatk/iterators/LocusIteratorByState.java | 4 +- .../sting/gatk/samples/Sample.java | 165 +++++++++--------- .../sting/gatk/samples/SampleDataSource.java | 37 ++-- .../sting/gatk/walkers/Walker.java | 10 ++ .../beagle/ProduceBeagleInputWalker.java | 2 +- .../walkers/coverage/CallableLociWalker.java | 4 +- .../phasing/ReadBackedPhasingWalker.java | 2 +- .../qc/CountLociByPopulationWalker.java | 2 +- .../gatk/walkers/qc/CountMalesWalker.java | 2 +- .../sting/utils/MendelianViolation.java | 14 +- .../pileup/AbstractReadBackedPileup.java | 4 +- .../utils/pileup/PileupElementTracker.java | 4 +- .../samples/SampleDataSourceUnitTest.java | 8 +- .../sting/gatk/samples/SampleUnitTest.java | 2 +- .../pileup/ReadBackedPileupUnitTest.java | 6 +- 19 files changed, 137 insertions(+), 275 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index f5590b708d..38ef2879b8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -1034,121 +1034,14 @@ public ReadMetrics getCumulativeMetrics() { return readsDataSource == null ? null : readsDataSource.getCumulativeReadMetrics(); } - public SampleDataSource getSampleMetadata() { - return this.sampleDataSource; - } - - /** - * Get a sample by its ID - * If an alias is passed in, return the main sample object - * @param id sample id - * @return sample Object with this ID - */ - public Sample getSampleById(String id) { - return sampleDataSource.getSampleById(id); - } - - /** - * Get the sample for a given read group - * Must first look up ID for read group - * @param readGroup of sample - * @return sample object with ID from the read group - */ - public Sample getSampleByReadGroup(SAMReadGroupRecord readGroup) { - return sampleDataSource.getSampleByReadGroup(readGroup); - } - - /** - * Get a sample for a given read - * Must first look up read group, and then sample ID for that read group - * @param read of sample - * @return sample object of this read - */ - public Sample getSampleByRead(SAMRecord read) { - return getSampleByReadGroup(read.getReadGroup()); - } + // ------------------------------------------------------------------------------------- + // + // code for working with Samples database + // + // ------------------------------------------------------------------------------------- - /** - * Get number of sample objects - * @return size of samples map - */ - public int sampleCount() { - return sampleDataSource.sampleCount(); - } - - /** - * Return all samples with a given family ID - * Note that this isn't terribly efficient (linear) - it may be worth adding a new family ID data structure for this - * @param familyId family ID - * @return Samples with the given family ID - */ - public Set getFamily(String familyId) { - return sampleDataSource.getFamily(familyId); - } - - /** - * Returns all children of a given sample - * See note on the efficiency of getFamily() - since this depends on getFamily() it's also not efficient - * @param sample parent sample - * @return children of the given sample - */ - public Set getChildren(Sample sample) { - return sampleDataSource.getChildren(sample); - } - - /** - * Gets all the samples - * @return - */ - public Collection getSamples() { - return sampleDataSource.getSamples(); - } - - /** - * Takes a list of sample names and returns their corresponding sample objects - * - * @param sampleNameList List of sample names - * @return Corresponding set of samples - */ - public Set getSamples(Collection sampleNameList) { - return sampleDataSource.getSamples(sampleNameList); - } - - - /** - * Returns a set of samples that have any value (which could be null) for a given property - * @param key Property key - * @return Set of samples with the property - */ - public Set getSamplesWithProperty(String key) { - return sampleDataSource.getSamplesWithProperty(key); - } - - /** - * Returns a set of samples that have a property with a certain value - * Value must be a string for now - could add a similar method for matching any objects in the future - * - * @param key Property key - * @param value String property value - * @return Set of samples that match key and value - */ - public Set getSamplesWithProperty(String key, String value) { - return sampleDataSource.getSamplesWithProperty(key, value); - - } - - /** - * Returns a set of sample objects for the sample names in a variant context - * - * @param context Any variant context - * @return a set of the sample objects - */ - public Set getSamplesByVariantContext(VariantContext context) { - Set samples = new HashSet(); - for (String sampleName : context.getSampleNames()) { - samples.add(sampleDataSource.getOrCreateSample(sampleName)); - } - return samples; + public SampleDataSource getSampleDB() { + return this.sampleDataSource; } /** @@ -1158,18 +1051,6 @@ public Set getSAMFileSamples() { return sampleDataSource.getSAMFileSamples(); } - /** - * Return a subcontext restricted to samples with a given property key/value - * Gets the sample names from key/value and relies on VariantContext.subContextFromGenotypes for the filtering - * @param context VariantContext to filter - * @param key property key - * @param value property value (must be string) - * @return subcontext - */ - public VariantContext subContextFromSampleProperty(VariantContext context, String key, String value) { - return sampleDataSource.subContextFromSampleProperty(context, key, value); - } - public Map getApproximateCommandLineArguments(Object... argumentProviders) { return CommandLineUtils.getApproximateCommandLineArguments(parsingEngine,argumentProviders); } 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 707f4e97c0..f77fbe4e91 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java @@ -26,7 +26,6 @@ package org.broadinstitute.sting.gatk.contexts; import net.sf.samtools.SAMReadGroupRecord; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -76,14 +75,6 @@ public static Map splitContextBySampleName(AlignmentCo return splitContextBySampleName(context, null); } - public static Map splitContextBySample(AlignmentContext context) { - Map m = new HashMap(); - for ( Map.Entry entry : splitContextBySampleName(context, null).entrySet() ) { - m.put(new Sample(entry.getKey()), entry.getValue()); - } - return m; - } - /** * Splits the given AlignmentContext into a StratifiedAlignmentContext per sample, but referencd by sample name instead * of sample object. 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 09ab4bd444..b7846399f7 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java @@ -56,7 +56,7 @@ public Object execute(Walker walker, ShardStrategy shardStrategy) { traversalEngine.startTimersIfNecessary(); if(shard.getShardType() == Shard.ShardType.LOCUS) { LocusWalker lWalker = (LocusWalker)walker; - WindowMaker windowMaker = new WindowMaker(shard, engine.getGenomeLocParser(), getReadIterator(shard), shard.getGenomeLocs(), engine.getSampleMetadata()); + WindowMaker windowMaker = new WindowMaker(shard, engine.getGenomeLocParser(), getReadIterator(shard), shard.getGenomeLocs(), engine.getSampleDB()); for(WindowMaker.WindowMakerIterator iterator: windowMaker) { ShardDataProvider dataProvider = new LocusShardDataProvider(shard,iterator.getSourceInfo(),engine.getGenomeLocParser(),iterator.getLocus(),iterator,reference,rods); Object result = traversalEngine.traverse(walker, dataProvider, accumulator.getReduceInit()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java b/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java index 2b6488ada5..428813b714 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java @@ -62,7 +62,7 @@ public Object call() { Object accumulator = walker.reduceInit(); LocusWalker lWalker = (LocusWalker)walker; - WindowMaker windowMaker = new WindowMaker(shard,microScheduler.getEngine().getGenomeLocParser(),microScheduler.getReadIterator(shard),shard.getGenomeLocs(), microScheduler.engine.getSampleMetadata()); // todo: microScheduler.engine is protected - is it okay to user it here? + WindowMaker windowMaker = new WindowMaker(shard,microScheduler.getEngine().getGenomeLocParser(),microScheduler.getReadIterator(shard),shard.getGenomeLocs(), microScheduler.engine.getSampleDB()); // todo: microScheduler.engine is protected - is it okay to user it here? ShardDataProvider dataProvider = null; for(WindowMaker.WindowMakerIterator iterator: windowMaker) { 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 2f25bf7b16..61b861fd60 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -542,7 +542,7 @@ public ReadStateManager(Iterator source, DownsamplingMethod downsampl Map readSelectors = new HashMap(); for(Sample sample: samples) { readStatesBySample.put(sample,new PerSampleReadStateManager()); - readSelectors.put(sample.getId(),downsamplingMethod.type == DownsampleType.BY_SAMPLE ? new NRandomReadSelector(null,targetCoverage) : new AllReadsSelector()); + readSelectors.put(sample.getID(),downsamplingMethod.type == DownsampleType.BY_SAMPLE ? new NRandomReadSelector(null,targetCoverage) : new AllReadsSelector()); } samplePartitioner = new SamplePartitioner(readSelectors); @@ -640,7 +640,7 @@ public void collectPendingReads() { samplePartitioner.complete(); for(Sample sample: samples) { - ReadSelector aggregator = samplePartitioner.getSelectedReads(sample.getId()); + ReadSelector aggregator = samplePartitioner.getSelectedReads(sample.getID()); Collection newReads = new ArrayList(aggregator.getSelectedReads()); 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 c37796bb61..f92533f089 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 @@ import org.broadinstitute.sting.utils.exceptions.StingException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -13,23 +14,16 @@ * Time: 3:31:38 PM */ public class Sample implements java.io.Serializable { - private final static String MOTHER = "mother"; - private final static String FATHER = "father"; - private final static String GENDER = "gender"; - private final static String POPULATION = "population"; - private final static String FAMILY = "familyId"; - private final static String AFFECTION = "affection"; - private final static String QUANT_TRAIT = "quantTrait"; - - private final String id; - - private boolean hasSampleFileEntry = false; // true if this sample has an entry in a sample file + final private String familyID, paternalID, maternalID; + final private Sample.Gender gender; + final private double quantitativePhenotype; + final private Sample.Affection affection; + final private String population; + final private String ID; + final private SampleDataSource dataSource; private boolean hasSAMFileEntry = false; // true if this sample has an entry in the SAM file - - private HashMap properties = new HashMap(); - - private HashMap relationships = new HashMap(); + private Map properties = new HashMap(); public enum Gender { MALE, @@ -47,26 +41,28 @@ public enum Affection { /** A quantitative trait: value of the trait is stored elsewhere */ QUANTITATIVE } - public final static double UNSET_QUANTITIATIVE_TRAIT_VALUE = Double.NaN; - - public Sample(String id) { -/* if (id == null) { - throw new StingException("Error creating sample: sample ID cannot be null"); - }*/ - this.id = id; - } - public String getId() { - return this.id; - } + public final static double UNSET_QUANTITIATIVE_TRAIT_VALUE = Double.NaN; - public Map getProperties() { - return properties; + public Sample(final String ID, final SampleDataSource dataSource, + final String familyID, final String paternalID, final String maternalID, + final Gender gender, final double quantitativePhenotype, final Affection affection, + final String population) { + this.familyID = familyID; + this.paternalID = paternalID; + this.maternalID = maternalID; + this.gender = gender; + this.quantitativePhenotype = quantitativePhenotype; + this.affection = affection; + this.population = population; + this.ID = ID; + this.dataSource = dataSource; } - @Deprecated - public void setSampleFileEntry(boolean value) { - this.hasSampleFileEntry = value; + public Sample(String id, SampleDataSource dataSource) { + this(id, dataSource, + null, null, null, + Gender.UNKNOWN, UNSET_QUANTITIATIVE_TRAIT_VALUE, Affection.UNKNOWN, null); } @Deprecated @@ -79,58 +75,39 @@ public void setSAMFileEntry(boolean value) { this.hasSAMFileEntry = value; } - /** - * Get one property - * @param key key of property - * @return value of property as generic object - */ - public Object getProperty(String key) { - return properties.get(key); + // ------------------------------------------------------------------------------------- + // + // standard property getters + // + // ------------------------------------------------------------------------------------- + + public String getID() { + return ID; } - /** - * Set a property - * If property already exists, it is overwritten - * @param key key of property - * @param value object to be stored in properties array - */ - public void setProperty(String key, Object value) { - if (relationships.containsKey(key)) { - throw new StingException("The same key cannot exist as a property and a relationship"); - } + public String getFamilyID() { + return familyID; + } - if (key.equals(GENDER) && value.getClass() != Gender.class) { - throw new StingException("'gender' property must be of type Sample.Gender"); - } + public String getPaternalID() { + return paternalID; + } - if (key.equals(POPULATION) && value.getClass() != String.class) { - throw new StingException("'population' property must be of type String"); - } + public String getMaternalID() { + return maternalID; + } - properties.put(key, value); + public Affection getAffection() { + return affection; } - /** - * Get one relationship - * @param key of relationship - * @return Sample object that this relationship points to - */ - public Sample getRelationship(String key) { - return relationships.get(key); + public boolean hasQuantitativeTrait() { + return affection == Affection.QUANTITATIVE; } - /** - * Set one relationship - * If already set, it is overwritten - * @param key key of the relationship - * @param value Sample object this relationship points to - */ - public void setRelationship(String key, Sample value) { - if (properties.containsKey(key)) { - throw new StingException("The same key cannot exist as a property and a relationship"); - } - relationships.put(key, value); + public double getQuantitativePhenotype() { + return quantitativePhenotype; } /** @@ -138,7 +115,7 @@ public void setRelationship(String key, Sample value) { * @return sample object with relationship mother, if exists, or null */ public Sample getMother() { - return getRelationship(MOTHER); + return dataSource.getSampleById(maternalID); } /** @@ -146,7 +123,7 @@ public Sample getMother() { * @return sample object with relationship father, if exists, or null */ public Sample getFather() { - return getRelationship(FATHER); + return dataSource.getSampleById(paternalID); } /** @@ -154,29 +131,48 @@ public Sample getFather() { * @return property of key "gender" - must be of type Gender */ public Gender getGender() { - return (Gender) properties.get(GENDER); + return gender; } public String getPopulation() { - return (String) properties.get(POPULATION); + return population; } public String getFamilyId() { - return (String) properties.get(FAMILY); + return familyID; } /** * @return True if sample is male, false if female, unknown, or null */ public boolean isMale() { - return properties.get(GENDER) == Gender.MALE; + return getGender() == Gender.MALE; } /** * @return True if sample is female, false if male, unknown or null */ public boolean isFemale() { - return properties.get(GENDER) == Gender.MALE; + return getGender() == Gender.MALE; + } + + // ------------------------------------------------------------------------------------- + // + // code for working with additional -- none standard -- properites + // + // ------------------------------------------------------------------------------------- + + public Map getExtraProperties() { + return Collections.unmodifiableMap(properties); + } + + /** + * Get one property + * @param key key of property + * @return value of property as generic object + */ + public Object getExtraPropertyValue(final String key) { + return properties.get(key); } /** @@ -184,7 +180,7 @@ public boolean isFemale() { * @param key property key * @return true if sample has this property (even if its value is null) */ - public boolean hasProperty(String key) { + public boolean hasExtraProperty(String key) { return properties.containsKey(key); } @@ -196,17 +192,14 @@ public boolean equals(Object o) { Sample sample = (Sample) o; if (hasSAMFileEntry != sample.hasSAMFileEntry) return false; - if (hasSampleFileEntry != sample.hasSampleFileEntry) return false; - if (id != null ? !id.equals(sample.id) : sample.id != null) return false; + if (ID != null ? !ID.equals(sample.ID) : sample.ID != null) return false; if (properties != null ? !properties.equals(sample.properties) : sample.properties != null) return false; - if (relationships != null ? !relationships.equals(sample.relationships) : sample.relationships != null) - return false; return true; } @Override public int hashCode() { - return id != null ? id.hashCode() : "".hashCode(); + return ID != null ? ID.hashCode() : "".hashCode(); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index d3c59d9f46..f4855b27f8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -46,12 +46,6 @@ public class SampleDataSource { */ private HashMap sampleAliases = new HashMap(); - /** - * While loading sample files, we must be aware of "special" properties and relationships that are always allowed - */ - public static final String[] specialProperties = new String[] {"familyId", "population", "gender"}; - public static final String[] specialRelationships = new String[] {"mother", "father"}; - /** * Constructor takes both a SAM header and sample files because the two must be integrated. * @param header SAMFileHeader that has been created for this analysis @@ -63,8 +57,7 @@ public SampleDataSource(SAMFileHeader header, List sampleFiles) { // create empty sample object for each sample referenced in the SAM header for (String sampleName : SampleUtils.getSAMFileSamples(header)) { if (!hasSample(sampleName)) { - Sample newSample = new Sample(sampleName); - newSample.setSAMFileEntry(true); + Sample newSample = new Sample(sampleName, this); samples.put(sampleName, newSample); } } @@ -78,7 +71,7 @@ public SampleDataSource(SAMFileHeader header, List sampleFiles) { } public SampleDataSource() { - samples.put(null, new Sample(null)); + samples.put(null, new Sample(null, this)); } /** @@ -87,7 +80,7 @@ public SampleDataSource() { public void addSamplesFromSAMHeader(SAMFileHeader header) { for (String sampleName : SampleUtils.getSAMFileSamples(header)) { if (!hasSample(sampleName)) { - Sample newSample = new Sample(sampleName); + Sample newSample = new Sample(sampleName, this); newSample.setSAMFileEntry(true); samples.put(sampleName, newSample); } @@ -151,9 +144,9 @@ public void addFile(File sampleFile) {} // // try { // // step 1: add the sample if it doesn't already exist -// Sample sample = getSampleById(sampleParser.getId()); +// Sample sample = getSampleById(sampleParser.getID()); // if (sample == null) { -// sample = new Sample(sampleParser.getId()); +// sample = new Sample(sampleParser.getID()); // } // addSample(sample); // sample.setSampleFileEntry(true); @@ -207,7 +200,7 @@ public void addFile(File sampleFile) {} // // // next check that there isn't already a conflicting property there // if (sample.getRelationship(relationship) != null) { -// if (sample.getRelationship(relationship).getId() != sampleParser.getProperties().get(relationship)) { +// if (sample.getRelationship(relationship).getID() != sampleParser.getProperties().get(relationship)) { // throw new StingException(relationship + " is a conflicting relationship!"); // } // // if the relationship is already set - and consistent with what we're reading now - no need to continue @@ -222,7 +215,7 @@ public void addFile(File sampleFile) {} // } // } catch (Exception e) { // throw new StingException("An error occurred while loading this sample from the sample file: " + -// sampleParser.getId(), e); +// sampleParser.getID(), e); // } // } // } @@ -377,7 +370,7 @@ private String aliasFilter(String sampleId) { * @param sample to be added */ private void addSample(Sample sample) { - samples.put(sample.getId(), sample); + samples.put(sample.getID(), sample); } /** @@ -496,7 +489,7 @@ public Set getSamples(Collection sampleNameList) { public Set getSamplesWithProperty(String key) { HashSet toReturn = new HashSet(); for (Sample s : samples.values()) { - if (s.hasProperty(key)) + if (s.hasExtraProperty(key)) toReturn.add(s); } return toReturn; @@ -513,7 +506,7 @@ public Set getSamplesWithProperty(String key) { public Set getSamplesWithProperty(String key, String value) { Set toReturn = getSamplesWithProperty(key); for (Sample s : toReturn) { - if (!s.getProperty(key).equals(value)) + if (!s.getExtraPropertyValue(key).equals(value)) toReturn.remove(s); } return toReturn; @@ -522,7 +515,7 @@ public Set getSamplesWithProperty(String key, String value) { public Sample getOrCreateSample(String id) { Sample sample = getSampleById(id); if (sample == null) { - sample = new Sample(id); + sample = new Sample(id, this); addSample(sample); } return sample; @@ -568,16 +561,10 @@ public VariantContext subContextFromSampleProperty(VariantContext context, Strin Set samplesWithProperty = new HashSet(); for (String sampleName : context.getSampleNames()) { Sample s = samples.get(sampleName); - if (s != null && s.hasProperty(key) && s.getProperty(key).equals(value)) + if (s != null && s.hasExtraProperty(key) && s.getExtraPropertyValue(key).equals(value)) samplesWithProperty.add(sampleName); } Map genotypes = context.getGenotypes(samplesWithProperty); return context.subContextFromGenotypes(genotypes.values()); } - - public static SampleDataSource createEmptyDataSource() { - SAMFileHeader header = new SAMFileHeader(); - return new SampleDataSource(header, null); - } - } 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 10261112ca..ef791f12f9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java @@ -30,6 +30,8 @@ import org.broadinstitute.sting.gatk.CommandLineGATK; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.filters.MalformedReadFilter; +import org.broadinstitute.sting.gatk.samples.Sample; +import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.collections.Pair; @@ -87,6 +89,14 @@ protected SAMSequenceDictionary getMasterSequenceDictionary() { return getToolkit().getMasterSequenceDictionary(); } + protected SampleDataSource getSampleDB() { + return getToolkit().getSampleDB(); + } + + protected Sample getSampleByID(final String id) { + return getToolkit().getSampleDB().getSampleById(id); + } + /** * (conceptual static) method that states whether you want to see reads piling up at a locus * that contain a deletion at the locus. 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 87695077df..ecc4ad7935 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 @@ -247,7 +247,7 @@ public void writeBeagleOutput(VariantContext preferredVC, VariantContext otherVC Map preferredGenotypes = preferredVC.getGenotypes(); Map otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; for ( String sample : samples ) { - boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getToolkit().getSampleById(sample).isMale(); + boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSampleByID(sample).isMale(); Genotype genotype; boolean isValidation; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java index 32875a098e..1e2d402713 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java @@ -227,9 +227,9 @@ public enum CalledState { @Override public void initialize() { - if ( getToolkit().getSamples().size() != 2 ) { + if ( getSampleDB().getSamples().size() != 2 ) { // unbelievably there are actually two samples even when there's just one in the header. God I hate this Samples system - throw new UserException.BadArgumentValue("-I", "CallableLoci only works for a single sample, but multiple samples were found in the provided BAM files: " + getToolkit().getSamples()); + throw new UserException.BadArgumentValue("-I", "CallableLoci only works for a single sample, but multiple samples were found in the provided BAM files: " + getSampleDB().getSamples()); } try { 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 ccbcca4f52..a4729371ab 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 @@ -1102,7 +1102,7 @@ else if (alignment.hasExtendedEventPileup()) { if (!p.isDeletion()) // IGNORE deletions for now readBases.putReadBase(p); } - sampleReadBases.put(sample.getId(), readBases); + sampleReadBases.put(sample.getID(), readBases); } } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java index 6802e9c8d7..bdff128c8b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java @@ -25,7 +25,7 @@ public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentCo for (SAMRecord read : reads) { // get the sample - Sample sample = getToolkit().getSampleByRead(read); + Sample sample = getSampleDB().getSampleByRead(read); if (sample == null) return 1; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java index 3c93f07866..2d89a7d442 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java @@ -16,7 +16,7 @@ @Requires({DataSource.READS, DataSource.REFERENCE}) public class CountMalesWalker extends ReadWalker { public Integer map(ReferenceContext ref, SAMRecord read, ReadMetaDataTracker tracker) { - Sample sample = getToolkit().getSampleByRead(read); + Sample sample = getSampleDB().getSampleByRead(read); return sample.isMale() ? 1 : 0; } diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index 7a044e4d1b..a87a73a2d3 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -88,9 +88,9 @@ public MendelianViolation(String family, double minGenotypeQualityP) { * @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(); + sampleMom = sample.getMother().getID(); + sampleDad = sample.getFather().getID(); + sampleChild = sample.getID(); minGenotypeQuality = minGenotypeQualityP; } @@ -102,13 +102,13 @@ public MendelianViolation(Sample sample, double minGenotypeQualityP) { */ public MendelianViolation(GenomeAnalysisEngine engine, double minGenotypeQualityP) { boolean gotSampleInformation = false; - Collection samples = engine.getSamples(); + Collection samples = engine.getSampleDB().getSamples(); // Iterate through all samples in the sample_metadata file but we really can only take one. for (Sample sample : samples) { if (sample.getMother() != null && sample.getFather() != null) { - sampleMom = sample.getMother().getId(); - sampleDad = sample.getFather().getId(); - sampleChild = sample.getId(); + sampleMom = sample.getMother().getID(); + sampleDad = sample.getFather().getID(); + sampleChild = sample.getID(); minGenotypeQuality = minGenotypeQualityP; gotSampleInformation = true; break; // we can only deal with one trio information diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java index 92915f5904..3bc325cea5 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java @@ -555,7 +555,7 @@ public Collection getSampleNames() { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; Collection sampleNames = new HashSet(); for (Sample sample : tracker.getSamples()) { - sampleNames.add(sample.getId()); + sampleNames.add(sample.getID()); } return sampleNames; } @@ -700,7 +700,7 @@ public RBP getPileupForSample(Sample sample) { for(PE p: pileupElementTracker) { SAMRecord read = p.getRead(); if(sample != null) { - if(read.getReadGroup() != null && sample.getId().equals(read.getReadGroup().getSample())) + if(read.getReadGroup() != null && sample.getID().equals(read.getReadGroup().getSample())) filteredTracker.add(p); } else { diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java index 137167dfc0..56e06c3d7a 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java @@ -74,7 +74,7 @@ public PerSamplePileupElementTracker(Map> Sample sample = entry.getKey(); AbstractReadBackedPileup pileupBySample = entry.getValue(); pileup.put(sample,pileupBySample.pileupElementTracker); - sampleNames.put(sample.getId(), sample); + sampleNames.put(sample.getID(), sample); } } @@ -105,7 +105,7 @@ public PileupElementTracker getElements(final Collection selectSampl public void addElements(final Sample sample, PileupElementTracker elements) { pileup.put(sample,elements); - sampleNames.put(sample.getId(), sample); + sampleNames.put(sample.getID(), sample); size += elements.size(); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java index 390ed95aeb..61aed7b34e 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java @@ -149,8 +149,8 @@ public void getSamplesWithPropertyTest() { Iterator i = ceuSamples.iterator(); ArrayList sampleNames = new ArrayList(); - sampleNames.add(i.next().getId()); - sampleNames.add(i.next().getId()); + sampleNames.add(i.next().getID()); + sampleNames.add(i.next().getID()); Assert.assertTrue(sampleNames.contains("sampleA")); Assert.assertTrue(sampleNames.contains("sampleB")); } @@ -191,8 +191,8 @@ public void variantContextTest() { // make sure both samples are included Iterator i = set.iterator(); ArrayList sampleNames = new ArrayList(); - sampleNames.add(i.next().getId()); - sampleNames.add(i.next().getId()); + sampleNames.add(i.next().getID()); + sampleNames.add(i.next().getID()); Assert.assertTrue(sampleNames.contains("NA123")); Assert.assertTrue(sampleNames.contains("NA456")); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index 7f9a57f087..ca777200b4 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -55,7 +55,7 @@ public void basicHashTest() { */ @Test() public void specialGettersTest() { - Assert.assertTrue(sampleC.getId().equals("sampleC")); + Assert.assertTrue(sampleC.getID().equals("sampleC")); Assert.assertTrue(sampleC.getPopulation().equals("pop1")); Assert.assertTrue(sampleC.isMale()); Assert.assertFalse(sampleA.isMale()); // sample A doesn't have a gender, so this should be false diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java index d982d54a24..2b9ff71137 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java @@ -168,9 +168,9 @@ public void testGetPileupForSample() { Sample sample2 = new Sample("sample2"); SAMReadGroupRecord readGroupOne = new SAMReadGroupRecord("rg1"); - readGroupOne.setSample(sample1.getId()); + readGroupOne.setSample(sample1.getID()); SAMReadGroupRecord readGroupTwo = new SAMReadGroupRecord("rg2"); - readGroupTwo.setSample(sample2.getId()); + readGroupTwo.setSample(sample2.getID()); SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1,1,1000); header.addReadGroup(readGroupOne); @@ -191,7 +191,7 @@ public void testGetPileupForSample() { Assert.assertEquals(sample1Pileup.size(),1,"Sample 1 pileup has wrong number of elements"); Assert.assertEquals(sample1Pileup.getReads().get(0),read1,"Sample 1 pileup has incorrect read"); - ReadBackedPileup sample2Pileup = pileup.getPileupForSampleName(sample2.getId()); + ReadBackedPileup sample2Pileup = pileup.getPileupForSampleName(sample2.getID()); Assert.assertEquals(sample2Pileup.size(),1,"Sample 2 pileup has wrong number of elements"); Assert.assertEquals(sample2Pileup.getReads().get(0),read2,"Sample 2 pileup has incorrect read"); From c08468eb9d89503d825a8e24277b544f8435e957 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 29 Sep 2011 11:21:50 -0400 Subject: [PATCH 144/363] A couple of updates while trying to get desired R 2.13 compactPDF support. preQC: - For R 2.13 when parsing fingerprints explicitly coercing the text before parsing - Added LOD geom_line() at +/-3 based on Tim's presentation at PM meeting (ppt to go to pipeline wiki asap) - PF_INDEL_RATE of zero replaced with NA - NA's are not "violations" auto filter samples since 0+NA = NA, and subset test only looks for 0 violations - Restored plots for MEAN_READ_LENGTH, BAD_CYCLES, and MEDIAN_INSERT_SIZE by explicitly print()'ing the created plots postQC: - Fixed R 2.13 font scaling by moving size out of aes, except when using highlighting - TODO: Don't know how to scale by aes for highlighting *and* use a smaller overall font size outside aes From 5c9227cf5e9eefc24e3b42d1b8f8dd6f49c54f27 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 11:50:05 -0400 Subject: [PATCH 145/363] Further cleanup of Sample database -- Removing more and more unnecessary code -- Partial removal of type safe Sample usage. On the road to SampleDB only --- .../sting/gatk/GenomeAnalysisEngine.java | 7 +- .../sting/gatk/samples/Sample.java | 87 ++-- .../sting/gatk/samples/SampleDataSource.java | 491 +++--------------- .../sting/gatk/walkers/Walker.java | 5 +- .../beagle/ProduceBeagleInputWalker.java | 3 +- .../phasing/ReadBackedPhasingWalker.java | 6 +- .../qc/CountLociByPopulationWalker.java | 59 --- .../gatk/walkers/qc/CountMalesWalker.java | 4 +- .../sting/utils/ped/PedReader.java | 1 - .../pileup/AbstractReadBackedPileup.java | 34 -- .../pileup/ReadBackedExtendedEventPileup.java | 13 - .../sting/utils/pileup/ReadBackedPileup.java | 14 - .../reads/DownsamplerBenchmark.java | 3 +- .../samples/SampleDataSourceUnitTest.java | 207 -------- .../sting/gatk/samples/SampleUnitTest.java | 60 +-- .../pileup/ReadBackedPileupUnitTest.java | 14 +- 16 files changed, 138 insertions(+), 870 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 38ef2879b8..0501287407 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -46,10 +46,7 @@ import org.broadinstitute.sting.gatk.refdata.utils.RMDIntervalGenerator; import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet; import org.broadinstitute.sting.gatk.walkers.*; -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; -import org.broadinstitute.sting.utils.SequenceDictionaryUtils; +import org.broadinstitute.sting.utils.*; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -1048,7 +1045,7 @@ public SampleDataSource getSampleDB() { * Returns all samples that were referenced in the SAM file */ public Set getSAMFileSamples() { - return sampleDataSource.getSAMFileSamples(); + return sampleDataSource.getSamples(SampleUtils.getSAMFileSamples(getSAMFileHeader())); } public Map getApproximateCommandLineArguments(Object... argumentProviders) { 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 f92533f089..c6fcbbc2a7 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -1,8 +1,6 @@ package org.broadinstitute.sting.gatk.samples; -import org.broadinstitute.sting.utils.exceptions.StingException; - import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -18,12 +16,11 @@ public class Sample implements java.io.Serializable { final private Sample.Gender gender; final private double quantitativePhenotype; final private Sample.Affection affection; - final private String population; final private String ID; final private SampleDataSource dataSource; - private boolean hasSAMFileEntry = false; // true if this sample has an entry in the SAM file - private Map properties = new HashMap(); + // todo -- conditionally add the property map -- should be empty by default + private final Map properties = new HashMap(); public enum Gender { MALE, @@ -46,33 +43,31 @@ public enum Affection { public Sample(final String ID, final SampleDataSource dataSource, final String familyID, final String paternalID, final String maternalID, - final Gender gender, final double quantitativePhenotype, final Affection affection, - final String population) { + final Gender gender, final double quantitativePhenotype, final Affection affection) { this.familyID = familyID; this.paternalID = paternalID; this.maternalID = maternalID; this.gender = gender; this.quantitativePhenotype = quantitativePhenotype; this.affection = affection; - this.population = population; this.ID = ID; this.dataSource = dataSource; } - public Sample(String id, SampleDataSource dataSource) { - this(id, dataSource, - null, null, null, - Gender.UNKNOWN, UNSET_QUANTITIATIVE_TRAIT_VALUE, Affection.UNKNOWN, null); + public Sample(final String ID, final SampleDataSource dataSource, + final String familyID, final String paternalID, final String maternalID, final Gender gender) { + this(ID, dataSource, familyID, paternalID, maternalID, gender, + UNSET_QUANTITIATIVE_TRAIT_VALUE, Affection.UNKNOWN); } - @Deprecated - public boolean hasSAMFileEntry() { - return this.hasSAMFileEntry; + public Sample(final String ID, final SampleDataSource dataSource, final double quantitativePhenotype, final Affection affection) { + this(ID, dataSource, null, null, null, Gender.UNKNOWN, quantitativePhenotype, affection); } - @Deprecated - public void setSAMFileEntry(boolean value) { - this.hasSAMFileEntry = value; + public Sample(String id, SampleDataSource dataSource) { + this(id, dataSource, + null, null, null, + Gender.UNKNOWN, UNSET_QUANTITIATIVE_TRAIT_VALUE, Affection.UNKNOWN); } // ------------------------------------------------------------------------------------- @@ -115,7 +110,7 @@ public double getQuantitativePhenotype() { * @return sample object with relationship mother, if exists, or null */ public Sample getMother() { - return dataSource.getSampleById(maternalID); + return dataSource.getSample(maternalID); } /** @@ -123,7 +118,7 @@ public Sample getMother() { * @return sample object with relationship father, if exists, or null */ public Sample getFather() { - return dataSource.getSampleById(paternalID); + return dataSource.getSample(paternalID); } /** @@ -134,28 +129,10 @@ public Gender getGender() { return gender; } - public String getPopulation() { - return population; - } - public String getFamilyId() { return familyID; } - /** - * @return True if sample is male, false if female, unknown, or null - */ - public boolean isMale() { - return getGender() == Gender.MALE; - } - - /** - * @return True if sample is female, false if male, unknown or null - */ - public boolean isFemale() { - return getGender() == Gender.MALE; - } - // ------------------------------------------------------------------------------------- // // code for working with additional -- none standard -- properites @@ -184,22 +161,20 @@ public boolean hasExtraProperty(String key) { return properties.containsKey(key); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Sample sample = (Sample) o; - - if (hasSAMFileEntry != sample.hasSAMFileEntry) return false; - if (ID != null ? !ID.equals(sample.ID) : sample.ID != null) return false; - if (properties != null ? !properties.equals(sample.properties) : sample.properties != null) return false; - - return true; - } - - @Override - public int hashCode() { - return ID != null ? ID.hashCode() : "".hashCode(); - } +// @Override +// public boolean equals(Object o) { +// if (this == o) return true; +// if (o == null || getClass() != o.getClass()) return false; +// +// Sample sample = (Sample) o; +// if (ID != null ? !ID.equals(sample.ID) : sample.ID != null) return false; +// if (properties != null ? !properties.equals(sample.properties) : sample.properties != null) return false; +// +// return true; +// } +// +// @Override +// public int hashCode() { +// return ID != null ? ID.hashCode() : "".hashCode(); +// } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index f4855b27f8..fec82a71f8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -6,7 +6,6 @@ import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; import java.util.*; @@ -26,394 +25,121 @@ * */ public class SampleDataSource { - - /** - * SAMFileHeader that has been created for this analysis. - */ - private SAMFileHeader header; - /** * This is where Sample objects are stored. Samples are usually accessed by their ID, which is unique, so * this is stored as a HashMap. */ private final HashMap samples = new HashMap(); - /** - * Samples can have "aliases", because sometimes the same sample is referenced by different IDs in different - * datasets. If this is the case, one ID is the "primary ID" and others are "aliases". - * - * This maps ID => primary ID for all samples ID strings - both primary IDs and aliases. - */ - private HashMap sampleAliases = new HashMap(); - /** * Constructor takes both a SAM header and sample files because the two must be integrated. - * @param header SAMFileHeader that has been created for this analysis - * @param sampleFiles Sample files that were included on the command line */ - public SampleDataSource(SAMFileHeader header, List sampleFiles) { - this(); - this.header = header; - // create empty sample object for each sample referenced in the SAM header - for (String sampleName : SampleUtils.getSAMFileSamples(header)) { - if (!hasSample(sampleName)) { - Sample newSample = new Sample(sampleName, this); - samples.put(sampleName, newSample); - } - } - - // add files consecutively - if (sampleFiles != null) { - for (File file : sampleFiles) { - addFile(file); - } - } - } - public SampleDataSource() { samples.put(null, new Sample(null, this)); } + public SampleDataSource(final SAMFileHeader header, final List sampleFiles) { + this(); + addSamples(header); + addSamples(sampleFiles); + } + + // -------------------------------------------------------------------------------- + // + // Functions for adding samples to the DB + // + // TODO: these should be protected, really + // + // -------------------------------------------------------------------------------- + /** * Hallucinates sample objects for all the samples in the SAM file and stores them */ - public void addSamplesFromSAMHeader(SAMFileHeader header) { + public SampleDataSource addSamples(SAMFileHeader header) { for (String sampleName : SampleUtils.getSAMFileSamples(header)) { - if (!hasSample(sampleName)) { + if (getSample(sampleName) == null) { Sample newSample = new Sample(sampleName, this); - newSample.setSAMFileEntry(true); samples.put(sampleName, newSample); } } + return this; + } + + public SampleDataSource addSamples(final List sampleFiles) { + // add files consecutively + for (File file : sampleFiles) { + addSamples(file); + } + return this; } /** * Parse one sample file and integrate it with samples that are already there * Fail quickly if we find any errors in the file */ - public void addFile(File sampleFile) {} -// -// BufferedReader reader; -// try { -// reader = new BufferedReader(new FileReader(sampleFile)); -// } -// catch (IOException e) { -// throw new StingException("Could not open sample file " + sampleFile.getAbsolutePath(), e); -// } -// -// // set up YAML reader - a "Constructor" creates java object from YAML and "Loader" loads the file -// Constructor con = new Constructor(SampleFileParser.class); -// TypeDescription desc = new TypeDescription(SampleFileParser.class); -// desc.putListPropertyType("propertyDefinitions", PropertyDefinition.class); -// desc.putListPropertyType("sampleAliases", SampleAlias.class); -// con.addTypeDescription(desc); -// Yaml yaml = new Yaml(con); -// -// // SampleFileParser stores an object representation of a sample file - this is what we'll parse -// SampleFileParser parser; -// try { -// parser = (SampleFileParser) yaml.load(reader); -// } -// catch (Exception e) { -// throw new StingException("There was a syntactic error with the YAML in sample file " + sampleFile.getAbsolutePath(), e); -// } -// -// // check to see which validation options were built into the file -// boolean restrictProperties = parser.getAllowedProperties() != null; -// boolean restrictRelationships = parser.getAllowedRelationships() != null; -// boolean restrictPropertyValues = parser.getPropertyDefinitions() != null; -// -// // propertyValues stores the values that are allowed for a given property -// HashMap propertyValues = null; -// if (restrictPropertyValues) { -// propertyValues = new HashMap(); -// for (PropertyDefinition def : parser.getPropertyDefinitions()) { -// HashSet set = new HashSet(); -// for (String value : def.getValues()) { -// set.add(value); -// } -// propertyValues.put(def.getProperty(), set); -// } -// } -// -// // make sure the aliases are valid -// validateAliases(parser); -// -// // loop through each sample in the file - a SampleParser stores an object that will become a Sample -// for (SampleParser sampleParser : parser.getSamples()) { -// -// try { -// // step 1: add the sample if it doesn't already exist -// Sample sample = getSampleById(sampleParser.getID()); -// if (sample == null) { -// sample = new Sample(sampleParser.getID()); -// } -// addSample(sample); -// sample.setSampleFileEntry(true); -// -// // step 2: add the properties -// if (sampleParser.getProperties() != null) { -// for (String property : sampleParser.getProperties().keySet()) { -// -// // check that property is allowed -// if (restrictProperties) { -// if (!isPropertyValid(property, parser.getAllowedProperties())) { -// throw new StingException(property + " is an invalid property. It is not included in the list " + -// "of allowed properties."); -// } -// } -// -// // next check that the value is allowed -// if (restrictPropertyValues) { -// if (!isValueAllowed(property, sampleParser.getProperties().get(property), propertyValues)) { -// throw new StingException("The value of property '" + property + "' is invalid. " + -// "It is not included in the list of allowed values for this property."); -// } -// } -// -// // next check that there isn't already a conflicting property there -// if (sample.getProperty(property) != null && -// sample.getProperty(property) != sampleParser.getProperties().get(property)) -// { -// throw new StingException(property + " is a conflicting property!"); -// } -// -// // checks are passed - now add the property! -// saveProperty(sample, property, sampleParser.getProperties().get(property)); -// } -// } -// -// // step 3: add the relationships -// if (sampleParser.getRelationships() != null) { -// for (String relationship : sampleParser.getRelationships().keySet()) { -// String relativeId = sampleParser.getRelationships().get(relationship); -// if (relativeId == null) { -// throw new StingException("The relationship cannot be null"); -// } -// -// // first check that it's not invalid -// if (restrictRelationships) { -// if (!isRelationshipValid(relationship, parser.getAllowedRelationships())) { -// throw new StingException(relationship + " is an invalid relationship"); -// } -// } -// -// // next check that there isn't already a conflicting property there -// if (sample.getRelationship(relationship) != null) { -// if (sample.getRelationship(relationship).getID() != sampleParser.getProperties().get(relationship)) { -// throw new StingException(relationship + " is a conflicting relationship!"); -// } -// // if the relationship is already set - and consistent with what we're reading now - no need to continue -// else { -// continue; -// } -// } -// -// // checks are passed - now save the relationship -// saveRelationship(sample, relationship, relativeId); -// } -// } -// } catch (Exception e) { -// throw new StingException("An error occurred while loading this sample from the sample file: " + -// sampleParser.getID(), e); -// } -// } -// } -// -// private boolean isValueAllowed(String key, Object value, HashMap valuesList) { -// -// // if the property values weren't specified for this property, then any value is okay -// if (!valuesList.containsKey(key)) { -// return true; -// } -// -// // if this property has enumerated values, it must be a string -// else if (value.getClass() != String.class) -// return false; -// -// // is the value specified or not? -// else if (!valuesList.get(key).contains(value)) -// return false; -// -// return true; -// } -// -// /** -// * Makes sure that the aliases are valid -// * Checks that 1) no string is used as both a main ID and an alias; -// * 2) no alias is used more than once -// * @param parser -// */ -// private void validateAliases(SampleFileParser parser) { -// -// // no aliases sure validate -// if (parser.getSampleAliases() == null) -// return; -// -// HashSet mainIds = new HashSet(); -// HashSet otherIds = new HashSet(); -// -// for (SampleAlias sampleAlias : parser.getSampleAliases()) { -// mainIds.add(sampleAlias.getMainId()); -// for (String otherId : sampleAlias.getOtherIds()) { -// if (mainIds.contains(otherId)) -// throw new StingException(String.format("The aliases in your sample file are invalid - the alias %s cannot " + -// "be both a main ID and an other ID", otherId)); -// -// if (!otherIds.add(otherId)) -// throw new StingException(String.format("The aliases in your sample file are invalid - %s is listed as an " + -// "alias more than once.", otherId)); -// } -// } -// } -// -// private boolean isPropertyValid(String property, String[] allowedProperties) { -// -// // is it a special property that is always allowed? -// for (String allowedProperty : specialProperties) { -// if (property.equals(allowedProperty)) -// return true; -// } -// -// // is it in the allowed properties list? -// for (String allowedProperty : allowedProperties) { -// if (property.equals(allowedProperty)) -// return true; -// } -// -// return false; -// } -// -// private boolean isRelationshipValid(String relationship, String[] allowedRelationships) { -// -// // is it a special relationship that is always allowed? -// for (String allowedRelationship : specialRelationships) { -// if (relationship.equals(allowedRelationship)) -// return true; -// } -// -// // is it in the allowed properties list? -// for (String allowedRelationship : allowedRelationships) { -// if (relationship.equals(allowedRelationship)) -// return true; -// } -// -// return false; -// } -// -// /** -// * Saves a property as the correct type -// * @param key property key -// * @param value property value, as read from YAML parser -// * @return property value to be stored -// */ -// private void saveProperty(Sample sample, String key, Object value) { -// -// // convert gender to the right type, if it was stored as a String -// if (key.equals("gender")) { -// if (((String) value).toLowerCase().equals("male")) { -// value = Sample.Gender.MALE; -// } -// else if (((String) value).toLowerCase().equals("female")) { -// value = Sample.Gender.FEMALE; -// } -// else if (((String) value).toLowerCase().equals("unknown")) { -// value = Sample.Gender.UNKNOWN; -// } -// else if (value != null) { -// throw new StingException("'gender' property must be male, female, or unknown."); -// } -// } -// try { -// sample.setProperty(key, value); -// } -// catch (Exception e) { -// throw new StingException("Could not save property " + key, e); -// } -// } -// -// /** -// * Saves a relationship as the correct type -// * @param key relationship key -// * @param relativeId sample ID string of the relative -// * @return relationship value to be stored -// */ -// private void saveRelationship(Sample sample, String key, String relativeId) { -// -// // get the reference that we'll store as the value -// Sample relative = getSampleById(relativeId); -// -// // create sample object for the relative, if necessary -// if (relative == null) { -// relative = new Sample(relativeId); -// addSample(relative); -// } -// sample.setRelationship(key, relative); -// } - - - - /** - * Filter a sample name in case it is an alias - * @param sampleId to be filtered - * @return ID of sample that stores data for this alias - */ - private String aliasFilter(String sampleId) { - if (!sampleAliases.containsKey(sampleId)) - return sampleId; - else - return sampleAliases.get(sampleId); + public SampleDataSource addSamples(File sampleFile) { + return this; } /** * Add a sample to the collection * @param sample to be added */ - private void addSample(Sample sample) { + private SampleDataSource addSample(Sample sample) { samples.put(sample.getID(), sample); + return this; } - /** - * Check if sample with this ID exists - * Note that this will return true if name passed in is an alias - * @param id ID of sample to be checked - * @return true if sample exists; false if not - */ - public boolean hasSample(String id) { - return samples.get(aliasFilter(id)) != null; - } + // -------------------------------------------------------------------------------- + // + // Functions for getting a sample from the DB + // + // -------------------------------------------------------------------------------- /** * Get a sample by its ID * If an alias is passed in, return the main sample object * @param id - * @return sample Object with this ID + * @return sample Object with this ID, or null if this does not exist */ - public Sample getSampleById(String id) { - return samples.get(aliasFilter(id)); + public Sample getSample(String id) { + return samples.get(id); } /** - * Get the sample for a given read group - * Must first look up ID for read group - * @param readGroup of sample - * @return sample object with ID from the read group + * + * @param read + * @return sample Object with this ID, or null if this does not exist */ - public Sample getSampleByReadGroup(SAMReadGroupRecord readGroup) { - String nameFromReadGroup = readGroup.getSample(); - return getSampleById(nameFromReadGroup); + public Sample getSample(final SAMRecord read) { + return getSample(read.getReadGroup()); } /** - * Get a sample for a given read - * Must first look up read group, and then sample ID for that read group - * @param read of sample - * @return sample object of this read + * + * @param rg + * @return sample Object with this ID, or null if this does not exist */ - public Sample getSampleByRead(SAMRecord read) { - return getSampleByReadGroup(read.getReadGroup()); + public Sample getSample(final SAMReadGroupRecord rg) { + return getSample(rg.getSample()); } + /** + * @param g Genotype + * @return sample Object with this ID, or null if this does not exist + */ + public Sample getSample(final Genotype g) { + return getSample(g.getSampleName()); + } + + // -------------------------------------------------------------------------------- + // + // Functions for accessing samples in the DB + // + // -------------------------------------------------------------------------------- + + + /** * Get number of sample objects * @return size of samples map @@ -469,10 +195,10 @@ public Set getSamples() { * @return Corresponding set of samples */ public Set getSamples(Collection sampleNameList) { - HashSet samples = new HashSet(); + HashSet samples = new HashSet(); for (String name : sampleNameList) { try { - samples.add(getSampleById(name)); + samples.add(getSample(name)); } catch (Exception e) { throw new StingException("Could not get sample with the following ID: " + name, e); @@ -480,91 +206,4 @@ public Set getSamples(Collection sampleNameList) { } return samples; } - - /** - * Returns a set of samples that have any value (which could be null) for a given property - * @param key Property key - * @return Set of samples with the property - */ - public Set getSamplesWithProperty(String key) { - HashSet toReturn = new HashSet(); - for (Sample s : samples.values()) { - if (s.hasExtraProperty(key)) - toReturn.add(s); - } - return toReturn; - } - - /** - * Returns a set of samples that have a property with a certain value - * Value must be a string for now - could add a similar method for matching any objects in the future - * - * @param key Property key - * @param value String property value - * @return Set of samples that match key and value - */ - public Set getSamplesWithProperty(String key, String value) { - Set toReturn = getSamplesWithProperty(key); - for (Sample s : toReturn) { - if (!s.getExtraPropertyValue(key).equals(value)) - toReturn.remove(s); - } - return toReturn; - } - - public Sample getOrCreateSample(String id) { - Sample sample = getSampleById(id); - if (sample == null) { - sample = new Sample(id, this); - addSample(sample); - } - return sample; - } - - /** - * Returns all samples that were referenced in the SAM file - */ - public Set getSAMFileSamples() { - Set toReturn = new HashSet(); - for (Sample sample : samples.values()) { - if (sample.hasSAMFileEntry()) - toReturn.add(sample); - } - return toReturn; - } - - /** - * Returns a set of sample objects for the sample names in a variant context - * - * @param context Any variant context - * @return a set of the sample objects - */ - public Set getSamplesByVariantContext(VariantContext context) { - Set samples = new HashSet(); - for (String sampleName : context.getSampleNames()) { - samples.add(getOrCreateSample(sampleName)); - } - return samples; - } - - - /** - * Return a subcontext restricted to samples with a given property key/value - * Gets the sample names from key/value and relies on VariantContext.subContextFromGenotypes for the filtering - * @param context VariantContext to filter - * @param key property key - * @param value property value (must be string) - * @return subcontext - */ - public VariantContext subContextFromSampleProperty(VariantContext context, String key, String value) { - - Set samplesWithProperty = new HashSet(); - for (String sampleName : context.getSampleNames()) { - Sample s = samples.get(sampleName); - if (s != null && s.hasExtraProperty(key) && s.getExtraPropertyValue(key).equals(value)) - samplesWithProperty.add(sampleName); - } - Map genotypes = context.getGenotypes(samplesWithProperty); - return context.subContextFromGenotypes(genotypes.values()); - } } 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 ef791f12f9..f67dace2cc 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java @@ -36,7 +36,6 @@ import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.help.DocumentedGATKFeature; -import org.broadinstitute.sting.utils.help.GenericDocumentationHandler; import java.util.List; @@ -93,8 +92,8 @@ protected SampleDataSource getSampleDB() { return getToolkit().getSampleDB(); } - protected Sample getSampleByID(final String id) { - return getToolkit().getSampleDB().getSampleById(id); + protected Sample getSample(final String id) { + return getToolkit().getSampleDB().getSample(id); } /** 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 ecc4ad7935..cdf1913f75 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 @@ -31,6 +31,7 @@ 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.gatk.walkers.variantrecalibration.VQSRCalibrationCurve; import org.broadinstitute.sting.utils.GenomeLoc; @@ -247,7 +248,7 @@ public void writeBeagleOutput(VariantContext preferredVC, VariantContext otherVC Map preferredGenotypes = preferredVC.getGenotypes(); Map otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; for ( String sample : samples ) { - boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSampleByID(sample).isMale(); + boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSample(sample).getGender() == Sample.Gender.MALE; Genotype genotype; boolean isValidation; 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 a4729371ab..bbbdf5f1a3 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 @@ -1095,14 +1095,14 @@ else if (alignment.hasExtendedEventPileup()) { // filter the read-base pileup based on min base and mapping qualities: pileup = pileup.getBaseAndMappingFilteredPileup(MIN_BASE_QUALITY_SCORE, MIN_MAPPING_QUALITY_SCORE); if (pileup != null) { - for (Sample sample : pileup.getSamples()) { - ReadBackedPileup samplePileup = pileup.getPileupForSample(sample); + for (final String sample : pileup.getSampleNames()) { + ReadBackedPileup samplePileup = pileup.getPileupForSampleName(sample); ReadBasesAtPosition readBases = new ReadBasesAtPosition(); for (PileupElement p : samplePileup) { if (!p.isDeletion()) // IGNORE deletions for now readBases.putReadBase(p); } - sampleReadBases.put(sample.getID(), readBases); + sampleReadBases.put(sample, readBases); } } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java deleted file mode 100644 index bdff128c8b..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountLociByPopulationWalker.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.broadinstitute.sting.gatk.walkers.qc; - -import net.sf.samtools.SAMRecord; -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.LocusWalker; -import org.broadinstitute.sting.gatk.walkers.TreeReducible; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Extends locus walker to print how many reads there are at each locus, by population - */ -public class CountLociByPopulationWalker extends LocusWalker implements TreeReducible { - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - - // in this HashMap, we'll keep count of how many - HashMap count = new HashMap(); - - ArrayList reads = (ArrayList) context.getBasePileup().getReads(); - - for (SAMRecord read : reads) { - - // get the sample - Sample sample = getSampleDB().getSampleByRead(read); - if (sample == null) - return 1; - - if (!count.containsKey(sample.getPopulation())) { - count.put(sample.getPopulation(), 1); - } - count.put(sample.getPopulation(), count.get(sample.getPopulation()) + 1); - } - - System.out.println("\n\n\n***** LOCUS: " + ref.getLocus().toString() + " *****"); - for (String population : count.keySet()) { - System.out.println(String.format("%s | %d", population, count.get(population))); - } - - return 1; - } - - public Long reduceInit() { return 0l; } - - public Long reduce(Integer value, Long sum) { - return value + sum; - } - - /** - * Reduces two subtrees together. In this case, the implementation of the tree reduce - * is exactly the same as the implementation of the single reduce. - */ - public Long treeReduce(Long lhs, Long rhs) { - return lhs + rhs; - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java index 2d89a7d442..f2c035c3cf 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java @@ -16,8 +16,8 @@ @Requires({DataSource.READS, DataSource.REFERENCE}) public class CountMalesWalker extends ReadWalker { public Integer map(ReferenceContext ref, SAMRecord read, ReadMetaDataTracker tracker) { - Sample sample = getSampleDB().getSampleByRead(read); - return sample.isMale() ? 1 : 0; + Sample sample = getSampleDB().getSample(read); + return sample.getGender() == Sample.Gender.MALE ? 1 : 0; } public Integer reduceInit() { return 0; } diff --git a/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java b/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java index 524ba74952..4d282d8218 100644 --- a/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java @@ -228,7 +228,6 @@ public List getRecords() { public void fillSampleDB(SampleDataSource db) { for ( final PedRecord rec : getRecords() ) { - Sample s = db.getOrCreateSample(rec.individualID); } } } diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java index 3bc325cea5..38ffcae8fd 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java @@ -570,16 +570,6 @@ public Collection getSampleNames() { } } - @Override - public Collection getSamples() { - if(!(pileupElementTracker instanceof PerSamplePileupElementTracker)) { - throw new StingException("Must be an instance of PerSampleElementTracker"); - } - PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; - return tracker.getSamples(); - } - - /** * Returns a pileup randomly downsampled to the desiredCoverage. * @@ -688,30 +678,6 @@ public RBP getPileupForSampleName(String sampleName) { } } - @Override - public RBP getPileupForSample(Sample sample) { - if(pileupElementTracker instanceof PerSamplePileupElementTracker) { - PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; - PileupElementTracker filteredElements = tracker.getElements(sample); - return filteredElements != null ? (RBP)createNewPileup(loc,filteredElements) : null; - } - else { - UnifiedPileupElementTracker filteredTracker = new UnifiedPileupElementTracker(); - for(PE p: pileupElementTracker) { - SAMRecord read = p.getRead(); - if(sample != null) { - if(read.getReadGroup() != null && sample.getID().equals(read.getReadGroup().getSample())) - filteredTracker.add(p); - } - else { - if(read.getReadGroup() == null || read.getReadGroup().getSample() == null) - filteredTracker.add(p); - } - } - return filteredTracker.size()>0 ? (RBP)createNewPileup(loc,filteredTracker) : null; - } - } - // -------------------------------------------------------- // // iterators diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java index 4e29be9348..8dd2394cf6 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java @@ -123,19 +123,6 @@ public interface ReadBackedExtendedEventPileup extends ReadBackedPileup { */ public Collection getSampleNames(); - /** - * Gets a list of all the samples stored in this pileup. - * @return List of samples in this pileup. - */ - public Collection getSamples(); - - /** - * Gets the particular subset of this pileup with the given sample name. - * @param sample Name of the sample to use. - * @return A subset of this pileup containing only reads with the given sample. - */ - public ReadBackedExtendedEventPileup getPileupForSample(Sample sample); - public Iterable toExtendedIterable(); /** diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java index 449ead56e6..b76619cd73 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java @@ -137,13 +137,6 @@ public interface ReadBackedPileup extends Iterable, HasGenomeLoca */ public ReadBackedPileup getPileupForLane(String laneID); - - /** - * Gets a collection of all the samples stored in this pileup. - * @return Collection of samples in this pileup. - */ - public Collection getSamples(); - /** * Gets a collection of *names* of all the samples stored in this pileup. * @return Collection of names @@ -165,13 +158,6 @@ public interface ReadBackedPileup extends Iterable, HasGenomeLoca * @return A subset of this pileup containing only reads with the given sample. */ public ReadBackedPileup getPileupForSampleName(String sampleName); - - /** - * Gets the particular subset of this pileup with the given sample. - * @param sample Sample to use. - * @return A subset of this pileup containing only reads with the given sample. - */ - public ReadBackedPileup getPileupForSample(Sample sample); /** * Simple useful routine to count the number of deletion bases in this pileup 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 17700cf7c1..9f3c2bb294 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 @@ -85,8 +85,7 @@ public void timeDownsampling(int reps) { (byte)0); GenomeLocParser genomeLocParser = new GenomeLocParser(reader.getFileHeader().getSequenceDictionary()); - SampleDataSource sampleDataSource = new SampleDataSource(); - sampleDataSource.addSamplesFromSAMHeader(reader.getFileHeader()); + SampleDataSource sampleDataSource = new SampleDataSource().addSamples(reader.getFileHeader()); // Filter unmapped reads. TODO: is this always strictly necessary? Who in the GATK normally filters these out? Iterator readIterator = new FilteringIterator(reader.iterator(),new UnmappedReadFilter()); diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java index 61aed7b34e..ccbfd5c990 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java @@ -20,7 +20,6 @@ * Time: 8:21:00 AM */ public class SampleDataSourceUnitTest extends BaseTest { - // this empty header used to instantiate sampledatasource objects private static SAMFileHeader header = new SAMFileHeader(); @@ -32,210 +31,4 @@ public class SampleDataSourceUnitTest extends BaseTest { public void loadSAMSamplesTest() { SampleDataSource s = new SampleDataSource(header, null); } - - // tests that a basic sample with relationships loads correctly - // Note that this is the only test for family relationships - we may want to expand this - @Test() - public void basicLoadSampleFileTest() { - File sampleFile = new File(sampleFilesDir + "basicSampleFile.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - Assert.assertTrue(s.sampleCount() == 5); - Sample sampleA = s.getSampleById("sampleA"); - Sample sampleB = s.getSampleById("sampleB"); - Assert.assertTrue(sampleB.getMother() == sampleA); - Assert.assertTrue(s.getChildren(sampleA).contains(sampleB)); - Set family = s.getFamily("family1"); - Assert.assertTrue(family.size() == 2); - Assert.assertTrue(family.contains(sampleA)); - Assert.assertTrue(family.contains(sampleB)); - } - - // but that file should fail if it has an extra character in it... - @Test(expectedExceptions=StingException.class) - public void loadInvalidSampleExtraCharText() { - File sampleFile = new File(sampleFilesDir + "invalidSyntaxExtraChar.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - } - - // ...or a typo... - @Test(expectedExceptions=StingException.class) - public void loadInvalidSampleTypoText() { - File sampleFile = new File(sampleFilesDir + "invalidSyntaxTypo.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - - } - - // ...or an extra unrecognized array - @Test(expectedExceptions=StingException.class) - public void loadInvalidSampleExtraArrayText() { - File sampleFile = new File(sampleFilesDir + "invalidSyntaxExtraArray.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - } - - // make sure aliases work - @Test(expectedExceptions=StingException.class) - public void sampleAliasText() { - File sampleFile = new File(sampleFilesDir + "basicSampleFileWithAlias.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - // this file has two samples, but one has an alias. let's make sure that checks out... - Assert.assertTrue(s.sampleCount() == 3); - Assert.assertTrue(s.getSampleById("sampleA") == s.getSampleById("sampleC")); - } - - // error is thrown if property is included that's not in properties array - @Test(expectedExceptions=StingException.class) - public void unallowedPropertySampleTest() { - File sampleFile = new File(sampleFilesDir + "basicSampleFileUnallowedProperty.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - } - - // same as above, with relationship - @Test(expectedExceptions=StingException.class) - public void unallowedRelationshipSampleTest() { - File sampleFile = new File(sampleFilesDir + "basicSampleFileUnallowedRelationship.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - } - - // two sample files - @Test() - public void twoSampleFilesTest() { - File sampleFile = new File(sampleFilesDir + "basicSampleFile.yaml"); - File secondFile = new File(sampleFilesDir + "basicSampleFileExt.yaml"); - ArrayList files = new ArrayList(); - files.add(sampleFile); - files.add(secondFile); - SampleDataSource s = new SampleDataSource(header, files); - Assert.assertTrue(s.getSampleById("sampleA").getProperty("propC").equals("valC")); - Assert.assertTrue(s.getSampleById("sampleA").getProperty("propA").equals("valA")); - } - - // two sample files, with contradictory properties - @Test(expectedExceptions=StingException.class) - public void twoContradictorySampleFilesTest() { - File sampleFile = new File(sampleFilesDir + "basicSampleFile.yaml"); - File secondFile = new File(sampleFilesDir + "basicSampleFileInvalidExt.yaml"); - ArrayList files = new ArrayList(); - files.add(sampleFile); - files.add(secondFile); - SampleDataSource s = new SampleDataSource(header, files); - } - - // three sample files - @Test() - public void threeSamplesTest() { - File sampleFile = new File(sampleFilesDir + "basicSampleFile.yaml"); - ArrayList files = new ArrayList(); - files.add(sampleFile); - files.add(new File(sampleFilesDir + "basicSampleFileExt.yaml")); - files.add(new File(sampleFilesDir + "basicSampleFileExt2.yaml")); - SampleDataSource s = new SampleDataSource(header, files); - Assert.assertTrue(s.sampleCount() == 6); - Assert.assertTrue(s.getSampleById("sampleE").getProperty("propC").equals("valC")); - Assert.assertTrue(s.getSampleById("sampleA").getProperty("propA").equals("valA")); - } - - /** - * testing getSamplesWithProperty - * in this file there are 5 samples - 2 with population "CEU", 1 with population "ABC", 1 with no population, - * and then the default null sample - */ - @Test() - public void getSamplesWithPropertyTest() { - File sampleFile = new File(sampleFilesDir + "sampleFileWithProperties.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - Assert.assertTrue(s.sampleCount() == 5); - Set ceuSamples = s.getSamplesWithProperty("population", "CEU"); - Assert.assertTrue(ceuSamples.size() == 2); - - Iterator i = ceuSamples.iterator(); - ArrayList sampleNames = new ArrayList(); - sampleNames.add(i.next().getID()); - sampleNames.add(i.next().getID()); - Assert.assertTrue(sampleNames.contains("sampleA")); - Assert.assertTrue(sampleNames.contains("sampleB")); - } - - // make sure we can import data types other than Strings - @Test() - public void sampleTestPropertyType() { - File sampleFile = new File(sampleFilesDir + "sampleFileOtherTypes.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - Sample sample = s.getSampleById("sampleA"); - Assert.assertTrue(sample.getProperty("a").getClass() == Integer.class); - Assert.assertTrue(sample.getProperty("b").getClass() == String.class); - Assert.assertTrue(sample.getProperty("c").getClass() == Double.class); - Assert.assertTrue(sample.getProperty("b").getClass() == String.class); - } - - /** - * check that getSamplesFromVariantContext works - * create a variant context with two sample names, and make sure the right samples are there - */ - @Test() - public void variantContextTest() { - SampleDataSource s = new SampleDataSource(header, null); - List alleleCollection = new ArrayList(); - Allele a1 = Allele.create("A", true); - alleleCollection.add(a1); - - Set genotypeCollection = new HashSet(); - genotypeCollection.add(new Genotype("NA123", alleleCollection)); - genotypeCollection.add(new Genotype("NA456", alleleCollection)); - - VariantContext v = new VariantContext("contextName", "chr1", 1, 1, alleleCollection, genotypeCollection); - - // make sure the set that's returned is the right size - HashSet set = (HashSet) s.getSamplesByVariantContext(v); - Assert.assertTrue(set.size() == 2); - - // make sure both samples are included - Iterator i = set.iterator(); - ArrayList sampleNames = new ArrayList(); - sampleNames.add(i.next().getID()); - sampleNames.add(i.next().getID()); - Assert.assertTrue(sampleNames.contains("NA123")); - Assert.assertTrue(sampleNames.contains("NA456")); - } - - /** - * checking subContextFromSampleProperty - */ - - /** - * check that subContextFromSampleProperty works - * create a variant context with four sample names, make sure that it filters correctly to 2 - */ - @Test() - public void subContextFromSamplePropertyTest() { - - File sampleFile = new File(sampleFilesDir + "sampleFileWithProperties.yaml"); - SampleDataSource s = new SampleDataSource(header, makeFileList(sampleFile)); - Assert.assertTrue(s.sampleCount() == 5); - - List alleleCollection = new ArrayList(); - Allele a1 = Allele.create("A", true); - alleleCollection.add(a1); - - Set genotypeCollection = new HashSet(); - genotypeCollection.add(new Genotype("NA123", alleleCollection)); - genotypeCollection.add(new Genotype("sampleA", alleleCollection)); - genotypeCollection.add(new Genotype("sampleB", alleleCollection)); - genotypeCollection.add(new Genotype("sampleC", alleleCollection)); - - VariantContext v = new VariantContext("contextName", "chr1", 1, 1, alleleCollection, genotypeCollection); - VariantContext subContext = s.subContextFromSampleProperty(v, "population", "CEU"); - - Assert.assertTrue(subContext.getSampleNames().contains("sampleA")); - Assert.assertTrue(subContext.getSampleNames().contains("sampleA")); - Assert.assertTrue(subContext.getSampleNames().size() == 2); - - } - - - // we create lots of single item lists... - private ArrayList makeFileList(File file) { - ArrayList a = new ArrayList(); - a.add(file); - return a; - } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index ca777200b4..e8d1772b8f 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -13,41 +13,26 @@ * Time: 8:21:00 AM */ public class SampleUnitTest extends BaseTest { - - static Sample sampleA; - static Sample sampleA1; - static Sample sampleB; - static Sample sampleC; + SampleDataSource db; + static Sample fam1A, fam1B, fam1C; + static Sample s1, s2; + static Sample trait1, trait2, trait3, trait4; @BeforeClass public void init() { - sampleA = new Sample("sampleA"); - sampleA.setProperty("uniqueProperty", "uniqueValue"); - sampleA1 = new Sample("sampleA"); - sampleA1.setProperty("uniqueProperty", "uniqueValue"); - sampleB = new Sample("sampleB"); - sampleC = new Sample("sampleC"); - sampleC.setProperty("population", "pop1"); - sampleC.setProperty("gender", Sample.Gender.MALE); - } + db = new SampleDataSource(); - /** - * Testing equality - */ - @Test() - public void equalsTest() { - Assert.assertTrue(sampleA.equals(sampleA1)); - Assert.assertFalse(sampleA == sampleA1); - Assert.assertFalse(sampleA.equals(sampleB)); - } + fam1A = new Sample("1A", db, "fam1", "1B", "1C", Sample.Gender.UNKNOWN); + fam1B = new Sample("1B", db, "fam1", null, null, Sample.Gender.MALE); + fam1C = new Sample("1C", db, "fam1", null, null, Sample.Gender.FEMALE); - /** - * And hash - */ - @Test() - public void basicHashTest() { - Assert.assertFalse(sampleA.hashCode() == sampleB.hashCode()); - Assert.assertTrue(sampleA.hashCode() == sampleA1.hashCode()); + s1 = new Sample("s1", db); + s2 = new Sample("s2", db); + + trait1 = new Sample("t1", db, Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE, Sample.Affection.AFFECTED); + trait2 = new Sample("t2", db, Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE, Sample.Affection.UNAFFECTED); + trait3 = new Sample("t3", db, Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE, Sample.Affection.UNKNOWN); + trait4 = new Sample("t4", db, 1.0, Sample.Affection.QUANTITATIVE); } /** @@ -55,10 +40,15 @@ public void basicHashTest() { */ @Test() public void specialGettersTest() { - Assert.assertTrue(sampleC.getID().equals("sampleC")); - Assert.assertTrue(sampleC.getPopulation().equals("pop1")); - Assert.assertTrue(sampleC.isMale()); - Assert.assertFalse(sampleA.isMale()); // sample A doesn't have a gender, so this should be false + // todo -- test for sample with extra properties, like population +// Assert.assertTrue(sampleC.getID().equals("sampleC")); +// Assert.assertTrue(sampleC.getPopulation().equals("pop1")); } -} + @Test() + public void testGenders() { + Assert.assertTrue(fam1A.getGender() == Sample.Gender.UNKNOWN); + Assert.assertTrue(fam1B.getGender() == Sample.Gender.MALE); + Assert.assertTrue(fam1C.getGender() == Sample.Gender.FEMALE); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java index 2b9ff71137..7b1ee97681 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java @@ -142,8 +142,8 @@ public void testSplitBySample() { Arrays.asList(read2,read4), Arrays.asList(1,1)); Map sampleToPileupMap = new HashMap(); - sampleToPileupMap.put(new Sample(readGroupOne.getSample()),sample1Pileup); - sampleToPileupMap.put(new Sample(readGroupTwo.getSample()),sample2Pileup); + sampleToPileupMap.put(new Sample(readGroupOne.getSample(), null),sample1Pileup); + sampleToPileupMap.put(new Sample(readGroupTwo.getSample(), null),sample2Pileup); ReadBackedPileup compositePileup = new ReadBackedPileupImpl(null,sampleToPileupMap); @@ -164,8 +164,8 @@ public void testSplitBySample() { @Test public void testGetPileupForSample() { - Sample sample1 = new Sample("sample1"); - Sample sample2 = new Sample("sample2"); + Sample sample1 = new Sample("sample1", null); + Sample sample2 = new Sample("sample2", null); SAMReadGroupRecord readGroupOne = new SAMReadGroupRecord("rg1"); readGroupOne.setSample(sample1.getID()); @@ -187,15 +187,11 @@ public void testGetPileupForSample() { ReadBackedPileup pileup = new ReadBackedPileupImpl(null,sampleToPileupMap); - ReadBackedPileup sample1Pileup = pileup.getPileupForSample(sample1); - Assert.assertEquals(sample1Pileup.size(),1,"Sample 1 pileup has wrong number of elements"); - Assert.assertEquals(sample1Pileup.getReads().get(0),read1,"Sample 1 pileup has incorrect read"); - ReadBackedPileup sample2Pileup = pileup.getPileupForSampleName(sample2.getID()); Assert.assertEquals(sample2Pileup.size(),1,"Sample 2 pileup has wrong number of elements"); Assert.assertEquals(sample2Pileup.getReads().get(0),read2,"Sample 2 pileup has incorrect read"); - ReadBackedPileup missingSamplePileup = pileup.getPileupForSample(new Sample("missing")); + ReadBackedPileup missingSamplePileup = pileup.getPileupForSampleName("missing"); Assert.assertNull(missingSamplePileup,"Pileup for sample 'missing' should be null but isn't"); missingSamplePileup = pileup.getPileupForSampleName("not here"); From 5043d76c3d308d45db816cfb685fe14085d41acb Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 12:16:34 -0400 Subject: [PATCH 146/363] Removing more bad uses of SampleDataSource creation --- .../sting/utils/MendelianViolation.java | 26 ------------------- .../samples/SampleDataSourceUnitTest.java | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index a87a73a2d3..a89bad2fdc 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -1,6 +1,5 @@ package org.broadinstitute.sting.utils; -import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Genotype; @@ -94,31 +93,6 @@ public MendelianViolation(Sample sample, double minGenotypeQualityP) { minGenotypeQuality = minGenotypeQualityP; } - - /** - * The most common constructor to be used when give a YAML file with the relationships to the engine with the -SM option. - * @param engine - The GATK engine, use getToolkit(). That's where the sample information is stored. - * @param minGenotypeQualityP - the minimum phred scaled genotype quality score necessary to asses mendelian violation - */ - public MendelianViolation(GenomeAnalysisEngine engine, double minGenotypeQualityP) { - boolean gotSampleInformation = false; - Collection samples = engine.getSampleDB().getSamples(); - // Iterate through all samples in the sample_metadata file but we really can only take one. - for (Sample sample : samples) { - if (sample.getMother() != null && sample.getFather() != null) { - sampleMom = sample.getMother().getID(); - sampleDad = sample.getFather().getID(); - sampleChild = sample.getID(); - minGenotypeQuality = minGenotypeQualityP; - gotSampleInformation = true; - break; // we can only deal with one trio information - } - } - if (!gotSampleInformation) - throw new UserException("YAML file has no sample with relationship information (mother/father)"); - } - - /** * 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 diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java index ccbfd5c990..3d40d4de8f 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java @@ -29,6 +29,6 @@ public class SampleDataSourceUnitTest extends BaseTest { // make sure samples are created from the SAM file correctly @Test() public void loadSAMSamplesTest() { - SampleDataSource s = new SampleDataSource(header, null); + SampleDataSource s = new SampleDataSource(header, Collections.emptyList()); } } From 9536845e3544fb5323c740216a0080009b8e61d9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 12:20:07 -0400 Subject: [PATCH 147/363] Cleaning up unused code in MV --- .../sting/utils/MendelianViolation.java | 50 +------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index a89bad2fdc..e62a7e512f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -16,9 +16,6 @@ * Time: 12:38 PM */ public class MendelianViolation { - - - String sampleMom; String sampleDad; String sampleChild; @@ -31,22 +28,15 @@ public class MendelianViolation { private static Pattern FAMILY_PATTERN = Pattern.compile("(.*)\\+(.*)=(.*)"); - 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 }; - - public String getSampleMom() { return sampleMom; } - public String getSampleDad() { return sampleDad; } - public String getSampleChild() { return sampleChild; } - public double getMinGenotypeQuality() { return minGenotypeQuality; } @@ -130,7 +120,7 @@ public boolean setAlleles (VariantContext vc) * @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) + public boolean isViolation(VariantContext vc) { return setAlleles(vc) && isViolation(); } @@ -144,42 +134,4 @@ public boolean isViolation() { return false; return true; } - - /** - * @return the likelihood ratio for a mendelian violation - */ - public double violationLikelihoodRatio(VariantContext vc) { - double[] logLikAssignments = new double[27]; - // the matrix to set up is - // MOM DAD CHILD - // |- AA - // AA AA | AB - // |- BB - // |- AA - // 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(); - int offset = 0; - for ( int oMom = 0; oMom < 3; oMom++ ) { - for ( int oDad = 0; oDad < 3; oDad++ ) { - for ( int oChild = 0; oChild < 3; oChild ++ ) { - logLikAssignments[offset++] = momGL[oMom] + dadGL[oDad] + childGL[oChild]; - } - } - } - double[] mvLiks = new double[12]; - double[] nonMVLiks = new double[15]; - for ( int i = 0; i < 12; i ++ ) { - mvLiks[i] = logLikAssignments[mvOffsets[i]]; - } - - for ( int i = 0; i < 15; i++) { - nonMVLiks[i] = logLikAssignments[nonMVOffsets[i]]; - } - - return MathUtils.log10sumLog10(mvLiks) - MathUtils.log10sumLog10(nonMVLiks); - } } From 4086fa768fee77771fba45d6b9b0c4de000d17d0 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 29 Sep 2011 12:18:18 -0400 Subject: [PATCH 148/363] Disabling all ReadClipperUnitTests --- .../sting/utils/clipreads/ReadClipperUnitTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 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 1415379db2..d8695cf383 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -62,7 +62,7 @@ public void init() { readClipper = new ReadClipper(read); } - @Test + @Test ( enabled = false ) public void testHardClipBothEndsByReferenceCoordinates() { logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); @@ -76,7 +76,7 @@ public void testHardClipBothEndsByReferenceCoordinates() { } - @Test + @Test ( enabled = false ) public void testHardClipByReadCoordinates() { logger.warn("Executing testHardClipByReadCoordinates"); @@ -109,7 +109,7 @@ public void testHardClipByReadCoordinates() { } - @Test + @Test ( enabled = false ) public void testHardClipByReferenceCoordinates() { logger.warn("Executing testHardClipByReferenceCoordinates"); @@ -142,7 +142,7 @@ public void testHardClipByReferenceCoordinates() { } - @Test + @Test ( enabled = false ) public void testHardClipByReferenceCoordinatesLeftTail() { logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); @@ -163,7 +163,7 @@ public void testHardClipByReferenceCoordinatesLeftTail() { } - @Test + @Test ( enabled = false ) public void testHardClipByReferenceCoordinatesRightTail() { logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); @@ -184,7 +184,7 @@ public void testHardClipByReferenceCoordinatesRightTail() { } - @Test + @Test ( enabled = false ) public void testHardClipLowQualEnds() { logger.warn("Executing testHardClipByReferenceCoordinates"); From 21c4abdd36bed69f4b72d75fff3e7249e1bae92a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 29 Sep 2011 12:19:03 -0400 Subject: [PATCH 149/363] Disabling all SlidingReadUnitTests From d62f2f33bc27d370e9c457866077ffc6106699d3 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 29 Sep 2011 11:23:12 -0400 Subject: [PATCH 150/363] Added indel specific context size parameter Parameter was added to the framework but implementing the functionality is pending. From a5e75cd14cf7a8b6d3c4c8271302468fc5f256b4 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 29 Sep 2011 12:54:18 -0400 Subject: [PATCH 151/363] Outputting both consensus base qualities and counts The base qualities of a consensus reads are now the average quality of the bases forming the consensus base (most common base) and the consensus quality tag now carry an array with the counts of each base in the consensus. This should increase file size but improve calling sensitivity/specificity. --- .../java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 1 + 1 file changed, 1 insertion(+) 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 e0a3a5a533..fcb3089cd1 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -52,6 +52,7 @@ private ReadUtils() { } // ---------------------------------------------------------------------------------------------------- public static final String REDUCED_READ_QUALITY_TAG = "RQ"; + public static final String REDUCED_READ_CONSENSUS_COUNTS_TAG = "CC"; public final static Integer getReducedReadQualityTagValue(final SAMRecord read) { return read.getIntegerAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); From 68761a6e2882ce23744e13cf3c70073c90fca267 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 14:13:05 -0400 Subject: [PATCH 152/363] Removed sample from header --- .../org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java | 1 - 1 file changed, 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java index b76619cd73..32ab50695f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java @@ -25,7 +25,6 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.HasGenomeLocation; From 625ffb6a075f5233cd0730bb71899463bfec4ad3 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 14:52:11 -0400 Subject: [PATCH 153/363] LocusIteratorByState and ReadBackedPileups no long use Sample --- .../sting/gatk/executive/WindowMaker.java | 2 +- .../gatk/iterators/LocusIteratorByState.java | 60 +++++++------------ .../sting/gatk/samples/SampleDataSource.java | 11 ++-- .../pileup/AbstractReadBackedPileup.java | 34 +++++------ .../pileup/MergingPileupElementIterator.java | 3 +- .../utils/pileup/PileupElementTracker.java | 33 +++------- .../ReadBackedExtendedEventPileupImpl.java | 3 +- .../utils/pileup/ReadBackedPileupImpl.java | 3 +- .../LocusIteratorByStateUnitTest.java | 13 ++-- 9 files changed, 59 insertions(+), 103 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java index da0395ba84..fb207087fa 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java @@ -70,7 +70,7 @@ public WindowMaker(Shard shard, GenomeLocParser genomeLocParser, StingSAMIterato this.sourceInfo = shard.getReadProperties(); this.readIterator = iterator; - this.sourceIterator = new PeekableIterator(new LocusIteratorByState(iterator,sourceInfo,genomeLocParser,sampleData)); + this.sourceIterator = new PeekableIterator(new LocusIteratorByState(iterator,sourceInfo,genomeLocParser,sampleData.getSampleNames())); this.intervalIterator = intervals.size()>0 ? new PeekableIterator(intervals.iterator()) : null; } 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 61b861fd60..e466aa3250 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -35,8 +35,6 @@ import org.broadinstitute.sting.gatk.DownsamplingMethod; import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; -import org.broadinstitute.sting.gatk.samples.Sample; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.MathUtils; @@ -69,7 +67,7 @@ public class LocusIteratorByState extends LocusIterator { * Used to create new GenomeLocs. */ private final GenomeLocParser genomeLocParser; - private final ArrayList samples; + private final ArrayList samples; private final ReadStateManager readStates; static private class SAMRecordState { @@ -278,15 +276,15 @@ public CigarOperator stepForwardOnGenome() { // // ----------------------------------------------------------------------------------------------------------------- - public LocusIteratorByState(final Iterator samIterator, ReadProperties readInformation, GenomeLocParser genomeLocParser, SampleDataSource sampleData ) { + public LocusIteratorByState(final Iterator samIterator, ReadProperties readInformation, GenomeLocParser genomeLocParser, Collection samples ) { this.readInfo = readInformation; this.genomeLocParser = genomeLocParser; + this.samples = new ArrayList(samples); + this.readStates = new ReadStateManager(samIterator,readInformation.getDownsamplingMethod()); + } - // get the list of samples - this.samples = new ArrayList(sampleData.getSamples()); - - readStates = new ReadStateManager(samIterator,readInformation.getDownsamplingMethod()); - + public LocusIteratorByState(final Iterator samIterator, ReadProperties readInformation, GenomeLocParser genomeLocParser ) { + this(samIterator, readInformation, genomeLocParser, Collections.emptySet()); } public Iterator iterator() { @@ -303,19 +301,6 @@ public boolean hasNext() { //if ( DEBUG ) System.out.printf("hasNext() = %b%n", r); } - public void printState() { - for(Sample sample: samples) { - Iterator iterator = readStates.iterator(sample); - while(iterator.hasNext()) { - SAMRecordState state = iterator.next(); - logger.debug(String.format("printState():")); - SAMRecord read = state.getRead(); - int offset = state.getReadOffset(); - logger.debug(String.format(" read: %s(%d)=%s, cigar=%s", read.getReadName(), offset, (char)read.getReadBases()[offset], read.getCigarString())); - } - } - } - private GenomeLoc getLocation() { return readStates.isEmpty() ? null : readStates.getFirst().getLocation(genomeLocParser); } @@ -355,14 +340,14 @@ private void lazyLoadNextAlignmentContext() { // In this case, the subsequent call to next() will emit the normal pileup at the current base // and shift the position. if (readInfo.generateExtendedEvents() && hasExtendedEvents) { - Map fullExtendedEventPileup = new HashMap(); + Map fullExtendedEventPileup = new HashMap(); // get current location on the reference and decrement it by 1: the indels we just stepped over // are associated with the *previous* reference base GenomeLoc loc = genomeLocParser.incPos(getLocation(),-1); boolean hasBeenSampled = false; - for(Sample sample: samples) { + for(final String sample: samples) { Iterator iterator = readStates.iterator(sample); List indelPile = new ArrayList(readStates.size(sample)); hasBeenSampled |= loc.getStart() <= readStates.getDownsamplingExtent(sample); @@ -426,10 +411,10 @@ private void lazyLoadNextAlignmentContext() { nextAlignmentContext = new AlignmentContext(loc, new ReadBackedExtendedEventPileupImpl(loc, fullExtendedEventPileup), hasBeenSampled); } else { GenomeLoc location = getLocation(); - Map fullPileup = new HashMap(); + Map fullPileup = new HashMap(); boolean hasBeenSampled = false; - for(Sample sample: samples) { + for(final String sample: samples) { Iterator iterator = readStates.iterator(sample); List pile = new ArrayList(readStates.size(sample)); hasBeenSampled |= location.getStart() <= readStates.getDownsamplingExtent(sample); @@ -495,7 +480,7 @@ private static boolean filterBaseInRead(SAMRecord rec, long pos) { } private void updateReadStates() { - for(Sample sample: samples) { + for(final String sample: samples) { Iterator it = readStates.iterator(sample); while ( it.hasNext() ) { SAMRecordState state = it.next(); @@ -522,7 +507,7 @@ private class ReadStateManager { private final PeekableIterator iterator; private final DownsamplingMethod downsamplingMethod; private final SamplePartitioner samplePartitioner; - private final Map readStatesBySample = new HashMap(); + private final Map readStatesBySample = new HashMap(); private final int targetCoverage; private int totalReadStates = 0; @@ -540,9 +525,9 @@ public ReadStateManager(Iterator source, DownsamplingMethod downsampl } Map readSelectors = new HashMap(); - for(Sample sample: samples) { + for(final String sample: samples) { readStatesBySample.put(sample,new PerSampleReadStateManager()); - readSelectors.put(sample.getID(),downsamplingMethod.type == DownsampleType.BY_SAMPLE ? new NRandomReadSelector(null,targetCoverage) : new AllReadsSelector()); + readSelectors.put(sample,downsamplingMethod.type == DownsampleType.BY_SAMPLE ? new NRandomReadSelector(null,targetCoverage) : new AllReadsSelector()); } samplePartitioner = new SamplePartitioner(readSelectors); @@ -554,7 +539,7 @@ public ReadStateManager(Iterator source, DownsamplingMethod downsampl * @param sample The sample. * @return Iterator over the reads associated with that sample. */ - public Iterator iterator(final Sample sample) { + public Iterator iterator(final String sample) { return new Iterator() { private Iterator wrappedIterator = readStatesBySample.get(sample).iterator(); @@ -590,7 +575,7 @@ public int size() { * @param sample The sample. * @return Total number of reads in the given sample. */ - public int size(final Sample sample) { + public int size(final String sample) { return readStatesBySample.get(sample).size(); } @@ -600,12 +585,12 @@ public int size(final Sample sample) { * @param sample Sample, downsampled independently. * @return Integer stop of the furthest undownsampled region. */ - public int getDownsamplingExtent(final Sample sample) { + public int getDownsamplingExtent(final String sample) { return readStatesBySample.get(sample).getDownsamplingExtent(); } public SAMRecordState getFirst() { - for(Sample sample: samples) { + for(final String sample: samples) { PerSampleReadStateManager reads = readStatesBySample.get(sample); if(!reads.isEmpty()) return reads.peek(); @@ -639,8 +624,8 @@ public void collectPendingReads() { } samplePartitioner.complete(); - for(Sample sample: samples) { - ReadSelector aggregator = samplePartitioner.getSelectedReads(sample.getID()); + for(final String sample: samples) { + ReadSelector aggregator = samplePartitioner.getSelectedReads(sample); Collection newReads = new ArrayList(aggregator.getSelectedReads()); @@ -1072,6 +1057,3 @@ public void reset() { } } - - - diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index fec82a71f8..9543c834a1 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -182,12 +182,15 @@ public Set getChildren(Sample sample) { return children; } - public Set getSamples() { - HashSet set = new HashSet(); - set.addAll(samples.values()); - return set; + public Collection getSamples() { + return Collections.unmodifiableCollection(samples.values()); } + public Collection getSampleNames() { + return Collections.unmodifiableCollection(samples.keySet()); + } + + /** * Takes a collection of sample names and returns their corresponding sample objects * Note that, since a set is returned, if you pass in a list with duplicates names there will not be any duplicates in the returned set diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java index 38ffcae8fd..fd1a041c59 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java @@ -26,11 +26,9 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.StingException; import java.util.*; @@ -114,10 +112,10 @@ protected AbstractReadBackedPileup(GenomeLoc loc, PileupElementTracker track calculateCachedData(); } - protected AbstractReadBackedPileup(GenomeLoc loc, Map> pileupsBySample) { + protected AbstractReadBackedPileup(GenomeLoc loc, Map> pileupsBySample) { this.loc = loc; PerSamplePileupElementTracker tracker = new PerSamplePileupElementTracker(); - for(Map.Entry> pileupEntry: pileupsBySample.entrySet()) { + for(Map.Entry> pileupEntry: pileupsBySample.entrySet()) { tracker.addElements(pileupEntry.getKey(),pileupEntry.getValue().pileupElementTracker); addPileupToCumulativeStats(pileupEntry.getValue()); } @@ -213,7 +211,7 @@ public RBP getPileupWithoutDeletions() { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getPileupWithoutDeletions(); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -251,7 +249,7 @@ public RBP getOverlappingFragmentFilteredPileup() { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getOverlappingFragmentFilteredPileup(); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -305,7 +303,7 @@ public RBP getPileupWithoutMappingQualityZeroReads() { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getPileupWithoutMappingQualityZeroReads(); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -334,7 +332,7 @@ public RBP getPositiveStrandPileup() { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getPositiveStrandPileup(); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -363,7 +361,7 @@ public RBP getNegativeStrandPileup() { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getNegativeStrandPileup(); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -393,7 +391,7 @@ public RBP getFilteredPileup(PileupElementFilter filter) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getFilteredPileup(filter); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -425,7 +423,7 @@ public RBP getBaseAndMappingFilteredPileup( int minBaseQ, int minMapQ ) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getBaseAndMappingFilteredPileup(minBaseQ,minMapQ); filteredTracker.addElements(sample,pileup.pileupElementTracker); @@ -492,7 +490,7 @@ public RBP getPileupForReadGroup(String targetReadGroupId) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getPileupForReadGroup(targetReadGroupId); if(pileup != null) @@ -523,7 +521,7 @@ public RBP getPileupForLane(String laneID) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PerSamplePileupElementTracker filteredTracker = new PerSamplePileupElementTracker(); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); AbstractReadBackedPileup pileup = createNewPileup(loc,perSampleElements).getPileupForLane(laneID); if(pileup != null) @@ -553,11 +551,7 @@ public RBP getPileupForLane(String laneID) { public Collection getSampleNames() { if(pileupElementTracker instanceof PerSamplePileupElementTracker) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; - Collection sampleNames = new HashSet(); - for (Sample sample : tracker.getSamples()) { - sampleNames.add(sample.getID()); - } - return sampleNames; + return new HashSet(tracker.getSamples()); } else { Collection sampleNames = new HashSet(); @@ -594,7 +588,7 @@ public RBP getDownsampledPileup(int desiredCoverage) { int current = 0; - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker perSampleElements = tracker.getElements(sample); List filteredPileup = new ArrayList(); @@ -767,7 +761,7 @@ public int[] getBaseCounts() { if(pileupElementTracker instanceof PerSamplePileupElementTracker) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { int[] countsBySample = createNewPileup(loc,tracker.getElements(sample)).getBaseCounts(); for(int i = 0; i < counts.length; i++) counts[i] += countsBySample[i]; diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java b/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java index 58afc35e94..c00ed24f27 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/MergingPileupElementIterator.java @@ -25,7 +25,6 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.picard.util.PeekableIterator; -import org.broadinstitute.sting.gatk.samples.Sample; import java.util.Comparator; import java.util.Iterator; @@ -42,7 +41,7 @@ class MergingPileupElementIterator implements Iterator public MergingPileupElementIterator(PerSamplePileupElementTracker tracker) { perSampleIterators = new PriorityQueue>(Math.max(1,tracker.getSamples().size()),new PileupElementIteratorComparator()); - for(Sample sample: tracker.getSamples()) { + for(final String sample: tracker.getSamples()) { PileupElementTracker trackerPerSample = tracker.getElements(sample); if(trackerPerSample.size() != 0) perSampleIterators.add(new PeekableIterator(trackerPerSample.iterator())); diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java index 56e06c3d7a..09b907e00d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElementTracker.java @@ -24,8 +24,6 @@ package org.broadinstitute.sting.utils.pileup; -import org.broadinstitute.sting.gatk.samples.Sample; - import java.util.*; /** @@ -60,52 +58,35 @@ public int size() { } class PerSamplePileupElementTracker extends PileupElementTracker { - private final Map> pileup; - private final Map sampleNames = new HashMap(); + private final Map> pileup; private int size = 0; public PerSamplePileupElementTracker() { - pileup = new HashMap>(); - } - - public PerSamplePileupElementTracker(Map> pileupsBySample) { - pileup = new HashMap>(); - for(Map.Entry> entry: pileupsBySample.entrySet()) { - Sample sample = entry.getKey(); - AbstractReadBackedPileup pileupBySample = entry.getValue(); - pileup.put(sample,pileupBySample.pileupElementTracker); - sampleNames.put(sample.getID(), sample); - } + pileup = new HashMap>(); } /** * Gets a list of all the samples stored in this pileup. * @return List of samples in this pileup. */ - public Collection getSamples() { + public Collection getSamples() { return pileup.keySet(); } - public PileupElementTracker getElements(final Sample sample) { + public PileupElementTracker getElements(final String sample) { return pileup.get(sample); } - public PileupElementTracker getElements(final String sampleName) { - return pileup.get(sampleNames.get(sampleName)); - } - public PileupElementTracker getElements(final Collection selectSampleNames) { PerSamplePileupElementTracker result = new PerSamplePileupElementTracker(); - for (String sample : selectSampleNames) { - Sample sampleObject = sampleNames.get(sample); - result.addElements(sampleObject, pileup.get(sampleObject)); + for (final String sample : selectSampleNames) { + result.addElements(sample, pileup.get(sample)); } return result; } - public void addElements(final Sample sample, PileupElementTracker elements) { + public void addElements(final String sample, PileupElementTracker elements) { pileup.put(sample,elements); - sampleNames.put(sample.getID(), sample); size += elements.size(); } diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java index 6a3de5570c..21dfee8b86 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileupImpl.java @@ -24,7 +24,6 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -56,7 +55,7 @@ public ReadBackedExtendedEventPileupImpl(GenomeLoc loc, List pileupElementsBySample) { + public ReadBackedExtendedEventPileupImpl(GenomeLoc loc, Map pileupElementsBySample) { super(loc,pileupElementsBySample); } diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java index 7ebf6281b1..18e6d91341 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileupImpl.java @@ -24,7 +24,6 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import java.util.List; @@ -48,7 +47,7 @@ public ReadBackedPileupImpl(GenomeLoc loc, List pileupElements) { super(loc,pileupElements); } - public ReadBackedPileupImpl(GenomeLoc loc, Map pileupElementsBySample) { + public ReadBackedPileupImpl(GenomeLoc loc, Map pileupElementsBySample) { super(loc,pileupElementsBySample); } 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 c8cfdac9aa..0db2e466c2 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java @@ -28,7 +28,6 @@ * testing of the LocusIteratorByState */ public class LocusIteratorByStateUnitTest extends BaseTest { - private final int MAX_READS = 10; private static SAMFileHeader header; private LocusIteratorByState li; @@ -67,7 +66,7 @@ public void testIndelBaseQualityFiltering() { List reads = Arrays.asList(before,during,after); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser, new SampleDataSource()); + li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); boolean foundExtendedEventPileup = false; while (li.hasNext()) { @@ -119,7 +118,7 @@ public void testIndelPileupContainsAbuttingReads() { List reads = Arrays.asList(before,during,after); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser, new SampleDataSource()); + li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); boolean foundExtendedEventPileup = false; while (li.hasNext()) { @@ -153,7 +152,7 @@ public void testWholeIndelReadInIsolation() { List reads = Arrays.asList(indelOnlyRead); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser,new SampleDataSource()); + li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); // Traditionally, reads that end with indels bleed into the pileup at the following locus. Verify that the next pileup contains this read // and considers it to be an indel-containing read. @@ -166,7 +165,7 @@ public void testWholeIndelReadInIsolation() { // Turn on extended events, and make sure the event is found. JVMUtils.setFieldValue(JVMUtils.findField(ReadProperties.class,"generateExtendedEvents"),readAttributes,true); - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser,new SampleDataSource()); + li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); Assert.assertTrue(li.hasNext(),"LocusIteratorByState with extended events should contain exactly one pileup"); alignmentContext = li.next(); @@ -202,7 +201,7 @@ public void testWholeIndelReadWithoutExtendedEvents() { List reads = Arrays.asList(leadingRead,indelOnlyRead,fullMatchAfterIndel); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),createTestReadProperties(),genomeLocParser,new SampleDataSource()); + li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),createTestReadProperties(),genomeLocParser); int currentLocus = firstLocus; int numAlignmentContextsFound = 0; @@ -259,7 +258,7 @@ public void testWholeIndelReadWithExtendedEvents() { List reads = Arrays.asList(leadingRead,indelOnlyRead,fullMatchAfterIndel); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser,new SampleDataSource()); + li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); Assert.assertTrue(li.hasNext(),"Missing first locus at " + firstLocus); AlignmentContext alignmentContext = li.next(); From 9458f01409b5307981992bdeb7f26d460465f01f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 15:13:05 -0400 Subject: [PATCH 154/363] Test cleanup of Sample object --- .../reads/DownsamplerBenchmark.java | 2 +- .../utils/pileup/ReadBackedPileupUnitTest.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) 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 9f3c2bb294..0e1f4253aa 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 @@ -89,7 +89,7 @@ public void timeDownsampling(int reps) { // Filter unmapped reads. TODO: is this always strictly necessary? Who in the GATK normally filters these out? Iterator readIterator = new FilteringIterator(reader.iterator(),new UnmappedReadFilter()); - LocusIteratorByState locusIteratorByState = new LocusIteratorByState(readIterator,readProperties,genomeLocParser,sampleDataSource); + LocusIteratorByState locusIteratorByState = new LocusIteratorByState(readIterator,readProperties,genomeLocParser,sampleDataSource.getSampleNames()); while(locusIteratorByState.hasNext()) { locusIteratorByState.next().getLocation(); } diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java index 7b1ee97681..4e05415227 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java @@ -141,9 +141,9 @@ public void testSplitBySample() { ReadBackedPileupImpl sample2Pileup = new ReadBackedPileupImpl(null, Arrays.asList(read2,read4), Arrays.asList(1,1)); - Map sampleToPileupMap = new HashMap(); - sampleToPileupMap.put(new Sample(readGroupOne.getSample(), null),sample1Pileup); - sampleToPileupMap.put(new Sample(readGroupTwo.getSample(), null),sample2Pileup); + Map sampleToPileupMap = new HashMap(); + sampleToPileupMap.put(readGroupOne.getSample(),sample1Pileup); + sampleToPileupMap.put(readGroupTwo.getSample(),sample2Pileup); ReadBackedPileup compositePileup = new ReadBackedPileupImpl(null,sampleToPileupMap); @@ -164,13 +164,13 @@ public void testSplitBySample() { @Test public void testGetPileupForSample() { - Sample sample1 = new Sample("sample1", null); - Sample sample2 = new Sample("sample2", null); + String sample1 = "sample1"; + String sample2 = "sample2"; SAMReadGroupRecord readGroupOne = new SAMReadGroupRecord("rg1"); - readGroupOne.setSample(sample1.getID()); + readGroupOne.setSample(sample1); SAMReadGroupRecord readGroupTwo = new SAMReadGroupRecord("rg2"); - readGroupTwo.setSample(sample2.getID()); + readGroupTwo.setSample(sample2); SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1,1,1000); header.addReadGroup(readGroupOne); @@ -181,13 +181,13 @@ public void testGetPileupForSample() { SAMRecord read2 = ArtificialSAMUtils.createArtificialRead(header,"read2",0,1,10); read2.setAttribute("RG",readGroupTwo.getId()); - Map sampleToPileupMap = new HashMap(); + Map sampleToPileupMap = new HashMap(); sampleToPileupMap.put(sample1,new ReadBackedPileupImpl(null,Collections.singletonList(read1),0)); sampleToPileupMap.put(sample2,new ReadBackedPileupImpl(null,Collections.singletonList(read2),0)); ReadBackedPileup pileup = new ReadBackedPileupImpl(null,sampleToPileupMap); - ReadBackedPileup sample2Pileup = pileup.getPileupForSampleName(sample2.getID()); + ReadBackedPileup sample2Pileup = pileup.getPileupForSampleName(sample2); Assert.assertEquals(sample2Pileup.size(),1,"Sample 2 pileup has wrong number of elements"); Assert.assertEquals(sample2Pileup.getReads().get(0),read2,"Sample 2 pileup has incorrect read"); From 95082201579062be0d49a0f845177e85f6c99d71 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 29 Sep 2011 15:36:49 -0400 Subject: [PATCH 155/363] fixed hard clipping both ends inside deletion If both ends of the interval falls within a deletion in the read then hardClipBothEnds would cut the right tail first including the entire deletion, then fail to cut the left tail because there would not be any bases there anymore. Fixed. --- .../broadinstitute/sting/utils/clipreads/ReadClipper.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 5230381c05..11a59de102 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -94,6 +94,12 @@ public SAMRecord hardClipBothEndsByReferenceCoordinates(int left, int right) { if (left == right) return new SAMRecord(read.getHeader()); SAMRecord 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 SAMRecord(read.getHeader()); + ReadClipper clipper = new ReadClipper(leftTailRead); return clipper.hardClipByReferenceCoordinatesLeftTail(left); } From 98ecaf8aa0c0507f3f6774cc3065604648e210d6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 17:18:39 -0400 Subject: [PATCH 156/363] Support for ReducedReads with reduced counts and average quals -- ReadUtils and UnitTest updated to support new byte[] style -- Removed unnecessary read transformer in PairHMM --- .../indels/PairHMMIndelErrorModel.java | 4 --- .../sting/utils/pileup/PileupElement.java | 6 ++-- .../sting/utils/sam/ReadUtils.java | 33 ++++++++++++------- .../sting/utils/ReadUtilsUnitTest.java | 28 +++++----------- 4 files changed, 32 insertions(+), 39 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 c8328771b4..7c436ce447 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 @@ -415,10 +415,6 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi if (read == null) continue; - if ( isReduced ) { - read = ReadUtils.reducedReadWithReducedQuals(read); - } - if(ReadUtils.is454Read(read)) { continue; } 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 053864791b..f6ed792a57 100755 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java @@ -87,11 +87,11 @@ public boolean isReducedRead() { public int getReducedCount() { if ( ! isReducedRead() ) throw new IllegalArgumentException("Cannot get reduced count for non-reduced read " + getRead().getReadName()); - return (int)getQual(); + return ReadUtils.getReducedCount(getRead(), offset); } public byte getReducedQual() { - return (byte)(int)ReadUtils.getReducedReadQualityTagValue(getRead()); + if ( ! isReducedRead() ) throw new IllegalArgumentException("Cannot get reduced qual for non-reduced read " + getRead().getReadName()); + return ReadUtils.getReducedQual(getRead(), offset); } - } \ 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 fcb3089cd1..a57154ff1d 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -54,26 +54,35 @@ private ReadUtils() { } public static final String REDUCED_READ_QUALITY_TAG = "RQ"; public static final String REDUCED_READ_CONSENSUS_COUNTS_TAG = "CC"; - public final static Integer getReducedReadQualityTagValue(final SAMRecord read) { - return read.getIntegerAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); + public final static byte[] getReducedReadQualityTagValue(final SAMRecord read) { + return read.getByteArrayAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); } public final static boolean isReducedRead(final SAMRecord read) { return getReducedReadQualityTagValue(read) != null; } + public final static byte getReducedQual(final SAMRecord read, final int i) { + return read.getBaseQualities()[i]; + } + + public final static byte getReducedCount(final SAMRecord read, final int i) { + return getReducedReadQualityTagValue(read)[i]; + } + public final static SAMRecord reducedReadWithReducedQuals(final SAMRecord read) { if ( ! isReducedRead(read) ) throw new IllegalArgumentException("read must be a reduced read"); - try { - SAMRecord newRead = (SAMRecord)read.clone(); - byte reducedQual = (byte)(int)getReducedReadQualityTagValue(read); - byte[] newQuals = new byte[read.getBaseQualities().length]; - Arrays.fill(newQuals, reducedQual); - newRead.setBaseQualities(newQuals); - return newRead; - } catch ( CloneNotSupportedException e ) { - throw new ReviewedStingException("SAMRecord no longer supports clone", e); - } + return read; +// try { +// SAMRecord newRead = (SAMRecord)read.clone(); +// byte reducedQual = (byte)(int)getReducedReadQualityTagValue(read); +// byte[] newQuals = new byte[read.getBaseQualities().length]; +// Arrays.fill(newQuals, reducedQual); +// newRead.setBaseQualities(newQuals); +// return newRead; +// } catch ( CloneNotSupportedException e ) { +// throw new ReviewedStingException("SAMRecord no longer supports clone", e); +// } } // ---------------------------------------------------------------------------------------------------- diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java index e1fdadadc7..c14fea24c9 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java @@ -16,7 +16,7 @@ public class ReadUtilsUnitTest extends BaseTest { SAMRecord read, reducedRead; final static String BASES = "ACTG"; final static String QUALS = "!+5?"; - final private static int REDUCED_READ_QUAL = 20; + final private static byte[] REDUCED_READ_COUNTS = new byte[]{10, 20, 30, 40}; @BeforeTest public void init() { @@ -29,7 +29,7 @@ public void init() { reducedRead = ArtificialSAMUtils.createArtificialRead(header, "reducedRead", 0, 1, BASES.length()); reducedRead.setReadBases(BASES.getBytes()); reducedRead.setBaseQualityString(QUALS); - reducedRead.setAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG, REDUCED_READ_QUAL); + reducedRead.setAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG, REDUCED_READ_COUNTS); } private void testReadBasesAndQuals(SAMRecord read, int expectedStart, int expectedStop) { @@ -52,21 +52,10 @@ public void testReducedReads() { Assert.assertEquals(ReadUtils.getReducedReadQualityTagValue(read), null, "No reduced read tag in normal read"); Assert.assertTrue(ReadUtils.isReducedRead(reducedRead), "isReducedRead is true for reduced read"); - Assert.assertEquals((int) ReadUtils.getReducedReadQualityTagValue(reducedRead), REDUCED_READ_QUAL, "Reduced read tag is set to expected value"); - } - - @Test - public void testreducedReadWithReducedQualsWithReducedRead() { - SAMRecord replacedRead = ReadUtils.reducedReadWithReducedQuals(reducedRead); - Assert.assertEquals(replacedRead.getReadBases(), reducedRead.getReadBases()); - Assert.assertEquals(replacedRead.getBaseQualities().length, reducedRead.getBaseQualities().length); - for ( int i = 0; i < replacedRead.getBaseQualities().length; i++) - Assert.assertEquals(replacedRead.getBaseQualities()[i], REDUCED_READ_QUAL); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testreducedReadWithReducedQualsWithNormalRead() { - ReadUtils.reducedReadWithReducedQuals(read); + for ( int i = 0; i < reducedRead.getReadLength(); i++) { + Assert.assertEquals(ReadUtils.getReducedQual(reducedRead, i), read.getBaseQualities()[i], "Reduced read quality not set to the expected value at " + i); + Assert.assertEquals(ReadUtils.getReducedCount(reducedRead, i), REDUCED_READ_COUNTS[i], "Reduced read count not set to the expected value at " + i); + } } @Test @@ -77,8 +66,7 @@ public void testReducedReadPileupElement() { Assert.assertFalse(readp.isReducedRead()); Assert.assertTrue(reducedreadp.isReducedRead()); - Assert.assertEquals(reducedreadp.getReducedCount(), 0); - Assert.assertEquals(reducedreadp.getReducedQual(), REDUCED_READ_QUAL); - + Assert.assertEquals(reducedreadp.getReducedCount(), REDUCED_READ_COUNTS[0]); + Assert.assertEquals(reducedreadp.getReducedQual(), readp.getQual()); } } From b71b51751ecc7ea38e4bf88c11c3420e102b7b14 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 29 Sep 2011 17:30:01 -0400 Subject: [PATCH 157/363] Bug fix for UnitTest -- Provide the null sample to the LIBS, as this seems to be required for correctly passing this unit test -- Will be fixed in a future update --- .../iterators/LocusIteratorByStateUnitTest.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 0db2e466c2..6e9fa03724 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java @@ -40,6 +40,11 @@ public void beforeClass() { genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); } + private final LocusIteratorByState makeLTBS(List reads, ReadProperties readAttributes) { + return new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()), + readAttributes,genomeLocParser, new SampleDataSource().getSampleNames()); + } + @Test public void testIndelBaseQualityFiltering() { final byte[] bases = new byte[] {'A','A','A','A','A','A','A','A','A','A'}; @@ -66,7 +71,7 @@ public void testIndelBaseQualityFiltering() { List reads = Arrays.asList(before,during,after); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); + li = makeLTBS(reads,readAttributes); boolean foundExtendedEventPileup = false; while (li.hasNext()) { @@ -118,7 +123,7 @@ public void testIndelPileupContainsAbuttingReads() { List reads = Arrays.asList(before,during,after); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); + li = makeLTBS(reads,readAttributes); boolean foundExtendedEventPileup = false; while (li.hasNext()) { @@ -152,7 +157,7 @@ public void testWholeIndelReadInIsolation() { List reads = Arrays.asList(indelOnlyRead); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); + li = makeLTBS(reads, readAttributes); // Traditionally, reads that end with indels bleed into the pileup at the following locus. Verify that the next pileup contains this read // and considers it to be an indel-containing read. @@ -165,7 +170,7 @@ public void testWholeIndelReadInIsolation() { // Turn on extended events, and make sure the event is found. JVMUtils.setFieldValue(JVMUtils.findField(ReadProperties.class,"generateExtendedEvents"),readAttributes,true); - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); + li = makeLTBS(reads, readAttributes); Assert.assertTrue(li.hasNext(),"LocusIteratorByState with extended events should contain exactly one pileup"); alignmentContext = li.next(); @@ -201,7 +206,7 @@ public void testWholeIndelReadWithoutExtendedEvents() { List reads = Arrays.asList(leadingRead,indelOnlyRead,fullMatchAfterIndel); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),createTestReadProperties(),genomeLocParser); + li = makeLTBS(reads, createTestReadProperties()); int currentLocus = firstLocus; int numAlignmentContextsFound = 0; @@ -258,7 +263,7 @@ public void testWholeIndelReadWithExtendedEvents() { List reads = Arrays.asList(leadingRead,indelOnlyRead,fullMatchAfterIndel); // create the iterator by state with the fake reads and fake records - li = new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()),readAttributes,genomeLocParser); + li = makeLTBS(reads,readAttributes); Assert.assertTrue(li.hasNext(),"Missing first locus at " + firstLocus); AlignmentContext alignmentContext = li.next(); From cabacf028d96cd3678d41891e491aedf356473da Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 29 Sep 2011 18:45:12 -0400 Subject: [PATCH 158/363] Intermediate commit to fix interval skipping may need additional testing. --- .../java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java | 1 + 1 file changed, 1 insertion(+) 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 fcb3089cd1..3c389fd4c7 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -966,4 +966,5 @@ public static int compareSAMRecords(SAMRecord read1, SAMRecord read2) { AlignmentStartComparator comp = new AlignmentStartComparator(); return comp.compare(read1, read2); } + } From a881d6f1452f2ec8e8a2d48aad5b6eb80087cf54 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 08:42:09 -0400 Subject: [PATCH 159/363] Now only generates the poly VCF with select variants if the file doesn't exist From e055a78f6e479eabcb172a5aa2ca8164fd2ea974 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 09:49:35 -0400 Subject: [PATCH 160/363] LIBS now requires at least one sample be present -- UnitTest provides a "null" sample for matching the reads without read groups --- .../sting/gatk/iterators/LocusIteratorByState.java | 9 +++------ .../gatk/iterators/LocusIteratorByStateUnitTest.java | 7 ++++--- 2 files changed, 7 insertions(+), 9 deletions(-) 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 e466aa3250..9a468f4824 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -39,6 +39,7 @@ 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; @@ -50,9 +51,6 @@ /** Iterator that traverses a SAM File, accumulating information on a per-locus basis */ public class LocusIteratorByState extends LocusIterator { -// private static long discarded_bases = 0L; -// private static long observed_bases = 0L; - /** our log, which we want to capture anything from this class */ private static Logger logger = Logger.getLogger(LocusIteratorByState.class); @@ -281,10 +279,9 @@ public LocusIteratorByState(final Iterator samIterator, ReadPropertie this.genomeLocParser = genomeLocParser; this.samples = new ArrayList(samples); this.readStates = new ReadStateManager(samIterator,readInformation.getDownsamplingMethod()); - } - public LocusIteratorByState(final Iterator samIterator, ReadProperties readInformation, GenomeLocParser genomeLocParser ) { - this(samIterator, readInformation, genomeLocParser, Collections.emptySet()); + if ( this.samples.isEmpty() ) + throw new IllegalArgumentException("samples list must not be empty"); } public Iterator iterator() { 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 6e9fa03724..dc43b49689 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java @@ -28,10 +28,8 @@ * testing of the LocusIteratorByState */ public class LocusIteratorByStateUnitTest extends BaseTest { - private final int MAX_READS = 10; private static SAMFileHeader header; private LocusIteratorByState li; - private GenomeLocParser genomeLocParser; @BeforeClass @@ -41,8 +39,11 @@ public void beforeClass() { } private final LocusIteratorByState makeLTBS(List reads, ReadProperties readAttributes) { + List samples = new ArrayList(); + samples.add(null); + return new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()), - readAttributes,genomeLocParser, new SampleDataSource().getSampleNames()); + readAttributes,genomeLocParser, samples); } @Test From a69a4dda2fcf14ef3f7e8c6e97b92645fdf62e6d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 09:56:23 -0400 Subject: [PATCH 161/363] SamplesDB no longer has null sample -- Updated getSamples().size() == 2 test in CallableLociWalker that really ensured there was one sample in the system --- .../sting/gatk/samples/SampleDataSource.java | 2 +- .../walkers/coverage/CallableLociWalker.java | 3 +-- .../gatk/walkers/qc/CountMalesWalker.java | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index 9543c834a1..d5285271b4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -35,7 +35,7 @@ public class SampleDataSource { * Constructor takes both a SAM header and sample files because the two must be integrated. */ public SampleDataSource() { - samples.put(null, new Sample(null, this)); + } public SampleDataSource(final SAMFileHeader header, final List sampleFiles) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java index 1e2d402713..1dfc6fea00 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CallableLociWalker.java @@ -227,8 +227,7 @@ public enum CalledState { @Override public void initialize() { - if ( getSampleDB().getSamples().size() != 2 ) { - // unbelievably there are actually two samples even when there's just one in the header. God I hate this Samples system + if ( getSampleDB().getSamples().size() != 1 ) { throw new UserException.BadArgumentValue("-I", "CallableLoci only works for a single sample, but multiple samples were found in the provided BAM files: " + getSampleDB().getSamples()); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java index f2c035c3cf..d776fe4158 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java @@ -1,3 +1,27 @@ +/* + * 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.qc; import net.sf.samtools.SAMRecord; From 3289a325fc90e4445e66c853ce5d20a5efb956ed Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 09:57:39 -0400 Subject: [PATCH 162/363] Removed final use of Sample in RBP --- .../sting/utils/pileup/ReadBackedExtendedEventPileup.java | 1 - 1 file changed, 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java index 8dd2394cf6..afed681770 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java @@ -25,7 +25,6 @@ package org.broadinstitute.sting.utils.pileup; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.collections.Pair; From 30d23942b1f481568ca95fd6ff82a0b1099ad237 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 10:02:57 -0400 Subject: [PATCH 163/363] Renamed ReadBackedPileup getXSampleName() functions to getXSample -- now that we don't have Sample objects floating around we don't have to have all of the Name extensions on our functions --- .../sting/gatk/contexts/AlignmentContextUtils.java | 4 ++-- .../gatk/walkers/phasing/ReadBackedPhasingWalker.java | 5 ++--- .../sting/utils/pileup/AbstractReadBackedPileup.java | 6 +++--- .../sting/utils/pileup/ReadBackedExtendedEventPileup.java | 2 +- .../sting/utils/pileup/ReadBackedPileup.java | 6 +++--- .../sting/utils/variantcontext/VariantContext.java | 2 +- .../sting/utils/pileup/ReadBackedPileupUnitTest.java | 7 +++---- 7 files changed, 15 insertions(+), 17 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 f77fbe4e91..c9506ec4c9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/gatk/contexts/AlignmentContextUtils.java @@ -88,8 +88,8 @@ public static Map splitContextBySampleName(AlignmentCo GenomeLoc loc = context.getLocation(); HashMap contexts = new HashMap(); - for(String sample: context.getPileup().getSampleNames()) { - ReadBackedPileup pileupBySample = context.getPileup().getPileupForSampleName(sample); + for(String sample: context.getPileup().getSamples()) { + ReadBackedPileup pileupBySample = context.getPileup().getPileupForSample(sample); // Don't add empty pileups to the split context. if(pileupBySample.size() == 0) 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 bbbdf5f1a3..998cfa6542 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 @@ -30,7 +30,6 @@ 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.samples.Sample; import org.broadinstitute.sting.gatk.filters.MappingQualityZeroFilter; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.*; @@ -1095,8 +1094,8 @@ else if (alignment.hasExtendedEventPileup()) { // filter the read-base pileup based on min base and mapping qualities: pileup = pileup.getBaseAndMappingFilteredPileup(MIN_BASE_QUALITY_SCORE, MIN_MAPPING_QUALITY_SCORE); if (pileup != null) { - for (final String sample : pileup.getSampleNames()) { - ReadBackedPileup samplePileup = pileup.getPileupForSampleName(sample); + for (final String sample : pileup.getSamples()) { + ReadBackedPileup samplePileup = pileup.getPileupForSample(sample); ReadBasesAtPosition readBases = new ReadBasesAtPosition(); for (PileupElement p : samplePileup) { if (!p.isDeletion()) // IGNORE deletions for now diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java index fd1a041c59..b3f2bc6b01 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/AbstractReadBackedPileup.java @@ -548,7 +548,7 @@ public RBP getPileupForLane(String laneID) { } } - public Collection getSampleNames() { + public Collection getSamples() { if(pileupElementTracker instanceof PerSamplePileupElementTracker) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; return new HashSet(tracker.getSamples()); @@ -623,7 +623,7 @@ public RBP getDownsampledPileup(int desiredCoverage) { } @Override - public RBP getPileupForSampleNames(Collection sampleNames) { + public RBP getPileupForSamples(Collection sampleNames) { if(pileupElementTracker instanceof PerSamplePileupElementTracker) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PileupElementTracker filteredElements = tracker.getElements(sampleNames); @@ -649,7 +649,7 @@ public RBP getPileupForSampleNames(Collection sampleNames) { @Override - public RBP getPileupForSampleName(String sampleName) { + public RBP getPileupForSample(String sampleName) { if(pileupElementTracker instanceof PerSamplePileupElementTracker) { PerSamplePileupElementTracker tracker = (PerSamplePileupElementTracker)pileupElementTracker; PileupElementTracker filteredElements = tracker.getElements(sampleName); diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java index afed681770..e7c0bc18f5 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedExtendedEventPileup.java @@ -120,7 +120,7 @@ public interface ReadBackedExtendedEventPileup extends ReadBackedPileup { * Gets a list of all the samples stored in this pileup. * @return List of samples in this pileup. */ - public Collection getSampleNames(); + public Collection getSamples(); public Iterable toExtendedIterable(); diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java index 32ab50695f..3d30aa11bb 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/ReadBackedPileup.java @@ -140,7 +140,7 @@ public interface ReadBackedPileup extends Iterable, HasGenomeLoca * Gets a collection of *names* of all the samples stored in this pileup. * @return Collection of names */ - public Collection getSampleNames(); + public Collection getSamples(); /** @@ -148,7 +148,7 @@ public interface ReadBackedPileup extends Iterable, HasGenomeLoca * @param sampleNames Name of the sample to use. * @return A subset of this pileup containing only reads with the given sample. */ - public ReadBackedPileup getPileupForSampleNames(Collection sampleNames); + public ReadBackedPileup getPileupForSamples(Collection sampleNames); /** @@ -156,7 +156,7 @@ public interface ReadBackedPileup extends Iterable, HasGenomeLoca * @param sampleName Name of the sample to use. * @return A subset of this pileup containing only reads with the given sample. */ - public ReadBackedPileup getPileupForSampleName(String sampleName); + public ReadBackedPileup getPileupForSample(String sampleName); /** * Simple useful routine to count the number of deletion bases in this pileup 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 412cbd90b1..05e21c8b8b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -132,7 +132,7 @@ * vc.hasGenotypes() * vc.isMonomorphic() * vc.isPolymorphic() - * vc.getSampleNames().size() + * vc.getSamples().size() * * vc.getGenotypes() * vc.getGenotypes().get("g1") diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java index 4e05415227..b07da7cc82 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/ReadBackedPileupUnitTest.java @@ -28,7 +28,6 @@ import net.sf.samtools.SAMReadGroupRecord; import net.sf.samtools.SAMRecord; import org.testng.Assert; -import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.testng.annotations.Test; @@ -187,14 +186,14 @@ public void testGetPileupForSample() { ReadBackedPileup pileup = new ReadBackedPileupImpl(null,sampleToPileupMap); - ReadBackedPileup sample2Pileup = pileup.getPileupForSampleName(sample2); + ReadBackedPileup sample2Pileup = pileup.getPileupForSample(sample2); Assert.assertEquals(sample2Pileup.size(),1,"Sample 2 pileup has wrong number of elements"); Assert.assertEquals(sample2Pileup.getReads().get(0),read2,"Sample 2 pileup has incorrect read"); - ReadBackedPileup missingSamplePileup = pileup.getPileupForSampleName("missing"); + ReadBackedPileup missingSamplePileup = pileup.getPileupForSample("missing"); Assert.assertNull(missingSamplePileup,"Pileup for sample 'missing' should be null but isn't"); - missingSamplePileup = pileup.getPileupForSampleName("not here"); + missingSamplePileup = pileup.getPileupForSample("not here"); Assert.assertNull(missingSamplePileup,"Pileup for sample 'not here' should be null but isn't"); } } From 178ba24c27af683602b34cc6e6b3d685b896db54 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 10:28:18 -0400 Subject: [PATCH 164/363] Move getSamplesForSamFile to SampleUtils -- A nearly identical piece of code already lived in SampleUtils. Now there are two functions, one taking a regular header and another grabbing the merged header from the GATK engine itself. Much cleaner --- .../sting/gatk/GenomeAnalysisEngine.java | 7 ------- .../org/broadinstitute/sting/utils/SampleUtils.java | 12 ++++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 0501287407..4884452d24 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -1041,13 +1041,6 @@ public SampleDataSource getSampleDB() { return this.sampleDataSource; } - /** - * Returns all samples that were referenced in the SAM file - */ - public Set getSAMFileSamples() { - return sampleDataSource.getSamples(SampleUtils.getSAMFileSamples(getSAMFileHeader())); - } - public Map getApproximateCommandLineArguments(Object... argumentProviders) { return CommandLineUtils.getApproximateCommandLineArguments(parsingEngine,argumentProviders); } diff --git a/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java b/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java index 1b4703e4af..edc1413ba4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/SampleUtils.java @@ -69,6 +69,18 @@ public static Set getSAMFileSamples(SAMFileHeader header) { return samples; } + + /** + * Same as @link getSAMFileSamples but gets all of the samples + * in the SAM files loaded by the engine + * + * @param engine + * @return + */ + public final static Set getSAMFileSamples(GenomeAnalysisEngine engine) { + return SampleUtils.getSAMFileSamples(engine.getSAMFileHeader()); + } + /** * Gets all of the unique sample names from all VCF rods input by the user * From 6637cd9dd5b8257b1ea81805fd0906824632db64 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Fri, 30 Sep 2011 10:35:10 -0400 Subject: [PATCH 165/363] Fixes to CalibrateGenotypeLikelihoods: a) Fix up the walker itself which was broken since the new rod binding system, b) added ability to do indels and to optionally test banded implementation, c) experimental ability to do multiple samples (may need more work) From 810e8ad0118bdc486a81b23378f12290aaa072fc Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 10:43:51 -0400 Subject: [PATCH 166/363] Removed getXByReaders() function from the engine -- These could be simplied in their downstream uses -- Or they could be replaced with a generic getSAMFileHeaders() function and then apply the getSamples(header) as desired downstream --- .../sting/gatk/GenomeAnalysisEngine.java | 108 ++---------------- .../coverage/DepthOfCoverageWalker.java | 13 +-- .../indels/SomaticIndelDetectorWalker.java | 2 +- 3 files changed, 17 insertions(+), 106 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 4884452d24..52544fbd28 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -712,100 +712,6 @@ public File getSourceFileForReaderID(final SAMReaderID id) { return getReadsDataSource().getSAMFile(id); } - /** - * Returns sets of samples present in the (merged) input SAM stream, grouped by readers (i.e. underlying - * individual bam files). For instance: if GATK is run with three input bam files (three -I arguments), then the list - * returned by this method will contain 3 elements (one for each reader), with each element being a set of sample names - * found in the corresponding bam file. - * - * @return Sets of samples in the merged input SAM stream, grouped by readers - */ - public List> getSamplesByReaders() { - Collection readers = getReadsDataSource().getReaderIDs(); - - List> sample_sets = new ArrayList>(readers.size()); - - for (SAMReaderID r : readers) { - - Set samples = new HashSet(1); - sample_sets.add(samples); - - for (SAMReadGroupRecord g : getReadsDataSource().getHeader(r).getReadGroups()) { - samples.add(g.getSample()); - } - } - - return sample_sets; - - } - - /** - * Returns sets of libraries present in the (merged) input SAM stream, grouped by readers (i.e. underlying - * individual bam files). For instance: if GATK is run with three input bam files (three -I arguments), then the list - * returned by this method will contain 3 elements (one for each reader), with each element being a set of library names - * found in the corresponding bam file. - * - * @return Sets of libraries present in the (merged) input SAM stream, grouped by readers - */ - public List> getLibrariesByReaders() { - - - Collection readers = getReadsDataSource().getReaderIDs(); - - List> lib_sets = new ArrayList>(readers.size()); - - for (SAMReaderID r : readers) { - - Set libs = new HashSet(2); - lib_sets.add(libs); - - for (SAMReadGroupRecord g : getReadsDataSource().getHeader(r).getReadGroups()) { - libs.add(g.getLibrary()); - } - } - - return lib_sets; - - } - - /** - * **** UNLESS YOU HAVE GOOD REASON TO, DO NOT USE THIS METHOD; USE getFileToReadGroupIdMapping() INSTEAD **** - * - * Returns sets of (remapped) read groups in input SAM stream, grouped by readers (i.e. underlying - * individual bam files). For instance: if GATK is run with three input bam files (three -I arguments), then the list - * returned by this method will contain 3 elements (one for each reader), with each element being a set of remapped read groups - * (i.e. as seen by read.getReadGroup().getReadGroupId() in the merged stream) that come from the corresponding bam file. - * - * @return sets of (merged) read group ids in order of input bams - */ - public List> getMergedReadGroupsByReaders() { - - - Collection readers = getReadsDataSource().getReaderIDs(); - - List> rg_sets = new ArrayList>(readers.size()); - - for (SAMReaderID r : readers) { - - Set groups = new HashSet(5); - rg_sets.add(groups); - - for (SAMReadGroupRecord g : getReadsDataSource().getHeader(r).getReadGroups()) { - if (getReadsDataSource().hasReadGroupCollisions()) { // Check if there were read group clashes with hasGroupIdDuplicates and if so: - // use HeaderMerger to translate original read group id from the reader into the read group id in the - // merged stream, and save that remapped read group id to associate it with specific reader - groups.add(getReadsDataSource().getReadGroupId(r, g.getReadGroupId())); - } else { - // otherwise, pass through the unmapped read groups since this is what Picard does as well - groups.add(g.getReadGroupId()); - } - } - } - - return rg_sets; - - } - /** * Now that all files are open, validate the sequence dictionaries of the reads vs. the reference vrs the reference ordered data (if available). * @@ -925,6 +831,18 @@ public SAMFileHeader getSAMFileHeader(SAMReaderID reader) { return readsDataSource.getHeader(reader); } + /** + * Returns an ordered list of the unmerged SAM file headers known to this engine. + * @return list of header for each input SAM file, in command line order + */ + public List getSAMFileHeaders() { + final List headers = new ArrayList(); + for ( final SAMReaderID id : getReadsDataSource().getReaderIDs() ) { + headers.add(getReadsDataSource().getHeader(id)); + } + return headers; + } + /** * Gets the master sequence dictionary for this GATK engine instance * @return a never-null dictionary listing all of the contigs known to this engine instance @@ -943,8 +861,6 @@ public SAMDataSource getReadsDataSource() { return this.readsDataSource; } - - /** * Sets the collection of GATK main application arguments. * diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java index 664c319ab5..3168d9a6bc 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/DepthOfCoverageWalker.java @@ -32,6 +32,7 @@ import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.refdata.SeekableRODIterator; +import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.refseq.RefSeqCodec; import org.broadinstitute.sting.utils.codecs.refseq.RefSeqFeature; import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrack; @@ -281,20 +282,14 @@ private HashSet getSamplesFromToolKit( Collection getSamplesFromToolKit(DoCOutputType.Partition type) { HashSet partition = new HashSet(); if ( type == DoCOutputType.Partition.sample ) { - for ( Set sampleSet : getToolkit().getSamplesByReaders() ) { - for ( String s : sampleSet ) { - partition.add(s); - } - } + partition.addAll(SampleUtils.getSAMFileSamples(getToolkit())); } else if ( type == DoCOutputType.Partition.readgroup ) { for ( SAMReadGroupRecord rg : getToolkit().getSAMFileHeader().getReadGroups() ) { partition.add(rg.getSample()+"_rg_"+rg.getReadGroupId()); } } else if ( type == DoCOutputType.Partition.library ) { - for ( Set libraries : getToolkit().getLibrariesByReaders() ) { - for ( String l : libraries ) { - partition.add(l); - } + for ( SAMReadGroupRecord rg : getToolkit().getSAMFileHeader().getReadGroups() ) { + partition.add(rg.getLibrary()); } } else if ( type == DoCOutputType.Partition.center ) { for ( SAMReadGroupRecord rg : getToolkit().getSAMFileHeader().getReadGroups() ) { 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 8bba8eac2e..84cb69b071 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 @@ -392,7 +392,7 @@ public void initialize() { location = getToolkit().getGenomeLocParser().createGenomeLoc(getToolkit().getSAMFileHeader().getSequence(0).getSequenceName(),1); - normalSamples = getToolkit().getSamplesByReaders().get(0); + normalSamples = SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeaders().get(0)); try { // we already checked that bedOutput and output_file are not set simultaneously From 73577b72cf9a8a122a61f454b776429b5223f58c Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Fri, 30 Sep 2011 13:41:24 -0400 Subject: [PATCH 167/363] Misc minor updates in HaplotypeCaller From 56f10b40a8dcae431ff9fc423d6a8bcf98076879 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 14:18:27 -0400 Subject: [PATCH 168/363] Fixing test bugs for WindowMaker that required empty sample list --- .../gatk/executive/LinearMicroScheduler.java | 3 ++- .../sting/gatk/executive/ShardTraverser.java | 5 +++- .../sting/gatk/executive/WindowMaker.java | 12 ++++++--- .../gatk/iterators/LocusIteratorByState.java | 10 +++++++ .../providers/LocusViewTemplate.java | 26 +++++++++---------- .../reads/DownsamplerBenchmark.java | 2 +- .../LocusIteratorByStateUnitTest.java | 6 +---- 7 files changed, 39 insertions(+), 25 deletions(-) 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 b7846399f7..a5d1370ba8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java @@ -56,7 +56,8 @@ public Object execute(Walker walker, ShardStrategy shardStrategy) { traversalEngine.startTimersIfNecessary(); if(shard.getShardType() == Shard.ShardType.LOCUS) { LocusWalker lWalker = (LocusWalker)walker; - WindowMaker windowMaker = new WindowMaker(shard, engine.getGenomeLocParser(), getReadIterator(shard), shard.getGenomeLocs(), engine.getSampleDB()); + WindowMaker windowMaker = new WindowMaker(shard, engine.getGenomeLocParser(), + getReadIterator(shard), shard.getGenomeLocs(), engine.getSampleDB().getSampleNames()); for(WindowMaker.WindowMakerIterator iterator: windowMaker) { ShardDataProvider dataProvider = new LocusShardDataProvider(shard,iterator.getSourceInfo(),engine.getGenomeLocParser(),iterator.getLocus(),iterator,reference,rods); Object result = traversalEngine.traverse(walker, dataProvider, accumulator.getReduceInit()); diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java b/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java index 428813b714..11e51d99bc 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java @@ -62,7 +62,10 @@ public Object call() { Object accumulator = walker.reduceInit(); LocusWalker lWalker = (LocusWalker)walker; - WindowMaker windowMaker = new WindowMaker(shard,microScheduler.getEngine().getGenomeLocParser(),microScheduler.getReadIterator(shard),shard.getGenomeLocs(), microScheduler.engine.getSampleDB()); // todo: microScheduler.engine is protected - is it okay to user it here? + WindowMaker windowMaker = new WindowMaker(shard,microScheduler.getEngine().getGenomeLocParser(), + microScheduler.getReadIterator(shard), + shard.getGenomeLocs(), + microScheduler.engine.getSampleDB().getSampleNames()); // todo: microScheduler.engine is protected - is it okay to user it here? ShardDataProvider dataProvider = null; for(WindowMaker.WindowMakerIterator iterator: windowMaker) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java index fb207087fa..825a81e64c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java @@ -12,6 +12,7 @@ import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -63,17 +64,20 @@ public class WindowMaker implements Iterable, I * the given intervals. * @param iterator The data source for this window. * @param intervals The set of intervals over which to traverse. - * @param sampleData SampleDataSource that we can reference reads with + * @param sampleNames The complete set of sample names in the reads in shard */ - public WindowMaker(Shard shard, GenomeLocParser genomeLocParser, StingSAMIterator iterator, List intervals, SampleDataSource sampleData ) { + public WindowMaker(Shard shard, GenomeLocParser genomeLocParser, StingSAMIterator iterator, List intervals, Collection sampleNames) { this.sourceInfo = shard.getReadProperties(); this.readIterator = iterator; - - this.sourceIterator = new PeekableIterator(new LocusIteratorByState(iterator,sourceInfo,genomeLocParser,sampleData.getSampleNames())); + this.sourceIterator = new PeekableIterator(new LocusIteratorByState(iterator,sourceInfo,genomeLocParser, sampleNames)); this.intervalIterator = intervals.size()>0 ? new PeekableIterator(intervals.iterator()) : null; } + public WindowMaker(Shard shard, GenomeLocParser genomeLocParser, StingSAMIterator iterator, List intervals ) { + this(shard, genomeLocParser, iterator, intervals, LocusIteratorByState.sampleListForSAMWithoutReadGroups()); + } + public Iterator iterator() { return this; } 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 9a468f4824..d16502b1d1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -284,6 +284,16 @@ public LocusIteratorByState(final Iterator samIterator, ReadPropertie throw new IllegalArgumentException("samples list must not be empty"); } + /** + * For testing only. Assumes that the incoming SAMRecords have no read groups, so creates a dummy sample list + * for the system. + */ + public final static Collection sampleListForSAMWithoutReadGroups() { + List samples = new ArrayList(); + samples.add(null); + return samples; + } + public Iterator iterator() { return this; } 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 d962f7dc8e..8b226101ab 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 @@ -50,7 +50,7 @@ public void emptyAlignmentContextTest() { GenomeLoc shardBounds = genomeLocParser.createGenomeLoc("chr1", 1, 5); Shard shard = new LocusShard(genomeLocParser, new SAMDataSource(Collections.emptyList(),genomeLocParser),Collections.singletonList(shardBounds),Collections.emptyMap()); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + 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); @@ -66,7 +66,7 @@ public void singleReadTest() { GenomeLoc shardBounds = genomeLocParser.createGenomeLoc("chr1", 1, 5); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(shardBounds)); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); @@ -81,7 +81,7 @@ public void readCoveringFirstPartTest() { SAMRecordIterator iterator = new SAMRecordIterator(read); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(),new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -95,7 +95,7 @@ public void readCoveringLastPartTest() { SAMRecordIterator iterator = new SAMRecordIterator(read); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -109,7 +109,7 @@ public void readCoveringMiddleTest() { SAMRecordIterator iterator = new SAMRecordIterator(read); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -123,7 +123,7 @@ public void readAndLocusOverlapAtLastBase() { SAMRecordIterator iterator = new SAMRecordIterator(read); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 5, 5))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(),new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -137,7 +137,7 @@ public void readOverlappingStartTest() { SAMRecordIterator iterator = new SAMRecordIterator(read); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 6, 15))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -151,7 +151,7 @@ public void readOverlappingEndTest() { SAMRecordIterator iterator = new SAMRecordIterator(read); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(),new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -166,7 +166,7 @@ public void readsSpanningTest() { SAMRecordIterator iterator = new SAMRecordIterator(read1, read2); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(),new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -185,7 +185,7 @@ public void duplicateReadsTest() { SAMRecordIterator iterator = new SAMRecordIterator(read1, read2, read3, read4); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(),new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -204,7 +204,7 @@ public void cascadingReadsWithinBoundsTest() { SAMRecordIterator iterator = new SAMRecordIterator(read1, read2, read3, read4); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(),new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -225,7 +225,7 @@ public void cascadingReadsAtBoundsTest() { SAMRecordIterator iterator = new SAMRecordIterator(read1, read2, read3, read4, read5, read6); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 1, 10))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); @@ -253,7 +253,7 @@ public void cascadingReadsOverlappingBoundsTest() { read07, read08, read09, read10, read11, read12); Shard shard = new MockLocusShard(genomeLocParser,Collections.singletonList(genomeLocParser.createGenomeLoc("chr1", 6, 15))); - WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs(), new SampleDataSource()); + WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, window.getSourceInfo(), genomeLocParser, window.getLocus(), window, null, null); LocusView view = createView(dataProvider); 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 0e1f4253aa..41d7a23c6c 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 @@ -89,7 +89,7 @@ public void timeDownsampling(int reps) { // Filter unmapped reads. TODO: is this always strictly necessary? Who in the GATK normally filters these out? Iterator readIterator = new FilteringIterator(reader.iterator(),new UnmappedReadFilter()); - LocusIteratorByState locusIteratorByState = new LocusIteratorByState(readIterator,readProperties,genomeLocParser,sampleDataSource.getSampleNames()); + LocusIteratorByState locusIteratorByState = new LocusIteratorByState(readIterator,readProperties,genomeLocParser, LocusIteratorByState.sampleListForSAMWithoutReadGroups()); while(locusIteratorByState.hasNext()) { locusIteratorByState.next().getLocation(); } 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 dc43b49689..297a8501aa 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/iterators/LocusIteratorByStateUnitTest.java @@ -11,7 +11,6 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -39,11 +38,8 @@ public void beforeClass() { } private final LocusIteratorByState makeLTBS(List reads, ReadProperties readAttributes) { - List samples = new ArrayList(); - samples.add(null); - return new LocusIteratorByState(new FakeCloseableIterator(reads.iterator()), - readAttributes,genomeLocParser, samples); + readAttributes, genomeLocParser, LocusIteratorByState.sampleListForSAMWithoutReadGroups()); } @Test From c1cf6bc45ac8dfed24c7ec13bbf0e843f6d7cf2e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 14:22:19 -0400 Subject: [PATCH 169/363] PEDReader should be in samples --- .../sting/{utils/ped => gatk/samples}/PedReader.java | 4 +--- .../src/org/broadinstitute/sting/gatk/samples/Sample.java | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) rename public/java/src/org/broadinstitute/sting/{utils/ped => gatk/samples}/PedReader.java (98%) diff --git a/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java similarity index 98% rename from public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java rename to public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index 4d282d8218..6514cffe4d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/ped/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -22,11 +22,9 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package org.broadinstitute.sting.utils.ped; +package org.broadinstitute.sting.gatk.samples; import org.apache.log4j.Logger; -import org.broadinstitute.sting.gatk.samples.Sample; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; 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 c6fcbbc2a7..db905a16e9 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -6,10 +6,7 @@ import java.util.Map; /** - * Created by IntelliJ IDEA. - * User: brett - * Date: Jul 26, 2010 - * Time: 3:31:38 PM + * */ public class Sample implements java.io.Serializable { final private String familyID, paternalID, maternalID; From d211b3b7f09d26df264a584559d8826ea84ad803 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 30 Sep 2011 10:53:59 -0400 Subject: [PATCH 170/363] Fixing interval overlap logic Complete re-write of the optimized interval overlap logic. Added lots of exceptions to trap future wrongdoings. From b5bdea1cb911e3fe6eafa80e1182671e3864590e Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 30 Sep 2011 13:25:05 -0400 Subject: [PATCH 171/363] Fix: hardclip first interval overlap but overlap subsequent intervals If a read had the left tail clipped (that would overlap the current interval) but the right tail overlaps other intervals, this was triggering out of order reads to get processed before they should. Fixed. From e0b771c23368f0d9f51fdf546d99ec4cf912f102 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 30 Sep 2011 14:11:42 -0400 Subject: [PATCH 172/363] Pre-sorting output sorting variable regions independently to make the output pre-sorted. Major speedup. From 05fba6f23ad7c9bb116a03d3243a0e56c1e08d7b Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 30 Sep 2011 15:44:30 -0400 Subject: [PATCH 173/363] Clipping ends inside deletion and before insertion fixed. --- .../broadinstitute/sting/utils/sam/ReadUtils.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 3c389fd4c7..49900f0da4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -879,9 +879,20 @@ else if (refCoord > read.getAlignmentEnd()) { if (endsWithinCigar) fallsInsideDeletion = cigarElement.getOperator() == CigarOperator.DELETION; - // if we end outside the current cigar element, we need to check if the next element is a 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(); + + // if it's an insertion, we need to clip the whole insertion before looking at the next element + if (nextCigarElement.getOperator() == CigarOperator.INSERTION) { + readBases += nextCigarElement.getLength(); + if (!cigarElementIterator.hasNext()) + throw new ReviewedStingException("Reference coordinate corresponds to a non-existent base in the read. This should never happen -- call Mauricio"); + + nextCigarElement = cigarElementIterator.next(); + } + + // if it's a deletion, we will pass the information on to be handled downstream. fallsInsideDeletion = nextCigarElement.getOperator() == CigarOperator.DELETION; } From 84160bd83fd92cc6f89f715f41976d2d1512cfb0 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 15:50:54 -0400 Subject: [PATCH 174/363] Reorganization of Sample -- Moved Gender and Afflication to separate public enums -- PedReader 90% implemented -- Improve interface cleanup to XReadLines and UserException --- .../sting/gatk/samples/Affection.java | 46 ++++ .../sting/gatk/samples/Gender.java | 34 +++ .../sting/gatk/samples/PedReader.java | 101 ++++----- .../sting/gatk/samples/Sample.java | 72 ++----- .../sting/gatk/samples/SampleDataSource.java | 24 ++- .../beagle/ProduceBeagleInputWalker.java | 4 +- .../gatk/walkers/qc/CountMalesWalker.java | 3 +- .../sting/utils/exceptions/UserException.java | 4 + .../sting/utils/text/XReadLines.java | 6 +- .../reads/DownsamplerBenchmark.java | 3 +- .../sting/gatk/samples/PedReaderUnitTest.java | 201 ++++++++++++++++++ .../sting/gatk/samples/SampleUnitTest.java | 20 +- 12 files changed, 384 insertions(+), 134 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/samples/Gender.java create mode 100644 public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java b/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java new file mode 100644 index 0000000000..de0dba884f --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java @@ -0,0 +1,46 @@ +/* + * 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.samples; + +/** + * Categorical sample trait for association and analysis + * + * Samples can have unknown status, be affected or unaffected by the + * categorical trait, or they can be marked as actually having a + * quantitative trait value (stored in an associated value in the Sample class) + * + * @author Mark DePristo + * @since Sept. 2011 + */ +public enum Affection { + /** Status is unknown */ + UNKNOWN, + /** Suffers from the disease */ + AFFECTED, + /** Unaffected by the disease */ + UNAFFECTED, + /** A quantitative trait: value of the trait is stored elsewhere */ + QUANTITATIVE +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/Gender.java b/public/java/src/org/broadinstitute/sting/gatk/samples/Gender.java new file mode 100644 index 0000000000..6fb44804a4 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Gender.java @@ -0,0 +1,34 @@ +/* + * 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.samples; + +/** +* ENUM of possible human genders: male, female, or unknown +*/ +public enum Gender { + MALE, + FEMALE, + UNKNOWN +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index 6514cffe4d..added09b65 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -32,6 +32,8 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.Reader; import java.util.*; /** @@ -115,10 +117,6 @@ public class PedReader { final static private Set CATAGORICAL_TRAIT_VALUES = new HashSet(Arrays.asList("-9", "0", "1", "2")); final static private String commentMarker = "#"; - private final File source; - private final List records; - - public enum MissingPedFields { NO_FAMILY_ID, NO_PARENTS, @@ -127,8 +125,8 @@ public enum MissingPedFields { } // phenotype - private final static String PHENOTYPE_MISSING_VALUE = "-9"; - private final static String PHENOTYPE_MISSING_VALUE_SECONDARY = "0"; + private final static String MISSING_VALUE1 = "-9"; + private final static String MISSING_VALUE2 = "0"; private final static String PHENOTYPE_UNAFFECTED = "1"; private final static String PHENOTYPE_AFFECTED = "2"; @@ -137,14 +135,15 @@ public enum MissingPedFields { private final static String SEX_FEMALE = "2"; // other=unknown - public PedReader(File source, EnumSet missingFields) throws FileNotFoundException { - this.source = source; - List lines = new XReadLines(source).readLines(); - this.records = parsePedLines(lines, missingFields); - } + public PedReader() { } - private final List parsePedLines(final List lines, EnumSet missingFields) { + public final List parse(File source, EnumSet missingFields, SampleDataSource sampleDB) throws FileNotFoundException { logger.info("Reading PED file " + source + " with missing fields: " + missingFields); + return parse(new FileReader(source), missingFields, sampleDB); + } + + public final List parse(Reader reader, EnumSet missingFields, SampleDataSource sampleDB) { + final List lines = new XReadLines(reader).readLines(); // What are the record offsets? final int familyPos = missingFields.contains(MissingPedFields.NO_FAMILY_ID) ? -1 : 0; @@ -153,7 +152,7 @@ private final List parsePedLines(final List lines, EnumSet parsePedLines(final List lines, EnumSet parsePedLines(final List lines, EnumSet recs = new ArrayList(splits.size()); + final List samples = new ArrayList(splits.size()); for ( final String[] parts : splits ) { String familyID = null, individualID, paternalID = null, maternalID = null; - Sample.Gender sex = Sample.Gender.UNKNOWN; - double quantitativePhenotype = Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE; - Sample.Affection affection = Sample.Affection.UNKNOWN; + Gender sex = Gender.UNKNOWN; + double quantitativePhenotype = Sample.UNSET_QT; + Affection affection = Affection.UNKNOWN; - if ( familyPos != -1 ) familyID = parts[familyPos]; + if ( familyPos != -1 ) familyID = maybeMissing(parts[familyPos]); individualID = parts[samplePos]; - if ( paternalPos != -1 ) paternalID = parts[paternalPos]; - if ( maternalPos != -1 ) maternalID = parts[maternalPos]; + if ( paternalPos != -1 ) paternalID = maybeMissing(parts[paternalPos]); + if ( maternalPos != -1 ) maternalID = maybeMissing(parts[maternalPos]); if ( sexPos != -1 ) { - if ( parts[sexPos].equals(SEX_MALE) ) sex = Sample.Gender.MALE; - else if ( parts[sexPos].equals(SEX_FEMALE) ) sex = Sample.Gender.FEMALE; - else sex = Sample.Gender.UNKNOWN; + if ( parts[sexPos].equals(SEX_MALE) ) sex = Gender.MALE; + else if ( parts[sexPos].equals(SEX_FEMALE) ) sex = Gender.FEMALE; + else sex = Gender.UNKNOWN; } if ( phenotypePos != -1 ) { if ( isQT ) { - if ( parts[phenotypePos].equals(PHENOTYPE_MISSING_VALUE) ) - affection = Sample.Affection.UNKNOWN; + if ( parts[phenotypePos].equals(MISSING_VALUE1) ) + affection = Affection.UNKNOWN; else { - affection = Sample.Affection.QUANTITATIVE; + affection = Affection.QUANTITATIVE; quantitativePhenotype = Double.valueOf(parts[phenotypePos]); } } else { - if ( parts[phenotypePos].equals(PHENOTYPE_MISSING_VALUE) ) affection = Sample.Affection.UNKNOWN; - else if ( parts[phenotypePos].equals(PHENOTYPE_MISSING_VALUE_SECONDARY) ) affection = Sample.Affection.UNKNOWN; - else if ( parts[phenotypePos].equals(PHENOTYPE_UNAFFECTED) ) affection = Sample.Affection.UNAFFECTED; - else if ( parts[phenotypePos].equals(PHENOTYPE_AFFECTED) ) affection = Sample.Affection.AFFECTED; + if ( parts[phenotypePos].equals(MISSING_VALUE1) ) affection = Affection.UNKNOWN; + else if ( parts[phenotypePos].equals(MISSING_VALUE2) ) affection = Affection.UNKNOWN; + else if ( parts[phenotypePos].equals(PHENOTYPE_UNAFFECTED) ) affection = Affection.UNAFFECTED; + else if ( parts[phenotypePos].equals(PHENOTYPE_AFFECTED) ) affection = Affection.AFFECTED; else throw new ReviewedStingException("Unexpected phenotype type " + parts[phenotypePos] + " at line " + lineNo); } } - recs.add(new PedRecord(familyID, individualID, paternalID, maternalID, sex, quantitativePhenotype, affection)); - + final Sample s = new Sample(familyID, sampleDB, individualID, paternalID, maternalID, sex, affection, quantitativePhenotype); + samples.add(s); + sampleDB.addSample(s); lineNo++; } - return Collections.unmodifiableList(recs); - } - - public List getRecords() { - return records; + sampleDB.validate(samples); + return samples; } - public void fillSampleDB(SampleDataSource db) { - for ( final PedRecord rec : getRecords() ) { - } - } -} - -class PedRecord { - final String familyID, individualID, paternalID, maternalID; - final Sample.Gender sex; - final double quantitativePhenotype; - final Sample.Affection affection; - - PedRecord(final String familyID, final String individualID, - final String paternalID, final String maternalID, - final Sample.Gender sex, - final double quantitativePhenotype, final Sample.Affection affection) { - this.familyID = familyID; - this.individualID = individualID; - this.paternalID = paternalID; - this.maternalID = maternalID; - this.sex = sex; - this.quantitativePhenotype = quantitativePhenotype; - this.affection = affection; + private final static String maybeMissing(final String string) { + if ( string.equals(MISSING_VALUE1) || string.equals(MISSING_VALUE2) ) + return null; + else + return string; } } \ No newline at end of file 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 db905a16e9..3426cf678d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -10,37 +10,18 @@ */ public class Sample implements java.io.Serializable { final private String familyID, paternalID, maternalID; - final private Sample.Gender gender; + final private Gender gender; final private double quantitativePhenotype; - final private Sample.Affection affection; + final private Affection affection; final private String ID; final private SampleDataSource dataSource; + final private Map properties = new HashMap(); - // todo -- conditionally add the property map -- should be empty by default - private final Map properties = new HashMap(); - - public enum Gender { - MALE, - FEMALE, - UNKNOWN - } - - public enum Affection { - /** Status is unknown */ - UNKNOWN, - /** Suffers from the disease */ - AFFECTED, - /** Unaffected by the disease */ - UNAFFECTED, - /** A quantitative trait: value of the trait is stored elsewhere */ - QUANTITATIVE - } - - public final static double UNSET_QUANTITIATIVE_TRAIT_VALUE = Double.NaN; + public final static double UNSET_QT = Double.NaN; public Sample(final String ID, final SampleDataSource dataSource, final String familyID, final String paternalID, final String maternalID, - final Gender gender, final double quantitativePhenotype, final Affection affection) { + final Gender gender, final Affection affection, final double quantitativePhenotype) { this.familyID = familyID; this.paternalID = paternalID; this.maternalID = maternalID; @@ -51,20 +32,31 @@ public Sample(final String ID, final SampleDataSource dataSource, this.dataSource = dataSource; } + protected Sample(final String ID, + final String familyID, final String paternalID, final String maternalID, + final Gender gender, final Affection affection, final double quantitativePhenotype) { + this(ID, null, familyID, paternalID, maternalID, gender, affection, quantitativePhenotype); + } + + protected Sample(final String ID, + final String familyID, final String paternalID, final String maternalID, + final Gender gender, final Affection affection) { + this(ID, null, familyID, paternalID, maternalID, gender, affection, UNSET_QT); + } + + public Sample(final String ID, final SampleDataSource dataSource, final String familyID, final String paternalID, final String maternalID, final Gender gender) { - this(ID, dataSource, familyID, paternalID, maternalID, gender, - UNSET_QUANTITIATIVE_TRAIT_VALUE, Affection.UNKNOWN); + this(ID, dataSource, familyID, paternalID, maternalID, gender, Affection.UNKNOWN, UNSET_QT); } - public Sample(final String ID, final SampleDataSource dataSource, final double quantitativePhenotype, final Affection affection) { - this(ID, dataSource, null, null, null, Gender.UNKNOWN, quantitativePhenotype, affection); + public Sample(final String ID, final SampleDataSource dataSource, final Affection affection, final double quantitativePhenotype) { + this(ID, dataSource, null, null, null, Gender.UNKNOWN, affection, quantitativePhenotype); } public Sample(String id, SampleDataSource dataSource) { - this(id, dataSource, - null, null, null, - Gender.UNKNOWN, UNSET_QUANTITIATIVE_TRAIT_VALUE, Affection.UNKNOWN); + this(id, dataSource, null, null, null, + Gender.UNKNOWN, Affection.UNKNOWN, UNSET_QT); } // ------------------------------------------------------------------------------------- @@ -77,7 +69,6 @@ public String getID() { return ID; } - public String getFamilyID() { return familyID; } @@ -157,21 +148,4 @@ public Object getExtraPropertyValue(final String key) { public boolean hasExtraProperty(String key) { return properties.containsKey(key); } - -// @Override -// public boolean equals(Object o) { -// if (this == o) return true; -// if (o == null || getClass() != o.getClass()) return false; -// -// Sample sample = (Sample) o; -// if (ID != null ? !ID.equals(sample.ID) : sample.ID != null) return false; -// if (properties != null ? !properties.equals(sample.properties) : sample.properties != null) return false; -// -// return true; -// } -// -// @Override -// public int hashCode() { -// return ID != null ? ID.hashCode() : "".hashCode(); -// } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index d5285271b4..e0d159947c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -55,7 +55,7 @@ public SampleDataSource(final SAMFileHeader header, final List sampleFiles /** * Hallucinates sample objects for all the samples in the SAM file and stores them */ - public SampleDataSource addSamples(SAMFileHeader header) { + protected SampleDataSource addSamples(SAMFileHeader header) { for (String sampleName : SampleUtils.getSAMFileSamples(header)) { if (getSample(sampleName) == null) { Sample newSample = new Sample(sampleName, this); @@ -65,7 +65,7 @@ public SampleDataSource addSamples(SAMFileHeader header) { return this; } - public SampleDataSource addSamples(final List sampleFiles) { + protected SampleDataSource addSamples(final List sampleFiles) { // add files consecutively for (File file : sampleFiles) { addSamples(file); @@ -77,7 +77,7 @@ public SampleDataSource addSamples(final List sampleFiles) { * Parse one sample file and integrate it with samples that are already there * Fail quickly if we find any errors in the file */ - public SampleDataSource addSamples(File sampleFile) { + protected SampleDataSource addSamples(File sampleFile) { return this; } @@ -85,7 +85,7 @@ public SampleDataSource addSamples(File sampleFile) { * Add a sample to the collection * @param sample to be added */ - private SampleDataSource addSample(Sample sample) { + protected SampleDataSource addSample(Sample sample) { samples.put(sample.getID(), sample); return this; } @@ -138,8 +138,6 @@ public Sample getSample(final Genotype g) { // // -------------------------------------------------------------------------------- - - /** * Get number of sample objects * @return size of samples map @@ -209,4 +207,18 @@ public Set getSamples(Collection sampleNameList) { } return samples; } + + // -------------------------------------------------------------------------------- + // + // Validation + // + // -------------------------------------------------------------------------------- + + public final void validate() { + validate(getSamples()); + } + + public final void validate(Collection samplesToCheck) { + + } } 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 cdf1913f75..b722220f9c 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 @@ -31,7 +31,7 @@ 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.Gender; import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.gatk.walkers.variantrecalibration.VQSRCalibrationCurve; import org.broadinstitute.sting.utils.GenomeLoc; @@ -248,7 +248,7 @@ public void writeBeagleOutput(VariantContext preferredVC, VariantContext otherVC Map preferredGenotypes = preferredVC.getGenotypes(); Map otherGenotypes = goodSite(otherVC) ? otherVC.getGenotypes() : null; for ( String sample : samples ) { - boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSample(sample).getGender() == Sample.Gender.MALE; + boolean isMaleOnChrX = CHECK_IS_MALE_ON_CHR_X && getSample(sample).getGender() == Gender.MALE; Genotype genotype; boolean isValidation; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java index d776fe4158..24c06d1013 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountMalesWalker.java @@ -27,6 +27,7 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.ReadMetaDataTracker; +import org.broadinstitute.sting.gatk.samples.Gender; import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.gatk.walkers.DataSource; import org.broadinstitute.sting.gatk.walkers.ReadWalker; @@ -41,7 +42,7 @@ public class CountMalesWalker extends ReadWalker { public Integer map(ReferenceContext ref, SAMRecord read, ReadMetaDataTracker tracker) { Sample sample = getSampleDB().getSample(read); - return sample.getGender() == Sample.Gender.MALE ? 1 : 0; + return sample.getGender() == Gender.MALE ? 1 : 0; } public Integer reduceInit() { return 0; } 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 70f7387f47..77f1ed6c06 100755 --- a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java +++ b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java @@ -215,6 +215,10 @@ public MalformedFile(File f, String message, Exception e) { super(String.format("File %s is malformed: %s caused by %s", f.getAbsolutePath(), message, e.getMessage())); } + public MalformedFile(String name, String message) { + super(String.format("File associated with name %s is malformed: %s", name, message)); + } + public MalformedFile(String name, String message, Exception e) { super(String.format("File associated with name %s is malformed: %s caused by %s", name, message, e.getMessage())); } diff --git a/public/java/src/org/broadinstitute/sting/utils/text/XReadLines.java b/public/java/src/org/broadinstitute/sting/utils/text/XReadLines.java index 52b6f3b011..49e9ddf524 100644 --- a/public/java/src/org/broadinstitute/sting/utils/text/XReadLines.java +++ b/public/java/src/org/broadinstitute/sting/utils/text/XReadLines.java @@ -99,9 +99,9 @@ public XReadLines(final InputStream inputStream) throws FileNotFoundException { * * @param reader */ - public XReadLines(final BufferedReader reader, final boolean trimWhitespace) { + public XReadLines(final Reader reader, final boolean trimWhitespace) { try { - this.in = reader; + this.in = new BufferedReader(reader); nextline = readNextLine(); this.trimWhitespace = trimWhitespace; } catch(IOException e) { @@ -109,7 +109,7 @@ public XReadLines(final BufferedReader reader, final boolean trimWhitespace) { } } - public XReadLines(final BufferedReader reader) throws FileNotFoundException { + public XReadLines(final Reader reader) { this(reader, true); } 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 41d7a23c6c..0d5734d432 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 @@ -40,6 +40,7 @@ import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.baq.BAQ; +import java.io.File; import java.util.Collections; import java.util.Iterator; @@ -85,8 +86,6 @@ public void timeDownsampling(int reps) { (byte)0); GenomeLocParser genomeLocParser = new GenomeLocParser(reader.getFileHeader().getSequenceDictionary()); - SampleDataSource sampleDataSource = new SampleDataSource().addSamples(reader.getFileHeader()); - // Filter unmapped reads. TODO: is this always strictly necessary? Who in the GATK normally filters these out? Iterator readIterator = new FilteringIterator(reader.iterator(),new UnmappedReadFilter()); LocusIteratorByState locusIteratorByState = new LocusIteratorByState(readIterator,readProperties,genomeLocParser, LocusIteratorByState.sampleListForSAMWithoutReadGroups()); diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java new file mode 100644 index 0000000000..1cad634ddb --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -0,0 +1,201 @@ +/* + * 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.samples; + +import org.apache.log4j.Logger; +import org.broadinstitute.sting.BaseTest; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.StringReader; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +/** + * UnitTest for PedReader + * + * @author Mark DePristo + * @since 2011 + */ +public class PedReaderUnitTest extends BaseTest { + private static Logger logger = Logger.getLogger(PedReaderUnitTest.class); + + private class PedReaderTest extends TestDataProvider { + public String fileContents; + public List expectedSamples; + + private PedReaderTest(final String name, final List expectedSamples, final String fileContents) { + super(PedReaderTest.class, name); + this.fileContents = fileContents; + this.expectedSamples = expectedSamples; + } + } + +// Family ID +// Individual ID +// Paternal ID +// Maternal ID +// Sex (1=male; 2=female; other=unknown) +// Phenotype +// +// -9 missing +// 0 missing +// 1 unaffected +// 2 affected + + @DataProvider(name = "readerTest") + public Object[][] createPEDFiles() { + new PedReaderTest("singleRecordMale", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED)), + "fam1 kid 0 0 1 1"); + + new PedReaderTest("singleRecordFemale", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.FEMALE, Affection.UNAFFECTED)), + "fam1 kid 0 0 2 1"); + + new PedReaderTest("singleRecordMissingGender", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.UNKNOWN, Affection.UNKNOWN)), + "fam1 kid 0 0 0 0"); + + // Affection + new PedReaderTest("singleRecordAffected", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.MALE, Affection.AFFECTED)), + "fam1 kid 0 0 1 2"); + + new PedReaderTest("singleRecordUnaffected", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED)), + "fam1 kid 0 0 1 1"); + + new PedReaderTest("singleRecordMissingAffection-9", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.MALE, Affection.UNKNOWN)), + "fam1 kid 0 0 1 -9"); + + new PedReaderTest("singleRecordMissingAffection0", + Arrays.asList(new Sample("kid", "fam1", null, null, Gender.MALE, Affection.UNKNOWN)), + "fam1 kid 0 0 1 0"); + + new PedReaderTest("multipleUnrelated", + Arrays.asList( + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.AFFECTED), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.UNAFFECTED)), + String.format("%s\n%s", + "fam1 s1 0 0 1 1", + "fam2 s2 0 0 2 2")); + + new PedReaderTest("explicitTrio", + Arrays.asList( + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.AFFECTED)), + String.format("%s\n%s\n%s", + "fam1 kid dad mom 1 2", + "fam1 dad 0 0 1 1", + "fam1 mom 0 0 2 2")); + + new PedReaderTest("implicitTrio", + Arrays.asList( + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), + "fam1 kid dad mom 1 1"); + + new PedReaderTest("partialTrio", + Arrays.asList( + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), + String.format("%s\n%s", + "fam1 kid dad mom 1 2", + "fam1 dad 0 0 1 1")); + + new PedReaderTest("bigPedigree", + Arrays.asList( + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", "granddad1", "grandma1", Gender.MALE, Affection.UNAFFECTED), + new Sample("granddad1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("grandma1", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN), + new Sample("mom", "fam1", "granddad2", "grandma2", Gender.FEMALE, Affection.AFFECTED), + new Sample("granddad2", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("grandma2", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), + String.format("%s\n%s\n%s", + "fam1 kid dad mom 1 2", + "fam1 dad granddad1 grandma1 1 1", + "fam1 mom granddad2 grandma2 2 2")); + + // Quantitative trait + new PedReaderTest("QuantitativeTrait", + Arrays.asList( + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + String.format("%s\n%s", + "fam1 s1 0 0 1 1", + "fam2 s2 0 0 2 10.0")); + + new PedReaderTest("QuantitativeTraitWithMissing", + Arrays.asList( + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN, Sample.UNSET_QT), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + String.format("%s\n%s", + "fam1 s1 0 0 1 -9", + "fam2 s2 0 0 2 10.0")); + + new PedReaderTest("QuantitativeTraitOnlyInts", + Arrays.asList( + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + String.format("%s\n%s", + "fam1 s1 0 0 1 1", + "fam2 s2 0 0 2 10")); + + return PedReaderTest.getTests(PedReaderTest.class); + } + + private static final void runTest(PedReaderTest test, String myFileContents, EnumSet missing) { + logger.warn("Test " + test); + PedReader reader = new PedReader(); + SampleDataSource sampleDB = new SampleDataSource(); + List readSamples = reader.parse(new StringReader(myFileContents), missing, sampleDB); + Assert.assertEquals(test.expectedSamples, readSamples, "Parsed incorrect number of samples"); + } + + @Test(enabled = true, dataProvider = "readerTest") + public void testPedReader(PedReaderTest test) { + runTest(test, test.fileContents, EnumSet.noneOf(PedReader.MissingPedFields.class)); + } + + @Test(enabled = true, dataProvider = "readerTest", dependsOnMethods = "testPedReader") + public void testPedReaderWithComments(PedReaderTest test) { + runTest(test, "#comment\n" + test.fileContents, EnumSet.noneOf(PedReader.MissingPedFields.class)); + } + + @Test(enabled = true, dataProvider = "readerTest", dependsOnMethods = "testPedReader") + public void testPedReaderWithMissing(PedReaderTest test) { + // todo -- test MISSING by splicing strings + //runTest(test, "#comment\n" + test.fileContents, EnumSet.noneOf(PedReader.MissingPedFields.class)); + } + +} \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index e8d1772b8f..279319edba 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -22,17 +22,17 @@ public class SampleUnitTest extends BaseTest { public void init() { db = new SampleDataSource(); - fam1A = new Sample("1A", db, "fam1", "1B", "1C", Sample.Gender.UNKNOWN); - fam1B = new Sample("1B", db, "fam1", null, null, Sample.Gender.MALE); - fam1C = new Sample("1C", db, "fam1", null, null, Sample.Gender.FEMALE); + fam1A = new Sample("1A", db, "fam1", "1B", "1C", Gender.UNKNOWN); + fam1B = new Sample("1B", db, "fam1", null, null, Gender.MALE); + fam1C = new Sample("1C", db, "fam1", null, null, Gender.FEMALE); s1 = new Sample("s1", db); s2 = new Sample("s2", db); - trait1 = new Sample("t1", db, Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE, Sample.Affection.AFFECTED); - trait2 = new Sample("t2", db, Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE, Sample.Affection.UNAFFECTED); - trait3 = new Sample("t3", db, Sample.UNSET_QUANTITIATIVE_TRAIT_VALUE, Sample.Affection.UNKNOWN); - trait4 = new Sample("t4", db, 1.0, Sample.Affection.QUANTITATIVE); + trait1 = new Sample("t1", db, Affection.AFFECTED, Sample.UNSET_QT); + trait2 = new Sample("t2", db, Affection.UNAFFECTED, Sample.UNSET_QT); + trait3 = new Sample("t3", db, Affection.UNKNOWN, Sample.UNSET_QT); + trait4 = new Sample("t4", db, Affection.QUANTITATIVE, 1.0); } /** @@ -47,8 +47,8 @@ public void specialGettersTest() { @Test() public void testGenders() { - Assert.assertTrue(fam1A.getGender() == Sample.Gender.UNKNOWN); - Assert.assertTrue(fam1B.getGender() == Sample.Gender.MALE); - Assert.assertTrue(fam1C.getGender() == Sample.Gender.FEMALE); + Assert.assertTrue(fam1A.getGender() == Gender.UNKNOWN); + Assert.assertTrue(fam1B.getGender() == Gender.MALE); + Assert.assertTrue(fam1C.getGender() == Gender.FEMALE); } } From c7898a9be78130b865cc8d6a8c3e3429ac71eb00 Mon Sep 17 00:00:00 2001 From: Andrey Sivachenko Date: Fri, 30 Sep 2011 16:40:21 -0400 Subject: [PATCH 175/363] inconsequential change in string constants printed into the vcf which noone uses anyway... --- .../gatk/walkers/indels/SomaticIndelDetectorWalker.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 8bba8eac2e..5b10a79c6f 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 @@ -265,7 +265,7 @@ private Set getVCFHeaderInfo() { Set headerInfo = new HashSet(); // first, the basic info - headerInfo.add(new VCFHeaderLine("source", "IndelGenotyperV2")); + headerInfo.add(new VCFHeaderLine("source", "SomaticIndelDetector")); headerInfo.add(new VCFHeaderLine("reference", getToolkit().getArguments().referenceFile.getName())); // FORMAT and INFO fields @@ -283,10 +283,10 @@ private Set getVCFHeaderInfo() { args.addAll(getToolkit().getFilters()); Map commandLineArgs = getToolkit().getApproximateCommandLineArguments(args); for ( Map.Entry commandLineArg : commandLineArgs.entrySet() ) - headerInfo.add(new VCFHeaderLine(String.format("IGv2_%s", commandLineArg.getKey()), commandLineArg.getValue())); + headerInfo.add(new VCFHeaderLine(String.format("SID_%s", commandLineArg.getKey()), commandLineArg.getValue())); // also, the list of input bams for ( String fileName : getToolkit().getArguments().samFiles ) - headerInfo.add(new VCFHeaderLine("IGv2_bam_file_used", fileName)); + headerInfo.add(new VCFHeaderLine("SID_bam_file_used", fileName)); return headerInfo; } From dd75ad9f49ed3a07dded0cc4eab7318cd60fda1e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 30 Sep 2011 18:03:34 -0400 Subject: [PATCH 176/363] 95% PedReader -- Passes significiant unit tests -- Implicit sample creation for mom / dad when you create single samples -- Continuing cleanup of Sample and SampleDataSource --- .../sting/gatk/samples/PedReader.java | 24 ++++++- .../sting/gatk/samples/Sample.java | 37 ++++++++++- .../sting/gatk/samples/SampleDataSource.java | 6 +- .../sting/gatk/samples/PedReaderUnitTest.java | 66 ++++++++++++++----- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index added09b65..e581c37189 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -160,13 +160,13 @@ public final List parse(Reader reader, EnumSet missing final List splits = new ArrayList(lines.size()); for ( final String line : lines ) { if ( line.startsWith(commentMarker)) continue; - String[] parts = line.split("\\W+"); + String[] parts = line.split("\\s+"); if ( parts.length != nExpectedFields ) throw new UserException.MalformedFile(reader.toString(), "Bad PED line " + lineNo + ": wrong number of fields"); if ( phenotypePos != -1 ) { - isQT = isQT || CATAGORICAL_TRAIT_VALUES.contains(parts[phenotypePos]); + isQT = isQT || ! CATAGORICAL_TRAIT_VALUES.contains(parts[phenotypePos]); } splits.add(parts); @@ -211,12 +211,21 @@ public final List parse(Reader reader, EnumSet missing } } - final Sample s = new Sample(familyID, sampleDB, individualID, paternalID, maternalID, sex, affection, quantitativePhenotype); + final Sample s = new Sample(individualID, sampleDB, familyID, paternalID, maternalID, sex, affection, quantitativePhenotype); samples.add(s); sampleDB.addSample(s); lineNo++; } + for ( final Sample sample : new ArrayList(samples) ) { + Sample dad = maybeAddImplicitSample(sampleDB, sample.getPaternalID(), sample.getFamilyID(), Gender.MALE); + if ( dad != null ) samples.add(dad); + + Sample mom = maybeAddImplicitSample(sampleDB, sample.getMaternalID(), sample.getFamilyID(), Gender.FEMALE); + if ( mom != null ) samples.add(mom); + } + + sampleDB.validate(samples); return samples; } @@ -227,4 +236,13 @@ private final static String maybeMissing(final String string) { else return string; } + + private final Sample maybeAddImplicitSample(SampleDataSource sampleDB, final String id, final String familyID, final Gender gender) { + if ( id != null && sampleDB.getSample(id) == null ) { + Sample s = new Sample(id, sampleDB, familyID, null, null, gender, Affection.UNKNOWN, Sample.UNSET_QT); + sampleDB.addSample(s); + return s; + } else + return null; + } } \ No newline at end of file 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 3426cf678d..0a5043013d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -117,8 +117,11 @@ public Gender getGender() { return gender; } - public String getFamilyId() { - return familyID; + @Override + public String toString() { + return String.format("Sample %s fam=%s dad=%s mom=%s gender=%s affection=%s qt=%s props=%s", + getID(), getFamilyID(), getPaternalID(), getMaternalID(), getGender(), getAffection(), + getQuantitativePhenotype(), getExtraProperties()); } // ------------------------------------------------------------------------------------- @@ -148,4 +151,34 @@ public Object getExtraPropertyValue(final String key) { public boolean hasExtraProperty(String key) { return properties.containsKey(key); } + + @Override + public int hashCode() { + return ID.hashCode(); + } + + @Override + public boolean equals(final Object o) { + if(o == null) + return false; + if(o instanceof Sample) { + Sample otherSample = (Sample)o; + return ID.equals(otherSample.ID) && + equalOrNull(familyID, otherSample.familyID) && + equalOrNull(paternalID, otherSample.paternalID) && + equalOrNull(maternalID, otherSample.maternalID) && + equalOrNull(gender, otherSample.gender) && + equalOrNull(quantitativePhenotype, otherSample.quantitativePhenotype) && + equalOrNull(affection, otherSample.affection) && + equalOrNull(properties, otherSample.properties); + } + return false; + } + + private final static boolean equalOrNull(final Object o1, final Object o2) { + if ( o1 == null ) + return o2 == null; + else + return o2 == null ? false : o1.equals(o2); + } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java index e0d159947c..b85759de2a 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java @@ -156,8 +156,8 @@ public Set getFamily(String familyId) { HashSet familyMembers = new HashSet(); for (Sample sample : samples.values()) { - if (sample.getFamilyId() != null) { - if (sample.getFamilyId().equals(familyId)) + if (sample.getFamilyID() != null) { + if (sample.getFamilyID().equals(familyId)) familyMembers.add(sample); } } @@ -172,7 +172,7 @@ public Set getFamily(String familyId) { */ public Set getChildren(Sample sample) { HashSet children = new HashSet(); - for (Sample familyMember : getFamily(sample.getFamilyId())) { + for (Sample familyMember : getFamily(sample.getFamilyID())) { if (familyMember.getMother() == sample || familyMember.getFather() == sample) { children.add(familyMember); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index 1cad634ddb..5eec0e8c85 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -33,6 +33,7 @@ import java.io.StringReader; import java.util.Arrays; import java.util.EnumSet; +import java.util.HashSet; import java.util.List; /** @@ -47,6 +48,7 @@ public class PedReaderUnitTest extends BaseTest { private class PedReaderTest extends TestDataProvider { public String fileContents; public List expectedSamples; + EnumSet missing; private PedReaderTest(final String name, final List expectedSamples, final String fileContents) { super(PedReaderTest.class, name); @@ -55,6 +57,19 @@ private PedReaderTest(final String name, final List expectedSamples, fin } } + private class PedReaderTestMissing extends TestDataProvider { + public String fileContents; + public List expectedSamples; + EnumSet missing; + + private PedReaderTestMissing(final String name, EnumSet missing, final List expectedSamples, final String fileContents) { + super(PedReaderTest.class, name); + this.fileContents = fileContents; + this.expectedSamples = expectedSamples; + this.missing = missing; + } + } + // Family ID // Individual ID // Paternal ID @@ -100,9 +115,9 @@ public Object[][] createPEDFiles() { new PedReaderTest("multipleUnrelated", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.AFFECTED), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.UNAFFECTED)), - String.format("%s\n%s", + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.AFFECTED)), + String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 2")); @@ -111,7 +126,7 @@ public Object[][] createPEDFiles() { new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.AFFECTED)), - String.format("%s\n%s\n%s", + String.format("%s%n%s%n%s", "fam1 kid dad mom 1 2", "fam1 dad 0 0 1 1", "fam1 mom 0 0 2 2")); @@ -121,14 +136,14 @@ public Object[][] createPEDFiles() { new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), - "fam1 kid dad mom 1 1"); + "fam1 kid dad mom 1 2"); new PedReaderTest("partialTrio", Arrays.asList( new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), - String.format("%s\n%s", + String.format("%s%n%s", "fam1 kid dad mom 1 2", "fam1 dad 0 0 1 1")); @@ -141,7 +156,7 @@ public Object[][] createPEDFiles() { new Sample("mom", "fam1", "granddad2", "grandma2", Gender.FEMALE, Affection.AFFECTED), new Sample("granddad2", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), new Sample("grandma2", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), - String.format("%s\n%s\n%s", + String.format("%s%n%s%n%s", "fam1 kid dad mom 1 2", "fam1 dad granddad1 grandma1 1 1", "fam1 mom granddad2 grandma2 2 2")); @@ -151,7 +166,7 @@ public Object[][] createPEDFiles() { Arrays.asList( new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), - String.format("%s\n%s", + String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 10.0")); @@ -159,7 +174,7 @@ public Object[][] createPEDFiles() { Arrays.asList( new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN, Sample.UNSET_QT), new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), - String.format("%s\n%s", + String.format("%s%n%s", "fam1 s1 0 0 1 -9", "fam2 s2 0 0 2 10.0")); @@ -167,7 +182,7 @@ public Object[][] createPEDFiles() { Arrays.asList( new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), - String.format("%s\n%s", + String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 10")); @@ -179,7 +194,7 @@ private static final void runTest(PedReaderTest test, String myFileContents, Enu PedReader reader = new PedReader(); SampleDataSource sampleDB = new SampleDataSource(); List readSamples = reader.parse(new StringReader(myFileContents), missing, sampleDB); - Assert.assertEquals(test.expectedSamples, readSamples, "Parsed incorrect number of samples"); + Assert.assertEquals(new HashSet(test.expectedSamples), new HashSet(readSamples), "Parsed incorrect number of samples"); } @Test(enabled = true, dataProvider = "readerTest") @@ -189,13 +204,32 @@ public void testPedReader(PedReaderTest test) { @Test(enabled = true, dataProvider = "readerTest", dependsOnMethods = "testPedReader") public void testPedReaderWithComments(PedReaderTest test) { - runTest(test, "#comment\n" + test.fileContents, EnumSet.noneOf(PedReader.MissingPedFields.class)); + runTest(test, String.format("#comment%n%s", test.fileContents), EnumSet.noneOf(PedReader.MissingPedFields.class)); } - @Test(enabled = true, dataProvider = "readerTest", dependsOnMethods = "testPedReader") - public void testPedReaderWithMissing(PedReaderTest test) { - // todo -- test MISSING by splicing strings - //runTest(test, "#comment\n" + test.fileContents, EnumSet.noneOf(PedReader.MissingPedFields.class)); + @DataProvider(name = "readerTestMissing") + public Object[][] createPEDFilesWithMissing() { + new PedReaderTestMissing("trioMissingFam", EnumSet.of(PedReader.MissingPedFields.NO_FAMILY_ID), + Arrays.asList( + new Sample("kid", null, "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", null, null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("mom", null, null, null, Gender.FEMALE, Affection.AFFECTED)), + String.format("%s%n%s%n%s", + "kid dad mom 1 2", + "dad 0 0 1 1", + "mom 0 0 2 2")); + + return PedReaderTestMissing.getTests(PedReaderTestMissing.class); } + @Test(enabled = true, dataProvider = "readerTestMissing", dependsOnMethods = "testPedReader") + public void testPedReaderWithMissing(PedReaderTest test) { +// public enum MissingPedFields { +// NO_FAMILY_ID, +// NO_PARENTS, +// NO_SEX, +// NO_PHENOTYPE +// } +// runTest(test, sliceContents(0, test.fileContents), EnumSet.of(PedReader.MissingPedFields.NO_FAMILY_ID)); + } } \ No newline at end of file From bf6a3a65320cf5d1fa9aed9f3db5154579921443 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Sun, 2 Oct 2011 22:33:46 -0400 Subject: [PATCH 177/363] Added framework to do batch CigarClip Testing *NOTE: This commit has not been compiled! --- .../sting/utils/clipreads/ReadClipperUnitTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 1415379db2..38eee762a1 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -62,6 +62,20 @@ public void init() { readClipper = new ReadClipper(read); } + private void testHardClipCigarByReadCoordinate( SAMRecord read, String inputCigar, String expectedCigar, int expectedStart, int expectedStop) { + read.setCigar(TextCigarCodec.getSingleton().decode(inputCigar) ); + SAMRecord clipped = readClipper.hardClipByReadCoordinates(expectedStart,expectedStop); + Assert.assertEquals(clipped.getCigarString(), expectedCigar, "Clipped Cigar string is different than expected"); + } +/* + private void testReadBasesAndQuals(SAMRecord 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 testHardClipBothEndsByReferenceCoordinates() { logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); From 52f670c8b86787d9e72c5e179fe82d37c5e4729f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 06:12:58 -0700 Subject: [PATCH 178/363] 100% version of PedReader -- Passes all unit tests -- Added unit tests for missing fields --- .../sting/gatk/samples/PedReader.java | 20 ++- .../sting/gatk/samples/PedReaderUnitTest.java | 164 +++++++++++------- 2 files changed, 114 insertions(+), 70 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index e581c37189..27b9181de6 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -117,13 +117,17 @@ public class PedReader { final static private Set CATAGORICAL_TRAIT_VALUES = new HashSet(Arrays.asList("-9", "0", "1", "2")); final static private String commentMarker = "#"; - public enum MissingPedFields { + public enum MissingPedField { NO_FAMILY_ID, NO_PARENTS, NO_SEX, NO_PHENOTYPE } + protected enum Field { + FAMILY_ID, INDIVIDUAL_ID, PATERNAL_ID, MATERNAL_ID, GENDER, PHENOTYPE + } + // phenotype private final static String MISSING_VALUE1 = "-9"; private final static String MISSING_VALUE2 = "0"; @@ -137,21 +141,21 @@ public enum MissingPedFields { public PedReader() { } - public final List parse(File source, EnumSet missingFields, SampleDataSource sampleDB) throws FileNotFoundException { + public final List parse(File source, EnumSet missingFields, SampleDataSource sampleDB) throws FileNotFoundException { logger.info("Reading PED file " + source + " with missing fields: " + missingFields); return parse(new FileReader(source), missingFields, sampleDB); } - public final List parse(Reader reader, EnumSet missingFields, SampleDataSource sampleDB) { + public final List parse(Reader reader, EnumSet missingFields, SampleDataSource sampleDB) { final List lines = new XReadLines(reader).readLines(); // What are the record offsets? - final int familyPos = missingFields.contains(MissingPedFields.NO_FAMILY_ID) ? -1 : 0; + final int familyPos = missingFields.contains(MissingPedField.NO_FAMILY_ID) ? -1 : 0; final int samplePos = familyPos + 1; - final int paternalPos = missingFields.contains(MissingPedFields.NO_PARENTS) ? -1 : samplePos + 1; - final int maternalPos = missingFields.contains(MissingPedFields.NO_PARENTS) ? -1 : paternalPos + 1; - final int sexPos = missingFields.contains(MissingPedFields.NO_SEX) ? -1 : Math.max(maternalPos, samplePos) + 1; - final int phenotypePos = missingFields.contains(MissingPedFields.NO_PHENOTYPE) ? -1 : Math.max(sexPos, Math.max(maternalPos, samplePos)) + 1; + final int paternalPos = missingFields.contains(MissingPedField.NO_PARENTS) ? -1 : samplePos + 1; + final int maternalPos = missingFields.contains(MissingPedField.NO_PARENTS) ? -1 : paternalPos + 1; + final int sexPos = missingFields.contains(MissingPedField.NO_SEX) ? -1 : Math.max(maternalPos, samplePos) + 1; + final int phenotypePos = missingFields.contains(MissingPedField.NO_PHENOTYPE) ? -1 : Math.max(sexPos, Math.max(maternalPos, samplePos)) + 1; final int nExpectedFields = MathUtils.arrayMaxInt(Arrays.asList(samplePos, paternalPos, maternalPos, sexPos, phenotypePos)) + 1; // go through once and determine properties diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index 5eec0e8c85..35be45bc78 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -26,15 +26,14 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.Utils; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.StringReader; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; +import java.lang.reflect.Array; +import java.util.*; /** * UnitTest for PedReader @@ -48,7 +47,7 @@ public class PedReaderUnitTest extends BaseTest { private class PedReaderTest extends TestDataProvider { public String fileContents; public List expectedSamples; - EnumSet missing; + EnumSet missing; private PedReaderTest(final String name, final List expectedSamples, final String fileContents) { super(PedReaderTest.class, name); @@ -57,19 +56,6 @@ private PedReaderTest(final String name, final List expectedSamples, fin } } - private class PedReaderTestMissing extends TestDataProvider { - public String fileContents; - public List expectedSamples; - EnumSet missing; - - private PedReaderTestMissing(final String name, EnumSet missing, final List expectedSamples, final String fileContents) { - super(PedReaderTest.class, name); - this.fileContents = fileContents; - this.expectedSamples = expectedSamples; - this.missing = missing; - } - } - // Family ID // Individual ID // Paternal ID @@ -115,17 +101,17 @@ public Object[][] createPEDFiles() { new PedReaderTest("multipleUnrelated", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.AFFECTED)), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.AFFECTED)), String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 2")); new PedReaderTest("explicitTrio", Arrays.asList( - new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), - new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), - new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.AFFECTED)), + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.AFFECTED)), String.format("%s%n%s%n%s", "fam1 kid dad mom 1 2", "fam1 dad 0 0 1 1", @@ -133,29 +119,29 @@ public Object[][] createPEDFiles() { new PedReaderTest("implicitTrio", Arrays.asList( - new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), - new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), - new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), "fam1 kid dad mom 1 2"); new PedReaderTest("partialTrio", Arrays.asList( - new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), - new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), - new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), String.format("%s%n%s", "fam1 kid dad mom 1 2", "fam1 dad 0 0 1 1")); new PedReaderTest("bigPedigree", Arrays.asList( - new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), - new Sample("dad", "fam1", "granddad1", "grandma1", Gender.MALE, Affection.UNAFFECTED), - new Sample("granddad1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), - new Sample("grandma1", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN), - new Sample("mom", "fam1", "granddad2", "grandma2", Gender.FEMALE, Affection.AFFECTED), - new Sample("granddad2", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), - new Sample("grandma2", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", "granddad1", "grandma1", Gender.MALE, Affection.UNAFFECTED), + new Sample("granddad1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("grandma1", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN), + new Sample("mom", "fam1", "granddad2", "grandma2", Gender.FEMALE, Affection.AFFECTED), + new Sample("granddad2", "fam1", null, null, Gender.MALE, Affection.UNKNOWN), + new Sample("grandma2", "fam1", null, null, Gender.FEMALE, Affection.UNKNOWN)), String.format("%s%n%s%n%s", "fam1 kid dad mom 1 2", "fam1 dad granddad1 grandma1 1 1", @@ -164,24 +150,24 @@ public Object[][] createPEDFiles() { // Quantitative trait new PedReaderTest("QuantitativeTrait", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 10.0")); new PedReaderTest("QuantitativeTraitWithMissing", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN, Sample.UNSET_QT), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN, Sample.UNSET_QT), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), String.format("%s%n%s", "fam1 s1 0 0 1 -9", "fam2 s2 0 0 2 10.0")); new PedReaderTest("QuantitativeTraitOnlyInts", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 10")); @@ -189,7 +175,7 @@ public Object[][] createPEDFiles() { return PedReaderTest.getTests(PedReaderTest.class); } - private static final void runTest(PedReaderTest test, String myFileContents, EnumSet missing) { + private static final void runTest(PedReaderTest test, String myFileContents, EnumSet missing) { logger.warn("Test " + test); PedReader reader = new PedReader(); SampleDataSource sampleDB = new SampleDataSource(); @@ -199,37 +185,91 @@ private static final void runTest(PedReaderTest test, String myFileContents, Enu @Test(enabled = true, dataProvider = "readerTest") public void testPedReader(PedReaderTest test) { - runTest(test, test.fileContents, EnumSet.noneOf(PedReader.MissingPedFields.class)); + runTest(test, test.fileContents, EnumSet.noneOf(PedReader.MissingPedField.class)); } @Test(enabled = true, dataProvider = "readerTest", dependsOnMethods = "testPedReader") public void testPedReaderWithComments(PedReaderTest test) { - runTest(test, String.format("#comment%n%s", test.fileContents), EnumSet.noneOf(PedReader.MissingPedFields.class)); + runTest(test, String.format("#comment%n%s", test.fileContents), EnumSet.noneOf(PedReader.MissingPedField.class)); + } + + // ----------------------------------------------------------------- + // missing format field tests + // ----------------------------------------------------------------- + + private class PedReaderTestMissing extends TestDataProvider { + public EnumSet missingDesc; + public EnumSet missingFields; + public final String fileContents; + public Sample expected; + + + private PedReaderTestMissing(final String name, final String fileContents, + EnumSet missingDesc, + EnumSet missingFields, + final Sample expected) { + super(PedReaderTestMissing.class, name); + this.fileContents = fileContents; + this.missingDesc = missingDesc; + this.missingFields = missingFields; + this.expected = expected; + } } @DataProvider(name = "readerTestMissing") public Object[][] createPEDFilesWithMissing() { - new PedReaderTestMissing("trioMissingFam", EnumSet.of(PedReader.MissingPedFields.NO_FAMILY_ID), - Arrays.asList( - new Sample("kid", null, "dad", "mom", Gender.MALE, Affection.AFFECTED), - new Sample("dad", null, null, null, Gender.MALE, Affection.UNAFFECTED), - new Sample("mom", null, null, null, Gender.FEMALE, Affection.AFFECTED)), - String.format("%s%n%s%n%s", - "kid dad mom 1 2", - "dad 0 0 1 1", - "mom 0 0 2 2")); + + new PedReaderTestMissing("missingFam", + "fam1 kid dad mom 1 2", + EnumSet.of(PedReader.MissingPedField.NO_FAMILY_ID), + EnumSet.of(PedReader.Field.FAMILY_ID), + new Sample("kid", null, "dad", "mom", Gender.MALE, Affection.AFFECTED)); + + new PedReaderTestMissing("missingParents", + "fam1 kid dad mom 1 2", + EnumSet.of(PedReader.MissingPedField.NO_PARENTS), + EnumSet.of(PedReader.Field.PATERNAL_ID, PedReader.Field.MATERNAL_ID), + new Sample("kid", "fam1", null, null, Gender.MALE, Affection.AFFECTED)); + + new PedReaderTestMissing("missingSex", + "fam1 kid dad mom 1 2", + EnumSet.of(PedReader.MissingPedField.NO_SEX), + EnumSet.of(PedReader.Field.GENDER), + new Sample("kid", "fam1", "dad", "mom", Gender.UNKNOWN, Affection.AFFECTED)); + + new PedReaderTestMissing("missingPhenotype", + "fam1 kid dad mom 1 2", + EnumSet.of(PedReader.MissingPedField.NO_PHENOTYPE), + EnumSet.of(PedReader.Field.PHENOTYPE), + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.UNKNOWN)); + + new PedReaderTestMissing("missingEverythingButGender", + "fam1 kid dad mom 1 2", + EnumSet.of(PedReader.MissingPedField.NO_PHENOTYPE, PedReader.MissingPedField.NO_PARENTS, PedReader.MissingPedField.NO_FAMILY_ID), + EnumSet.of(PedReader.Field.FAMILY_ID, PedReader.Field.PATERNAL_ID, PedReader.Field.MATERNAL_ID, PedReader.Field.PHENOTYPE), + new Sample("kid", null, null, null, Gender.MALE, Affection.UNKNOWN)); + return PedReaderTestMissing.getTests(PedReaderTestMissing.class); } @Test(enabled = true, dataProvider = "readerTestMissing", dependsOnMethods = "testPedReader") - public void testPedReaderWithMissing(PedReaderTest test) { -// public enum MissingPedFields { -// NO_FAMILY_ID, -// NO_PARENTS, -// NO_SEX, -// NO_PHENOTYPE -// } -// runTest(test, sliceContents(0, test.fileContents), EnumSet.of(PedReader.MissingPedFields.NO_FAMILY_ID)); + public void testPedReaderWithMissing(PedReaderTestMissing test) { + final String contents = sliceContents(test.missingFields, test.fileContents); + logger.warn("Test " + test); + PedReader reader = new PedReader(); + SampleDataSource sampleDB = new SampleDataSource(); + reader.parse(new StringReader(contents), test.missingDesc, sampleDB); + final Sample missingSample = sampleDB.getSample("kid"); + Assert.assertEquals(test.expected, missingSample, "Missing field value not expected value for " + test); + } + + private final static String sliceContents(EnumSet missingFieldsSet, String full) { + List parts = new ArrayList(Arrays.asList(full.split("\\s+"))); + final List missingFields = new ArrayList(missingFieldsSet); + Collections.reverse(missingFields); + for ( PedReader.Field field : missingFields ) + parts.remove(field.ordinal()); + return Utils.join("\t", parts); } } \ No newline at end of file From 0604ce55d1b4d418b30d3ece80b740fbb6a9e57d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 09:19:58 -0700 Subject: [PATCH 179/363] PedReader support for ; separated lines, not only newline --- .../sting/gatk/samples/PedReader.java | 10 ++++++---- .../sting/gatk/samples/PedReaderUnitTest.java | 14 ++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index 27b9181de6..72c5ec12c4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -30,10 +30,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.text.XReadLines; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.Reader; +import java.io.*; import java.util.*; /** @@ -146,6 +143,11 @@ public final List parse(File source, EnumSet missingFie return parse(new FileReader(source), missingFields, sampleDB); } + public final List parse(final String source, EnumSet missingFields, SampleDataSource sampleDB) { + logger.warn("Reading PED string: \"" + source + "\" with missing fields: " + missingFields); + return parse(new StringReader(source.replace(";", String.format("%n"))), missingFields, sampleDB); + } + public final List parse(Reader reader, EnumSet missingFields, SampleDataSource sampleDB) { final List lines = new XReadLines(reader).readLines(); diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index 35be45bc78..e68d169eae 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -179,7 +179,7 @@ private static final void runTest(PedReaderTest test, String myFileContents, Enu logger.warn("Test " + test); PedReader reader = new PedReader(); SampleDataSource sampleDB = new SampleDataSource(); - List readSamples = reader.parse(new StringReader(myFileContents), missing, sampleDB); + List readSamples = reader.parse(myFileContents, missing, sampleDB); Assert.assertEquals(new HashSet(test.expectedSamples), new HashSet(readSamples), "Parsed incorrect number of samples"); } @@ -188,11 +188,18 @@ public void testPedReader(PedReaderTest test) { runTest(test, test.fileContents, EnumSet.noneOf(PedReader.MissingPedField.class)); } - @Test(enabled = true, dataProvider = "readerTest", dependsOnMethods = "testPedReader") + @Test(enabled = true, dataProvider = "readerTest") public void testPedReaderWithComments(PedReaderTest test) { runTest(test, String.format("#comment%n%s", test.fileContents), EnumSet.noneOf(PedReader.MissingPedField.class)); } + @Test(enabled = true, dataProvider = "readerTest") + public void testPedReaderWithSemicolons(PedReaderTest test) { + runTest(test, + test.fileContents.replace(String.format("%n"), ";"), + EnumSet.noneOf(PedReader.MissingPedField.class)); + } + // ----------------------------------------------------------------- // missing format field tests // ----------------------------------------------------------------- @@ -218,7 +225,6 @@ private PedReaderTestMissing(final String name, final String fileContents, @DataProvider(name = "readerTestMissing") public Object[][] createPEDFilesWithMissing() { - new PedReaderTestMissing("missingFam", "fam1 kid dad mom 1 2", EnumSet.of(PedReader.MissingPedField.NO_FAMILY_ID), @@ -253,7 +259,7 @@ public Object[][] createPEDFilesWithMissing() { return PedReaderTestMissing.getTests(PedReaderTestMissing.class); } - @Test(enabled = true, dataProvider = "readerTestMissing", dependsOnMethods = "testPedReader") + @Test(enabled = true, dataProvider = "readerTestMissing") public void testPedReaderWithMissing(PedReaderTestMissing test) { final String contents = sliceContents(test.missingFields, test.fileContents); logger.warn("Test " + test); From 93fba06cb545d331df4d2ebb67579987e1581cf7 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 09:30:10 -0700 Subject: [PATCH 180/363] Support for whitespace only lines --- .../org/broadinstitute/sting/gatk/samples/PedReader.java | 4 +++- .../sting/gatk/samples/PedReaderUnitTest.java | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index 72c5ec12c4..648637b093 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -166,7 +166,9 @@ public final List parse(Reader reader, EnumSet missingF final List splits = new ArrayList(lines.size()); for ( final String line : lines ) { if ( line.startsWith(commentMarker)) continue; - String[] parts = line.split("\\s+"); + if ( line.trim().equals("") ) continue; + + final String[] parts = line.split("\\s+"); if ( parts.length != nExpectedFields ) throw new UserException.MalformedFile(reader.toString(), "Bad PED line " + lineNo + ": wrong number of fields"); diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index e68d169eae..16c1d178b5 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -107,6 +107,14 @@ public Object[][] createPEDFiles() { "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 2")); + new PedReaderTest("multipleUnrelatedExtraLine", + Arrays.asList( + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.AFFECTED)), + String.format("%s%n%s%n %n", // note extra newlines and whitespace + "fam1 s1 0 0 1 1", + "fam2 s2 0 0 2 2")); + new PedReaderTest("explicitTrio", Arrays.asList( new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), From 89ac50e86e52bd014f11efd568e73ae52ade6ab2 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 09:33:30 -0700 Subject: [PATCH 181/363] SampleDataSource -> SampleDB --- .../sting/gatk/GenomeAnalysisEngine.java | 12 +++++----- .../sting/gatk/executive/WindowMaker.java | 1 - .../sting/gatk/samples/PedReader.java | 8 +++---- .../sting/gatk/samples/Sample.java | 22 +++++++++---------- .../{SampleDataSource.java => SampleDB.java} | 14 ++++++------ .../sting/gatk/walkers/Walker.java | 4 ++-- .../providers/LocusViewTemplate.java | 1 - .../reads/DownsamplerBenchmark.java | 2 -- .../sting/gatk/samples/PedReaderUnitTest.java | 5 ++--- .../samples/SampleDataSourceUnitTest.java | 7 +----- .../sting/gatk/samples/SampleUnitTest.java | 4 ++-- 11 files changed, 34 insertions(+), 46 deletions(-) rename public/java/src/org/broadinstitute/sting/gatk/samples/{SampleDataSource.java => SampleDB.java} (94%) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 52544fbd28..a9a7de75f9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -34,8 +34,7 @@ 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.samples.Sample; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.SampleDB; import org.broadinstitute.sting.gatk.executive.MicroScheduler; import org.broadinstitute.sting.gatk.filters.FilterManager; import org.broadinstitute.sting.gatk.filters.ReadFilter; @@ -51,7 +50,6 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.interval.IntervalUtils; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.File; import java.util.*; @@ -88,7 +86,7 @@ public class GenomeAnalysisEngine { /** * Accessor for sample metadata */ - private SampleDataSource sampleDataSource = null; + private SampleDB sampleDB = null; /** * Accessor for sharded reference-ordered data. @@ -688,7 +686,7 @@ protected void initializeDataSources() { for (ReadFilter filter : filters) filter.initialize(this); - sampleDataSource = new SampleDataSource(getSAMFileHeader(), argCollection.sampleFiles); + sampleDB = new SampleDB(getSAMFileHeader(), argCollection.sampleFiles); // set the sequence dictionary of all of Tribble tracks to the sequence dictionary of our reference rodDataSources = getReferenceOrderedDataSources(referenceMetaDataFiles,referenceDataSource.getReference().getSequenceDictionary(),genomeLocParser,argCollection.unsafe); @@ -953,8 +951,8 @@ public ReadMetrics getCumulativeMetrics() { // // ------------------------------------------------------------------------------------- - public SampleDataSource getSampleDB() { - return this.sampleDataSource; + public SampleDB getSampleDB() { + return this.sampleDB; } public Map getApproximateCommandLineArguments(Object... argumentProviders) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java index 825a81e64c..d1f5d80daf 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/WindowMaker.java @@ -4,7 +4,6 @@ import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.iterators.LocusIterator; import org.broadinstitute.sting.gatk.iterators.LocusIteratorByState; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index 648637b093..d697498be1 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -138,17 +138,17 @@ protected enum Field { public PedReader() { } - public final List parse(File source, EnumSet missingFields, SampleDataSource sampleDB) throws FileNotFoundException { + public final List parse(File source, EnumSet missingFields, SampleDB sampleDB) throws FileNotFoundException { logger.info("Reading PED file " + source + " with missing fields: " + missingFields); return parse(new FileReader(source), missingFields, sampleDB); } - public final List parse(final String source, EnumSet missingFields, SampleDataSource sampleDB) { + public final List parse(final String source, EnumSet missingFields, SampleDB sampleDB) { logger.warn("Reading PED string: \"" + source + "\" with missing fields: " + missingFields); return parse(new StringReader(source.replace(";", String.format("%n"))), missingFields, sampleDB); } - public final List parse(Reader reader, EnumSet missingFields, SampleDataSource sampleDB) { + public final List parse(Reader reader, EnumSet missingFields, SampleDB sampleDB) { final List lines = new XReadLines(reader).readLines(); // What are the record offsets? @@ -245,7 +245,7 @@ private final static String maybeMissing(final String string) { return string; } - private final Sample maybeAddImplicitSample(SampleDataSource sampleDB, final String id, final String familyID, final Gender gender) { + private final Sample maybeAddImplicitSample(SampleDB sampleDB, final String id, final String familyID, final Gender gender) { if ( id != null && sampleDB.getSample(id) == null ) { Sample s = new Sample(id, sampleDB, familyID, null, null, gender, Affection.UNKNOWN, Sample.UNSET_QT); sampleDB.addSample(s); 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 0a5043013d..e68d92a9f7 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -14,12 +14,12 @@ public class Sample implements java.io.Serializable { final private double quantitativePhenotype; final private Affection affection; final private String ID; - final private SampleDataSource dataSource; + final private SampleDB infoDB; final private Map properties = new HashMap(); public final static double UNSET_QT = Double.NaN; - public Sample(final String ID, final SampleDataSource dataSource, + public Sample(final String ID, final SampleDB infoDB, final String familyID, final String paternalID, final String maternalID, final Gender gender, final Affection affection, final double quantitativePhenotype) { this.familyID = familyID; @@ -29,7 +29,7 @@ public Sample(final String ID, final SampleDataSource dataSource, this.quantitativePhenotype = quantitativePhenotype; this.affection = affection; this.ID = ID; - this.dataSource = dataSource; + this.infoDB = infoDB; } protected Sample(final String ID, @@ -45,17 +45,17 @@ protected Sample(final String ID, } - public Sample(final String ID, final SampleDataSource dataSource, + public Sample(final String ID, final SampleDB infoDB, final String familyID, final String paternalID, final String maternalID, final Gender gender) { - this(ID, dataSource, familyID, paternalID, maternalID, gender, Affection.UNKNOWN, UNSET_QT); + this(ID, infoDB, familyID, paternalID, maternalID, gender, Affection.UNKNOWN, UNSET_QT); } - public Sample(final String ID, final SampleDataSource dataSource, final Affection affection, final double quantitativePhenotype) { - this(ID, dataSource, null, null, null, Gender.UNKNOWN, affection, quantitativePhenotype); + public Sample(final String ID, final SampleDB infoDB, final Affection affection, final double quantitativePhenotype) { + this(ID, infoDB, null, null, null, Gender.UNKNOWN, affection, quantitativePhenotype); } - public Sample(String id, SampleDataSource dataSource) { - this(id, dataSource, null, null, null, + public Sample(String id, SampleDB infoDB) { + this(id, infoDB, null, null, null, Gender.UNKNOWN, Affection.UNKNOWN, UNSET_QT); } @@ -98,7 +98,7 @@ public double getQuantitativePhenotype() { * @return sample object with relationship mother, if exists, or null */ public Sample getMother() { - return dataSource.getSample(maternalID); + return infoDB.getSample(maternalID); } /** @@ -106,7 +106,7 @@ public Sample getMother() { * @return sample object with relationship father, if exists, or null */ public Sample getFather() { - return dataSource.getSample(paternalID); + return infoDB.getSample(paternalID); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java similarity index 94% rename from public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java rename to public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java index b85759de2a..6a2ec2ac4c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -24,7 +24,7 @@ * wants to access sample data, it asks GenomeAnalysis to fetch this data from its SampleDataSource. * */ -public class SampleDataSource { +public class SampleDB { /** * This is where Sample objects are stored. Samples are usually accessed by their ID, which is unique, so * this is stored as a HashMap. @@ -34,11 +34,11 @@ public class SampleDataSource { /** * Constructor takes both a SAM header and sample files because the two must be integrated. */ - public SampleDataSource() { + public SampleDB() { } - public SampleDataSource(final SAMFileHeader header, final List sampleFiles) { + public SampleDB(final SAMFileHeader header, final List sampleFiles) { this(); addSamples(header); addSamples(sampleFiles); @@ -55,7 +55,7 @@ public SampleDataSource(final SAMFileHeader header, final List sampleFiles /** * Hallucinates sample objects for all the samples in the SAM file and stores them */ - protected SampleDataSource addSamples(SAMFileHeader header) { + protected SampleDB addSamples(SAMFileHeader header) { for (String sampleName : SampleUtils.getSAMFileSamples(header)) { if (getSample(sampleName) == null) { Sample newSample = new Sample(sampleName, this); @@ -65,7 +65,7 @@ protected SampleDataSource addSamples(SAMFileHeader header) { return this; } - protected SampleDataSource addSamples(final List sampleFiles) { + protected SampleDB addSamples(final List sampleFiles) { // add files consecutively for (File file : sampleFiles) { addSamples(file); @@ -77,7 +77,7 @@ protected SampleDataSource addSamples(final List sampleFiles) { * Parse one sample file and integrate it with samples that are already there * Fail quickly if we find any errors in the file */ - protected SampleDataSource addSamples(File sampleFile) { + protected SampleDB addSamples(File sampleFile) { return this; } @@ -85,7 +85,7 @@ protected SampleDataSource addSamples(File sampleFile) { * Add a sample to the collection * @param sample to be added */ - protected SampleDataSource addSample(Sample sample) { + protected SampleDB addSample(Sample sample) { samples.put(sample.getID(), sample); return this; } 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 f67dace2cc..792fef9c32 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/Walker.java @@ -31,7 +31,7 @@ import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.filters.MalformedReadFilter; import org.broadinstitute.sting.gatk.samples.Sample; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; +import org.broadinstitute.sting.gatk.samples.SampleDB; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.collections.Pair; @@ -88,7 +88,7 @@ protected SAMSequenceDictionary getMasterSequenceDictionary() { return getToolkit().getMasterSequenceDictionary(); } - protected SampleDataSource getSampleDB() { + protected SampleDB getSampleDB() { return getToolkit().getSampleDB(); } 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 8b226101ab..2adb4864ca 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 @@ -8,7 +8,6 @@ import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; import org.broadinstitute.sting.gatk.datasources.reads.Shard; import org.broadinstitute.sting.gatk.executive.WindowMaker; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; 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 0d5734d432..5ee373e4ff 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 @@ -33,14 +33,12 @@ import org.broadinstitute.sting.gatk.ReadProperties; import org.broadinstitute.sting.gatk.arguments.GATKArgumentCollection; import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; -import org.broadinstitute.sting.gatk.samples.SampleDataSource; import org.broadinstitute.sting.gatk.filters.ReadFilter; import org.broadinstitute.sting.gatk.filters.UnmappedReadFilter; import org.broadinstitute.sting.gatk.iterators.LocusIteratorByState; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.baq.BAQ; -import java.io.File; import java.util.Collections; import java.util.Iterator; diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index 16c1d178b5..c14995dca5 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -32,7 +32,6 @@ import org.testng.annotations.Test; import java.io.StringReader; -import java.lang.reflect.Array; import java.util.*; /** @@ -186,7 +185,7 @@ public Object[][] createPEDFiles() { private static final void runTest(PedReaderTest test, String myFileContents, EnumSet missing) { logger.warn("Test " + test); PedReader reader = new PedReader(); - SampleDataSource sampleDB = new SampleDataSource(); + SampleDB sampleDB = new SampleDB(); List readSamples = reader.parse(myFileContents, missing, sampleDB); Assert.assertEquals(new HashSet(test.expectedSamples), new HashSet(readSamples), "Parsed incorrect number of samples"); } @@ -272,7 +271,7 @@ public void testPedReaderWithMissing(PedReaderTestMissing test) { final String contents = sliceContents(test.missingFields, test.fileContents); logger.warn("Test " + test); PedReader reader = new PedReader(); - SampleDataSource sampleDB = new SampleDataSource(); + SampleDB sampleDB = new SampleDB(); reader.parse(new StringReader(contents), test.missingDesc, sampleDB); final Sample missingSample = sampleDB.getSample("kid"); Assert.assertEquals(test.expected, missingSample, "Missing field value not expected value for " + test); diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java index 3d40d4de8f..90dd8e36ee 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java @@ -1,12 +1,7 @@ package org.broadinstitute.sting.gatk.samples; import net.sf.samtools.SAMFileHeader; -import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.Genotype; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; -import org.testng.Assert; import org.broadinstitute.sting.BaseTest; -import org.broadinstitute.sting.utils.exceptions.StingException; import org.testng.annotations.Test; @@ -29,6 +24,6 @@ public class SampleDataSourceUnitTest extends BaseTest { // make sure samples are created from the SAM file correctly @Test() public void loadSAMSamplesTest() { - SampleDataSource s = new SampleDataSource(header, Collections.emptyList()); + SampleDB s = new SampleDB(header, Collections.emptyList()); } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index 279319edba..372b59353d 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -13,14 +13,14 @@ * Time: 8:21:00 AM */ public class SampleUnitTest extends BaseTest { - SampleDataSource db; + SampleDB db; static Sample fam1A, fam1B, fam1C; static Sample s1, s2; static Sample trait1, trait2, trait3, trait4; @BeforeClass public void init() { - db = new SampleDataSource(); + db = new SampleDB(); fam1A = new Sample("1A", db, "fam1", "1B", "1C", Gender.UNKNOWN); fam1B = new Sample("1B", db, "fam1", null, null, Gender.MALE); From 8ee0f91904433e89cef8a3a3599504e5a71390da Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 09:50:01 -0700 Subject: [PATCH 182/363] Remove residual processing tracker arguments --- .../arguments/GATKArgumentCollection.java | 34 ------------------- .../executive/HierarchicalMicroScheduler.java | 5 --- 2 files changed, 39 deletions(-) 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 fd39d46b0f..9ce402cf30 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -218,26 +218,6 @@ public static DownsamplingMethod getDefaultDownsamplingMethod() { // distributed GATK arguments // // -------------------------------------------------------------------------------------------------------------- - @Element(required=false) - @Argument(fullName="processingTracker",shortName="C",doc="A lockable, shared file for coordinating distributed GATK runs",required=false) - @Hidden - public File processingTrackerFile = null; - - @Element(required=false) - @Argument(fullName="restartProcessingTracker",shortName="RPT",doc="Should we delete the processing tracker file at startup?",required=false) - @Hidden - public boolean restartProcessingTracker = false; - - @Element(required=false) - @Argument(fullName="processingTrackerStatusFile",shortName="CSF",doc="If provided, a detailed accounting of the state of the process tracker is written to this file. For debugging, only",required=false) - @Hidden - public File processingTrackerStatusFile = null; - - @Element(required=false) - @Argument(fullName="processingTrackerID",shortName="CID",doc="If provided, an integer ID (starting at 1) indicating a unique id for this process within the distributed GATK group",required=false) - @Hidden - public int processTrackerID = -1; - @Element(required = false) @Argument(fullName="allow_intervals_with_unindexed_bam",doc="Allow interval processing with an unsupported BAM. NO INTEGRATION TESTS are available. Use at your own risk.",required=false) @Hidden @@ -405,20 +385,6 @@ public boolean equals(GATKArgumentCollection other) { (other.performanceLog != null && !other.performanceLog.equals(this.performanceLog))) return false; - if ((other.processingTrackerFile == null && this.processingTrackerFile != null) || - (other.processingTrackerFile != null && !other.processingTrackerFile.equals(this.processingTrackerFile))) - return false; - - if ((other.processingTrackerStatusFile == null && this.processingTrackerStatusFile != null) || - (other.processingTrackerStatusFile != null && !other.processingTrackerStatusFile.equals(this.processingTrackerStatusFile))) - return false; - - if ( restartProcessingTracker != other.restartProcessingTracker ) - return false; - - if ( processTrackerID != other.processTrackerID ) - return false; - if (allowIntervalsWithUnindexedBAM != other.allowIntervalsWithUnindexedBAM) return false; 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 3b9e353114..a07f735fac 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java @@ -84,12 +84,7 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar */ protected HierarchicalMicroScheduler(GenomeAnalysisEngine engine, Walker walker, SAMDataSource reads, IndexedFastaSequenceFile reference, Collection rods, int nThreadsToUse ) { super(engine, walker, reads, reference, rods); - this.threadPool = Executors.newFixedThreadPool(nThreadsToUse); - - if (engine.getArguments().processingTrackerFile != null) { - throw new UserException.BadArgumentValue("-C", "Distributed GATK calculations currently not supported in multi-threaded mode. Complain to Mark depristo@broadinstitute.org to implement and test this code path"); - } } public Object execute( Walker walker, ShardStrategy shardStrategy ) { From c3eff7451abfcb91366f11158d7eac60fd120199 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 3 Oct 2011 14:20:39 -0400 Subject: [PATCH 183/363] Found a small inefficiency while profiling: we were still using String.split instead of ParsingUtils.split to break up array values in the INFO field. There was a noticeable (albeit not big) difference in the change when reading sites only files. --- .../utils/codecs/vcf/AbstractVCFCodec.java | 30 +++++++++++-------- .../sting/utils/codecs/vcf/VCFConstants.java | 1 + 2 files changed, 19 insertions(+), 12 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 43b07476da..c86b91b793 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 @@ -36,6 +36,7 @@ public abstract class AbstractVCFCodec implements FeatureCodec, NameAwareCodec, // 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 @@ -351,23 +352,28 @@ private Map parseInfo(String infoField, String id) { if ( infoField.indexOf("\t") != -1 || infoField.indexOf(" ") != -1 ) generateException("The VCF specification does not allow for whitespace in the INFO field"); - int infoValueSplitSize = ParsingUtils.split(infoField, infoValueArray, VCFConstants.INFO_FIELD_SEPARATOR_CHAR); - for (int i = 0; i < infoValueSplitSize; i++) { + 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 = infoValueArray[i].indexOf("="); + int eqI = infoFieldArray[i].indexOf("="); if ( eqI != -1 ) { - key = infoValueArray[i].substring(0, eqI); - String str = infoValueArray[i].substring(eqI+1, infoValueArray[i].length()); - - // lets see if the string contains a , separator - if ( str.contains(",") ) - value = Arrays.asList(str.split(",")); - else - value = str; + 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 = infoValueArray[i]; + key = infoFieldArray[i]; value = true; } diff --git a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFConstants.java b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFConstants.java index 91cf86c702..8e9d989cc5 100755 --- a/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFConstants.java +++ b/public/java/src/org/broadinstitute/sting/utils/codecs/vcf/VCFConstants.java @@ -71,6 +71,7 @@ public final class VCFConstants { 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 = ';'; From dd71884b0c095fc57ff3dbb1a06961ac771c97ee Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 12:08:07 -0700 Subject: [PATCH 184/363] On path to SampleDB engine integration -- PedReader tag parser -- Separation of SampleDBBuilder from SampleDB (now immutable) -- Removed old sample engine arguments --- .../arguments/GATKArgumentCollection.java | 29 +++-- .../sting/gatk/samples/PedReader.java | 56 +++++++- .../gatk/samples/PedigreeValidationType.java | 34 +++++ .../sting/gatk/samples/SampleDB.java | 54 ++------ .../sting/gatk/samples/SampleDBBuilder.java | 121 ++++++++++++++++++ .../sting/gatk/samples/PedReaderUnitTest.java | 66 ++++++++++ ...rceUnitTest.java => SampleDBUnitTest.java} | 4 +- 7 files changed, 304 insertions(+), 60 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java rename public/java/test/org/broadinstitute/sting/gatk/samples/{SampleDataSourceUnitTest.java => SampleDBUnitTest.java} (83%) 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 9ce402cf30..c27bb26d9a 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -32,6 +32,7 @@ import org.broadinstitute.sting.gatk.DownsampleType; import org.broadinstitute.sting.gatk.DownsamplingMethod; import org.broadinstitute.sting.gatk.phonehome.GATKRunReport; +import org.broadinstitute.sting.gatk.samples.PedigreeValidationType; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.interval.IntervalMergingRule; @@ -44,10 +45,7 @@ import java.io.File; import java.io.InputStream; import java.io.PrintStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author aaron @@ -72,11 +70,6 @@ public GATKArgumentCollection() { @Input(fullName = "input_file", shortName = "I", doc = "SAM or BAM file(s)", required = false) public List samFiles = new ArrayList(); - // parameters and their defaults - @ElementList(required = false) - @Argument(fullName = "sample_metadata", shortName = "SM", doc = "Sample file(s) in JSON format", required = false) - public List sampleFiles = new ArrayList(); - @Element(required = false) @Argument(fullName = "read_buffer_size", shortName = "rbs", doc="Number of reads per SAM file to buffer in memory", required = false) public Integer readBufferSize = null; @@ -215,9 +208,25 @@ public static DownsamplingMethod getDefaultDownsamplingMethod() { // -------------------------------------------------------------------------------------------------------------- // - // distributed GATK arguments + // PED (pedigree) support // // -------------------------------------------------------------------------------------------------------------- + + /** + * MARK: add documentation details + */ + @Argument(fullName="pedigree", shortName = "ped", doc="Pedigree file / string for samples",required=false) + public List pedigreeData = Collections.emptyList(); + + @Argument(fullName="pedigreeValidationType", shortName = "pedValidationType", doc="How strict should we be in validating the pedigree information?",required=false) + public PedigreeValidationType pedigreeValidationType = PedigreeValidationType.STRICT; + + // -------------------------------------------------------------------------------------------------------------- + // + // BAM indexing and sharding arguments + // + // -------------------------------------------------------------------------------------------------------------- + @Element(required = false) @Argument(fullName="allow_intervals_with_unindexed_bam",doc="Allow interval processing with an unsupported BAM. NO INTEGRATION TESTS are available. Use at your own risk.",required=false) @Hidden diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index d697498be1..ec49b0f60e 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -114,10 +114,42 @@ public class PedReader { final static private Set CATAGORICAL_TRAIT_VALUES = new HashSet(Arrays.asList("-9", "0", "1", "2")); final static private String commentMarker = "#"; + /** + * An enum that specifies which, if any, of the standard PED fields are + * missing from the input records. For example, suppose we have the full record: + * + * "fam1 kid dad mom 1 2" + * + * indicating a male affected child. This can be parsed with the -ped x.ped argument + * to the GATK. Suppose we only have: + * + * "fam1 kid 1" + * + * we can parse the reduced version of this record with -ped:NO_PARENTS,NO_PHENOTYPE x.ped + */ public enum MissingPedField { + /** + * The PED records do not have the first (FAMILY_ID) argument. The family id + * will be set to null / empty. + */ NO_FAMILY_ID, + + /** + * The PED records do not have either the paternal or maternal IDs, so + * the corresponding IDs are set to null. + */ NO_PARENTS, + + /** + * The PED records do not have the GENDER field, so the sex of each + * sample will be set to UNKNOWN. + */ NO_SEX, + + /** + * The PED records do not have the PHENOTYPE field, so the phenotype + * of each sample will be set to UNKNOWN. + */ NO_PHENOTYPE } @@ -233,8 +265,6 @@ public final List parse(Reader reader, EnumSet missingF if ( mom != null ) samples.add(mom); } - - sampleDB.validate(samples); return samples; } @@ -253,4 +283,26 @@ private final Sample maybeAddImplicitSample(SampleDB sampleDB, final String id, } else return null; } + + /** + * Parses a list of tags from the command line, assuming it comes from the GATK Engine + * tags, and returns the corresponding EnumSet. + * + * @param arg the actual engine arg, used for the UserException if there's an error + * @param tags a list of string tags that should be converted to the MissingPedField value + * @return + */ + public static final EnumSet parseMissingFieldTags(final Object arg, final List tags) { + final EnumSet missingFields = EnumSet.noneOf(MissingPedField.class); + + for ( final String tag : tags ) { + try { + missingFields.add(MissingPedField.valueOf(tag)); + } catch ( IllegalArgumentException e ) { + throw new UserException.BadArgumentValue(arg.toString(), "Unknown tag " + tag + " allowed values are " + MissingPedField.values()); + } + } + + return missingFields; + } } \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java new file mode 100644 index 0000000000..8a1a4f225c --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java @@ -0,0 +1,34 @@ +/* + * 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.samples; + +/** +* +*/ +public enum PedigreeValidationType { + STRICT, + LINIENT, + SILENT, +} 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 6a2ec2ac4c..75b37d758b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -38,51 +38,9 @@ public SampleDB() { } - public SampleDB(final SAMFileHeader header, final List sampleFiles) { - this(); - addSamples(header); - addSamples(sampleFiles); - } - - // -------------------------------------------------------------------------------- - // - // Functions for adding samples to the DB - // - // TODO: these should be protected, really - // - // -------------------------------------------------------------------------------- - - /** - * Hallucinates sample objects for all the samples in the SAM file and stores them - */ - protected SampleDB addSamples(SAMFileHeader header) { - for (String sampleName : SampleUtils.getSAMFileSamples(header)) { - if (getSample(sampleName) == null) { - Sample newSample = new Sample(sampleName, this); - samples.put(sampleName, newSample); - } - } - return this; - } - - protected SampleDB addSamples(final List sampleFiles) { - // add files consecutively - for (File file : sampleFiles) { - addSamples(file); - } - return this; - } - /** - * Parse one sample file and integrate it with samples that are already there - * Fail quickly if we find any errors in the file - */ - protected SampleDB addSamples(File sampleFile) { - return this; - } - - /** - * Add a sample to the collection + * Protected function to add a single sample to the database + * * @param sample to be added */ protected SampleDB addSample(Sample sample) { @@ -215,10 +173,14 @@ public Set getSamples(Collection sampleNameList) { // -------------------------------------------------------------------------------- public final void validate() { - validate(getSamples()); + validate(getSamples(), PedigreeValidationType.STRICT); } - public final void validate(Collection samplesToCheck) { + public final void validate(PedigreeValidationType validationType) { + validate(getSamples(), validationType); + } + public final void validate(Collection samplesToCheck, PedigreeValidationType validationType) { + // todo -- actually do an implementation } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java new file mode 100644 index 0000000000..33bed89d26 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.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.gatk.samples; + +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMReadGroupRecord; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; +import org.broadinstitute.sting.utils.SampleUtils; +import org.broadinstitute.sting.utils.exceptions.StingException; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.variantcontext.Genotype; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; + +/** + * + */ +public class SampleDBBuilder { + PedigreeValidationType validationStrictness; + final SampleDB sampleDB = new SampleDB(); + final GenomeAnalysisEngine engine; + + /** + * Constructor takes both a SAM header and sample files because the two must be integrated. + */ + public SampleDBBuilder(GenomeAnalysisEngine engine, PedigreeValidationType validationStrictness) { + this.engine = engine; + this.validationStrictness = validationStrictness; + } + + /** + * Hallucinates sample objects for all the samples in the SAM file and stores them + */ + public SampleDBBuilder addSamples(SAMFileHeader header) { + for (String sampleName : SampleUtils.getSAMFileSamples(header)) { + if (sampleDB.getSample(sampleName) == null) { + final Sample newSample = new Sample(sampleName, sampleDB); + addSample(newSample); + } + } + return this; + } + + public SampleDBBuilder addSamples(final List pedigreeArguments) { + for (final String ped : pedigreeArguments) { + final File pedFile = new File(ped); + if ( pedFile.exists() ) + addSamples(pedFile); + else + addSamples(ped); + } + + return this; + } + + /** + * Parse one sample file and integrate it with samples that are already there + * Fail quickly if we find any errors in the file + */ + protected SampleDBBuilder addSamples(File sampleFile) { + final PedReader reader = new PedReader(); + + try { + reader.parse(sampleFile, getMissingFields(sampleFile), sampleDB); + } catch ( FileNotFoundException e ) { + throw new UserException.CouldNotReadInputFile(sampleFile, e); + } + + return this; + } + + protected SampleDBBuilder addSamples(final String string) { + final PedReader reader = new PedReader(); + reader.parse(string, getMissingFields(string), sampleDB); + return this; + } + + /** + * Add a sample to the collection + * @param sample to be added + */ + protected SampleDBBuilder addSample(Sample sample) { + sampleDB.addSample(sample); + return this; + } + + public SampleDB getFinalSampleDB() { + sampleDB.validate(validationStrictness); + return sampleDB; + } + + public EnumSet getMissingFields(final Object engineArg) { + final List posTags = engine.getTags(engineArg).getPositionalTags(); + return PedReader.parseMissingFieldTags(engineArg, posTags); + } +} diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index c14995dca5..57bc6cf3b8 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -27,6 +27,7 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.exceptions.UserException; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -285,4 +286,69 @@ private final static String sliceContents(EnumSet missingFields parts.remove(field.ordinal()); return Utils.join("\t", parts); } + + // ----------------------------------------------------------------- + // parsing tags + // ----------------------------------------------------------------- + + private class PedReaderTestTagParsing extends TestDataProvider { + public EnumSet expected; + public final List tags; + + private PedReaderTestTagParsing(final List tags, EnumSet missingDesc) { + super(PedReaderTestTagParsing.class); + this.tags = tags; + this.expected = missingDesc; + + } + } + + @DataProvider(name = "readerTestTagParsing") + public Object[][] createReaderTestTagParsing() { + new PedReaderTestTagParsing( + Collections.emptyList(), + EnumSet.noneOf(PedReader.MissingPedField.class)); + + new PedReaderTestTagParsing( + Arrays.asList("NO_FAMILY_ID"), + EnumSet.of(PedReader.MissingPedField.NO_FAMILY_ID)); + + new PedReaderTestTagParsing( + Arrays.asList("NO_PARENTS"), + EnumSet.of(PedReader.MissingPedField.NO_PARENTS)); + + new PedReaderTestTagParsing( + Arrays.asList("NO_PHENOTYPE"), + EnumSet.of(PedReader.MissingPedField.NO_PHENOTYPE)); + + new PedReaderTestTagParsing( + Arrays.asList("NO_SEX"), + EnumSet.of(PedReader.MissingPedField.NO_SEX)); + + new PedReaderTestTagParsing( + Arrays.asList("NO_SEX", "NO_PHENOTYPE"), + EnumSet.of(PedReader.MissingPedField.NO_SEX, PedReader.MissingPedField.NO_PHENOTYPE)); + + new PedReaderTestTagParsing( + Arrays.asList("NO_SEX", "NO_PHENOTYPE", "NO_PARENTS"), + EnumSet.of(PedReader.MissingPedField.NO_SEX, PedReader.MissingPedField.NO_PHENOTYPE, PedReader.MissingPedField.NO_PARENTS)); + + return PedReaderTestTagParsing.getTests(PedReaderTestTagParsing.class); + } + + @Test(enabled = true, dataProvider = "readerTestTagParsing") + public void testPedReaderTagParsing(PedReaderTestTagParsing test) { + EnumSet parsed = PedReader.parseMissingFieldTags("test", test.tags); + Assert.assertEquals(test.expected, parsed, "Failed to properly parse tags " + test.tags); + } + + @Test(enabled = true, expectedExceptions = UserException.class) + public void testPedReaderTagParsing1() { + EnumSet parsed = PedReader.parseMissingFieldTags("test", Arrays.asList("XXX")); + } + + @Test(enabled = true, expectedExceptions = UserException.class) + public void testPedReaderTagParsing2() { + EnumSet parsed = PedReader.parseMissingFieldTags("test", Arrays.asList("NO_SEX", "XXX")); + } } \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java similarity index 83% rename from public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java rename to public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java index 90dd8e36ee..500d322db5 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDataSourceUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java @@ -14,7 +14,7 @@ * Date: Sep 9, 2010 * Time: 8:21:00 AM */ -public class SampleDataSourceUnitTest extends BaseTest { +public class SampleDBUnitTest extends BaseTest { // this empty header used to instantiate sampledatasource objects private static SAMFileHeader header = new SAMFileHeader(); @@ -24,6 +24,6 @@ public class SampleDataSourceUnitTest extends BaseTest { // make sure samples are created from the SAM file correctly @Test() public void loadSAMSamplesTest() { - SampleDB s = new SampleDB(header, Collections.emptyList()); + //SampleDB s = new SampleDB(header); } } From 2e3dc520882ebe9037f2ff001193c39f3313be4e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 14:41:13 -0700 Subject: [PATCH 185/363] Minor function renaming --- .../sting/gatk/GenomeAnalysisEngine.java | 17 +++++++++++++++-- .../sting/gatk/samples/SampleDBBuilder.java | 11 ++++++++--- .../sting/gatk/samples/PedReaderUnitTest.java | 1 - 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index a9a7de75f9..71e65f2fbb 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -44,6 +44,7 @@ import org.broadinstitute.sting.gatk.refdata.tracks.RMDTrackBuilder; import org.broadinstitute.sting.gatk.refdata.utils.RMDIntervalGenerator; import org.broadinstitute.sting.gatk.refdata.utils.RMDTriplet; +import org.broadinstitute.sting.gatk.samples.SampleDBBuilder; import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.utils.*; import org.broadinstitute.sting.utils.baq.BAQ; @@ -686,10 +687,22 @@ protected void initializeDataSources() { for (ReadFilter filter : filters) filter.initialize(this); - sampleDB = new SampleDB(getSAMFileHeader(), argCollection.sampleFiles); - // set the sequence dictionary of all of Tribble tracks to the sequence dictionary of our reference rodDataSources = getReferenceOrderedDataSources(referenceMetaDataFiles,referenceDataSource.getReference().getSequenceDictionary(),genomeLocParser,argCollection.unsafe); + + // set up sample db + initializeSampleDB(); + } + + /** + * Entry-point function to initialize the samples database from input data and pedigree arguments + */ + private void initializeSampleDB() { + SampleDBBuilder sampleDBBuilder = new SampleDBBuilder(this, argCollection.pedigreeValidationType); + sampleDBBuilder.addSamplesFromSAMHeader(getSAMFileHeader()); + sampleDBBuilder.addSamplesFromSampleNames(SampleUtils.getUniqueSamplesFromRods(this)); + sampleDBBuilder.addSamplesFromPedigreeArgument(argCollection.pedigreeData); + sampleDB = sampleDBBuilder.getFinalSampleDB(); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java index 33bed89d26..fd42a24f40 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java @@ -56,8 +56,12 @@ public SampleDBBuilder(GenomeAnalysisEngine engine, PedigreeValidationType valid /** * Hallucinates sample objects for all the samples in the SAM file and stores them */ - public SampleDBBuilder addSamples(SAMFileHeader header) { - for (String sampleName : SampleUtils.getSAMFileSamples(header)) { + public SampleDBBuilder addSamplesFromSAMHeader(final SAMFileHeader header) { + return addSamplesFromSampleNames(SampleUtils.getSAMFileSamples(header)); + } + + public SampleDBBuilder addSamplesFromSampleNames(final Collection sampleNames) { + for (final String sampleName : sampleNames) { if (sampleDB.getSample(sampleName) == null) { final Sample newSample = new Sample(sampleName, sampleDB); addSample(newSample); @@ -66,7 +70,7 @@ public SampleDBBuilder addSamples(SAMFileHeader header) { return this; } - public SampleDBBuilder addSamples(final List pedigreeArguments) { + public SampleDBBuilder addSamplesFromPedigreeArgument(final List pedigreeArguments) { for (final String ped : pedigreeArguments) { final File pedFile = new File(ped); if ( pedFile.exists() ) @@ -105,6 +109,7 @@ protected SampleDBBuilder addSamples(final String string) { * @param sample to be added */ protected SampleDBBuilder addSample(Sample sample) { + // todo -- merge with existing record if we have one sampleDB.addSample(sample); return this; } diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index 57bc6cf3b8..e63fc7febf 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -299,7 +299,6 @@ private PedReaderTestTagParsing(final List tags, EnumSet Date: Mon, 3 Oct 2011 19:09:02 -0700 Subject: [PATCH 186/363] Systematic unit tests for the sample object --- .../sting/gatk/samples/SampleUnitTest.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index 372b59353d..c2c9d77c6a 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -36,13 +36,25 @@ public void init() { } /** - * Now test the special getter methods + * Now basic getters */ @Test() - public void specialGettersTest() { - // todo -- test for sample with extra properties, like population -// Assert.assertTrue(sampleC.getID().equals("sampleC")); -// Assert.assertTrue(sampleC.getPopulation().equals("pop1")); + public void normalGettersTest() { + Assert.assertEquals("1A", fam1A.getID()); + Assert.assertEquals("fam1", fam1A.getFamilyID()); + Assert.assertEquals("1B", fam1A.getPaternalID()); + Assert.assertEquals("1C", fam1A.getMaternalID()); + Assert.assertEquals(null, fam1B.getPaternalID()); + Assert.assertEquals(null, fam1B.getMaternalID()); + + Assert.assertEquals(Affection.AFFECTED, trait1.getAffection()); + Assert.assertEquals(Sample.UNSET_QT, trait1.getQuantitativePhenotype()); + Assert.assertEquals(Affection.UNAFFECTED, trait2.getAffection()); + Assert.assertEquals(Sample.UNSET_QT, trait2.getQuantitativePhenotype()); + Assert.assertEquals(Affection.UNKNOWN, trait3.getAffection()); + Assert.assertEquals(Sample.UNSET_QT, trait3.getQuantitativePhenotype()); + Assert.assertEquals(Affection.QUANTITATIVE, trait4.getAffection()); + Assert.assertEquals(1.0, trait4.getQuantitativePhenotype()); } @Test() From b20689ff5500f1ba37044b2289e1e867efdd1dd0 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 3 Oct 2011 19:20:33 -0700 Subject: [PATCH 187/363] No longer supports extraProperties -- the underlying data structure is still present, but until I decide what to do for the extensible system I've completely disabled the subsystem -- Added code to merge Samples, so that a mostly full record can be merged with a consistent empty record. If the two records are inconsistent, an error is thrown -- addSample() in Sample.class now invokes mergeSample() when appropriate -- Validation types are now only STRICT or SILENT -- Validation code implemented in SampleDBBuilder -- Extensive unit tests for SampleDBBuilder --- build.xml | 14 ++- .../sting/gatk/GenomeAnalysisEngine.java | 3 +- .../arguments/GATKArgumentCollection.java | 7 +- .../gatk/samples/PedigreeValidationType.java | 3 +- .../sting/gatk/samples/Sample.java | 94 +++++++++++----- .../sting/gatk/samples/SampleDB.java | 25 +---- .../sting/gatk/samples/SampleDBBuilder.java | 89 ++++++++++----- .../sting/gatk/samples/SampleDBUnitTest.java | 105 ++++++++++++++++-- 8 files changed, 243 insertions(+), 97 deletions(-) diff --git a/build.xml b/build.xml index 1f26e7b7af..ef662a160a 100644 --- a/build.xml +++ b/build.xml @@ -146,12 +146,14 @@ - - + + + + + + + + diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 71e65f2fbb..9cfe7d48b9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -701,7 +701,8 @@ private void initializeSampleDB() { SampleDBBuilder sampleDBBuilder = new SampleDBBuilder(this, argCollection.pedigreeValidationType); sampleDBBuilder.addSamplesFromSAMHeader(getSAMFileHeader()); sampleDBBuilder.addSamplesFromSampleNames(SampleUtils.getUniqueSamplesFromRods(this)); - sampleDBBuilder.addSamplesFromPedigreeArgument(argCollection.pedigreeData); + sampleDBBuilder.addSamplesFromPedigreeFiles(argCollection.pedigreeFiles); + sampleDBBuilder.addSamplesFromPedigreeStrings(argCollection.pedigreeStrings); sampleDB = sampleDBBuilder.getFinalSampleDB(); } 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 c27bb26d9a..c71b3ce2c1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -215,8 +215,11 @@ public static DownsamplingMethod getDefaultDownsamplingMethod() { /** * MARK: add documentation details */ - @Argument(fullName="pedigree", shortName = "ped", doc="Pedigree file / string for samples",required=false) - public List pedigreeData = Collections.emptyList(); + @Argument(fullName="pedigree", shortName = "ped", doc="Pedigree files for samples",required=false) + public List pedigreeFiles = Collections.emptyList(); + + @Argument(fullName="pedigreeString", shortName = "pedString", doc="Pedigree string for samples",required=false) + public List pedigreeStrings = Collections.emptyList(); @Argument(fullName="pedigreeValidationType", shortName = "pedValidationType", doc="How strict should we be in validating the pedigree information?",required=false) public PedigreeValidationType pedigreeValidationType = PedigreeValidationType.STRICT; diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java index 8a1a4f225c..209636b54a 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java @@ -29,6 +29,5 @@ */ public enum PedigreeValidationType { STRICT, - LINIENT, - SILENT, + SILENT } 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 e68d92a9f7..3e61e03d9c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -1,6 +1,8 @@ package org.broadinstitute.sting.gatk.samples; +import org.broadinstitute.sting.utils.exceptions.UserException; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -121,36 +123,36 @@ public Gender getGender() { public String toString() { return String.format("Sample %s fam=%s dad=%s mom=%s gender=%s affection=%s qt=%s props=%s", getID(), getFamilyID(), getPaternalID(), getMaternalID(), getGender(), getAffection(), - getQuantitativePhenotype(), getExtraProperties()); - } - - // ------------------------------------------------------------------------------------- - // - // code for working with additional -- none standard -- properites - // - // ------------------------------------------------------------------------------------- - - public Map getExtraProperties() { - return Collections.unmodifiableMap(properties); - } - - /** - * Get one property - * @param key key of property - * @return value of property as generic object - */ - public Object getExtraPropertyValue(final String key) { - return properties.get(key); - } - - /** - * - * @param key property key - * @return true if sample has this property (even if its value is null) - */ - public boolean hasExtraProperty(String key) { - return properties.containsKey(key); - } + getQuantitativePhenotype(), properties); + } + +// // ------------------------------------------------------------------------------------- +// // +// // code for working with additional -- none standard -- properites +// // +// // ------------------------------------------------------------------------------------- +// +// public Map getExtraProperties() { +// return Collections.unmodifiableMap(properties); +// } +// +// /** +// * Get one property +// * @param key key of property +// * @return value of property as generic object +// */ +// public Object getExtraPropertyValue(final String key) { +// return properties.get(key); +// } +// +// /** +// * +// * @param key property key +// * @return true if sample has this property (even if its value is null) +// */ +// public boolean hasExtraProperty(String key) { +// return properties.containsKey(key); +// } @Override public int hashCode() { @@ -181,4 +183,36 @@ private final static boolean equalOrNull(final Object o1, final Object o2) { else return o2 == null ? false : o1.equals(o2); } + + private final static T mergeValues(final String name, final String field, final T o1, final T o2, final T emptyValue) { + if ( o1 == null || o1.equals(emptyValue) ) { + // take o2 if both are null, otherwise keep o2 + return o2 == null ? null : o2; + } else { + if ( o2 == null || o2.equals(emptyValue) ) + return o1; // keep o1, since it's a real value + else { + // both o1 and o2 have a value + if ( o1 == o2 ) + return o1; + else + throw new UserException("Inconsistent values detected for " + name + " for field " + field + " value1 " + o1 + " value2 " + o2); + } + } + } + + public final static Sample mergeSamples(final Sample prev, final Sample next) { + if ( prev.equals(next) ) + return next; + else { + return new Sample(prev.getID(), prev.infoDB, + mergeValues(prev.getID(), "Family_ID", prev.getFamilyID(), next.getFamilyID(), null), + mergeValues(prev.getID(), "Paternal_ID", prev.getPaternalID(), next.getPaternalID(), null), + mergeValues(prev.getID(), "Material_ID", prev.getMaternalID(), next.getMaternalID(), null), + mergeValues(prev.getID(), "Gender", prev.getGender(), next.getGender(), Gender.UNKNOWN), + mergeValues(prev.getID(), "Affection", prev.getAffection(), next.getAffection(), Affection.UNKNOWN), + mergeValues(prev.getID(), "QuantitativeTrait", prev.getQuantitativePhenotype(), next.getQuantitativePhenotype(), UNSET_QT)); + //mergeValues(prev.getID(), "ExtraProperties", prev.getExtraProperties(), next.getExtraProperties(), Collections.emptyMap())); + } + } } 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 75b37d758b..9abc285179 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -44,6 +44,9 @@ public SampleDB() { * @param sample to be added */ protected SampleDB addSample(Sample sample) { + Sample prev = samples.get(sample.getID()); + if ( prev != null ) + sample = Sample.mergeSamples(prev, sample); samples.put(sample.getID(), sample); return this; } @@ -138,8 +141,8 @@ public Set getChildren(Sample sample) { return children; } - public Collection getSamples() { - return Collections.unmodifiableCollection(samples.values()); + public Set getSamples() { + return new HashSet(samples.values()); } public Collection getSampleNames() { @@ -165,22 +168,4 @@ public Set getSamples(Collection sampleNameList) { } return samples; } - - // -------------------------------------------------------------------------------- - // - // Validation - // - // -------------------------------------------------------------------------------- - - public final void validate() { - validate(getSamples(), PedigreeValidationType.STRICT); - } - - public final void validate(PedigreeValidationType validationType) { - validate(getSamples(), validationType); - } - - public final void validate(Collection samplesToCheck, PedigreeValidationType validationType) { - // todo -- actually do an implementation - } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java index fd42a24f40..87733d1f63 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java @@ -45,6 +45,15 @@ public class SampleDBBuilder { final SampleDB sampleDB = new SampleDB(); final GenomeAnalysisEngine engine; + Set samplesFromDataSources = new HashSet(); + Set samplesFromPedigrees = new HashSet(); + + /** for testing only */ + protected SampleDBBuilder(PedigreeValidationType validationStrictness) { + engine = null; + this.validationStrictness = validationStrictness; + } + /** * Constructor takes both a SAM header and sample files because the two must be integrated. */ @@ -57,26 +66,34 @@ public SampleDBBuilder(GenomeAnalysisEngine engine, PedigreeValidationType valid * Hallucinates sample objects for all the samples in the SAM file and stores them */ public SampleDBBuilder addSamplesFromSAMHeader(final SAMFileHeader header) { - return addSamplesFromSampleNames(SampleUtils.getSAMFileSamples(header)); + addSamplesFromSampleNames(SampleUtils.getSAMFileSamples(header)); + return this; } public SampleDBBuilder addSamplesFromSampleNames(final Collection sampleNames) { for (final String sampleName : sampleNames) { if (sampleDB.getSample(sampleName) == null) { final Sample newSample = new Sample(sampleName, sampleDB); - addSample(newSample); + sampleDB.addSample(newSample); + samplesFromDataSources.add(newSample); // keep track of data source samples } } return this; } - public SampleDBBuilder addSamplesFromPedigreeArgument(final List pedigreeArguments) { - for (final String ped : pedigreeArguments) { - final File pedFile = new File(ped); - if ( pedFile.exists() ) - addSamples(pedFile); - else - addSamples(ped); + public SampleDBBuilder addSamplesFromPedigreeFiles(final List pedigreeFiles) { + for (final File pedFile : pedigreeFiles) { + Collection samples = addSamplesFromPedigreeArgument(pedFile); + samplesFromPedigrees.addAll(samples); + } + + return this; + } + + public SampleDBBuilder addSamplesFromPedigreeStrings(final List pedigreeStrings) { + for (final String pedString : pedigreeStrings) { + Collection samples = addSamplesFromPedigreeArgument(pedString); + samplesFromPedigrees.addAll(samples); } return this; @@ -86,41 +103,55 @@ public SampleDBBuilder addSamplesFromPedigreeArgument(final List pedigre * Parse one sample file and integrate it with samples that are already there * Fail quickly if we find any errors in the file */ - protected SampleDBBuilder addSamples(File sampleFile) { + private Collection addSamplesFromPedigreeArgument(File sampleFile) { final PedReader reader = new PedReader(); try { - reader.parse(sampleFile, getMissingFields(sampleFile), sampleDB); + return reader.parse(sampleFile, getMissingFields(sampleFile), sampleDB); } catch ( FileNotFoundException e ) { throw new UserException.CouldNotReadInputFile(sampleFile, e); } - - return this; } - protected SampleDBBuilder addSamples(final String string) { + private Collection addSamplesFromPedigreeArgument(final String string) { final PedReader reader = new PedReader(); - reader.parse(string, getMissingFields(string), sampleDB); - return this; - } - - /** - * Add a sample to the collection - * @param sample to be added - */ - protected SampleDBBuilder addSample(Sample sample) { - // todo -- merge with existing record if we have one - sampleDB.addSample(sample); - return this; + return reader.parse(string, getMissingFields(string), sampleDB); } public SampleDB getFinalSampleDB() { - sampleDB.validate(validationStrictness); + validate(); return sampleDB; } public EnumSet getMissingFields(final Object engineArg) { - final List posTags = engine.getTags(engineArg).getPositionalTags(); - return PedReader.parseMissingFieldTags(engineArg, posTags); + if ( engine == null ) + return EnumSet.noneOf(PedReader.MissingPedField.class); + else { + final List posTags = engine.getTags(engineArg).getPositionalTags(); + return PedReader.parseMissingFieldTags(engineArg, posTags); + } + } + + // -------------------------------------------------------------------------------- + // + // Validation + // + // -------------------------------------------------------------------------------- + + protected final void validate() { + if ( validationStrictness == PedigreeValidationType.SILENT ) + return; + else { + // check that samples in data sources are all annotated, if anything is annotated + if ( ! samplesFromPedigrees.isEmpty() && ! samplesFromDataSources.isEmpty() ) { + final Set sampleNamesFromPedigrees = new HashSet(); + for ( final Sample pSample : samplesFromPedigrees ) + sampleNamesFromPedigrees.add(pSample.getID()); + + for ( final Sample dsSample : samplesFromDataSources ) + if ( ! sampleNamesFromPedigrees.contains(dsSample.getID()) ) + throw new UserException("Sample " + dsSample.getID() + " found in data sources but not in pedigree files"); + } + } } } 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 500d322db5..f6d3b42b85 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java @@ -3,6 +3,11 @@ import net.sf.samtools.SAMFileHeader; import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.File; @@ -15,15 +20,101 @@ * Time: 8:21:00 AM */ public class SampleDBUnitTest extends BaseTest { - // this empty header used to instantiate sampledatasource objects - private static SAMFileHeader header = new SAMFileHeader(); - + private static SampleDBBuilder builder; // all the test sample files are located here - private String sampleFilesDir = validationDataLocation + "samples/"; + private File testPED = new File(testDir + "ceutrio.ped"); + + private static final Set testPEDSamples = new HashSet(Arrays.asList( + new Sample("kid", "fam1", "dad", "mom", Gender.MALE, Affection.AFFECTED), + new Sample("dad", "fam1", null, null, Gender.MALE, Affection.UNAFFECTED), + new Sample("mom", "fam1", null, null, Gender.FEMALE, Affection.AFFECTED))); + + 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 String testPEDString = + String.format("%s%n%s%n%s", + "fam1 kid dad mom 1 2", + "fam1 dad 0 0 1 1", + "fam1 mom 0 0 2 2"); + + private static final String testPEDStringInconsistentGender = + "fam1 kid 0 0 2 2"; + + private static final Set testPEDSamplesAsSet = + new HashSet(testPEDSamples); + + + @BeforeMethod + public void before() { + builder = new SampleDBBuilder(PedigreeValidationType.STRICT); + } + + @Test() + public void loadPEDFile() { + builder.addSamplesFromPedigreeFiles(Arrays.asList(testPED)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(testPEDSamplesAsSet, db.getSamples()); + } - // make sure samples are created from the SAM file correctly @Test() - public void loadSAMSamplesTest() { - //SampleDB s = new SampleDB(header); + public void loadPEDString() { + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDString)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(testPEDSamplesAsSet, db.getSamples()); + } + + private static final void addSAMHeader() { + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 10); + ArtificialSAMUtils.createEnumeratedReadGroups(header, Arrays.asList("1", "2", "3"), + Arrays.asList("kid", "mom", "dad")); + builder.addSamplesFromSAMHeader(header); + } + + @Test() + public void loadSAMHeader() { + addSAMHeader(); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(testSAMSamples, db.getSamples()); + } + + @Test() + public void loadSAMHeaderPlusPED() { + addSAMHeader(); + builder.addSamplesFromPedigreeFiles(Arrays.asList(testPED)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(testPEDSamples, db.getSamples()); + } + + @Test() + public void loadDuplicateData() { + builder.addSamplesFromPedigreeFiles(Arrays.asList(testPED)); + builder.addSamplesFromPedigreeFiles(Arrays.asList(testPED)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(testPEDSamples, db.getSamples()); + } + + @Test(expectedExceptions = UserException.class) + public void loadNonExistentFile() { + builder.addSamplesFromPedigreeFiles(Arrays.asList(new File("non-existence-file.txt"))); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(testSAMSamples, db.getSamples()); + } + + @Test(expectedExceptions = UserException.class) + public void loadInconsistentData() { + builder = new SampleDBBuilder(PedigreeValidationType.STRICT); + builder.addSamplesFromPedigreeFiles(Arrays.asList(testPED)); + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDStringInconsistentGender)); + builder.getFinalSampleDB(); + } + + @Test(expectedExceptions = UserException.class) + public void sampleInSAMHeaderNotInSamplesDB() { + addSAMHeader(); + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDStringInconsistentGender)); + builder.getFinalSampleDB(); } } From 6ca162ae9a977fe734f5c05790878c9fdc22de9e Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Mon, 3 Oct 2011 19:45:46 -0700 Subject: [PATCH 188/363] Not pre-sorted. Turns out it is a complicated solution when going multi-sample, so I'll leave it off for now. From a27641e1fc549f631091cd34f2a9343d22eb2f5a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 06:28:36 -0700 Subject: [PATCH 189/363] Cleaned up imports --- .../src/org/broadinstitute/sting/gatk/samples/Sample.java | 1 - .../src/org/broadinstitute/sting/gatk/samples/SampleDB.java | 3 --- .../broadinstitute/sting/gatk/samples/SampleDBBuilder.java | 4 ---- .../broadinstitute/sting/gatk/samples/SampleDBUnitTest.java | 6 +++--- .../broadinstitute/sting/gatk/samples/SampleUnitTest.java | 3 +-- 5 files changed, 4 insertions(+), 13 deletions(-) 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 3e61e03d9c..d57668715e 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -3,7 +3,6 @@ import org.broadinstitute.sting.utils.exceptions.UserException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; 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 9abc285179..4bcf3c9385 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -1,13 +1,10 @@ package org.broadinstitute.sting.gatk.samples; -import net.sf.samtools.SAMFileHeader; import net.sf.samtools.SAMReadGroupRecord; import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import java.io.File; import java.util.*; /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java index 87733d1f63..807b150b24 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java @@ -25,13 +25,9 @@ package org.broadinstitute.sting.gatk.samples; import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMReadGroupRecord; -import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.utils.SampleUtils; -import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.variantcontext.Genotype; import java.io.File; import java.io.FileNotFoundException; 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 f6d3b42b85..b6b4fab548 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java @@ -2,8 +2,6 @@ import net.sf.samtools.SAMFileHeader; import org.broadinstitute.sting.BaseTest; - -import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.testng.Assert; @@ -11,7 +9,9 @@ import org.testng.annotations.Test; import java.io.File; -import java.util.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; /** * Created by IntelliJ IDEA. diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index c2c9d77c6a..bc8a98c220 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -1,8 +1,7 @@ package org.broadinstitute.sting.gatk.samples; -import org.testng.Assert; import org.broadinstitute.sting.BaseTest; - +import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; From f552aede420a62173784d19a5cfd4a13b77d17cc Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 06:50:12 -0700 Subject: [PATCH 190/363] Only provide the sample names in the BAM file for efficiency --- .../sting/gatk/executive/LinearMicroScheduler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 a5d1370ba8..deafcd0cc0 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java @@ -13,6 +13,7 @@ import org.broadinstitute.sting.gatk.io.OutputTracker; import org.broadinstitute.sting.gatk.walkers.LocusWalker; import org.broadinstitute.sting.gatk.walkers.Walker; +import org.broadinstitute.sting.utils.SampleUtils; import java.util.Collection; @@ -57,7 +58,7 @@ public Object execute(Walker walker, ShardStrategy shardStrategy) { if(shard.getShardType() == Shard.ShardType.LOCUS) { LocusWalker lWalker = (LocusWalker)walker; WindowMaker windowMaker = new WindowMaker(shard, engine.getGenomeLocParser(), - getReadIterator(shard), shard.getGenomeLocs(), engine.getSampleDB().getSampleNames()); + getReadIterator(shard), shard.getGenomeLocs(), SampleUtils.getSAMFileSamples(engine)); for(WindowMaker.WindowMakerIterator iterator: windowMaker) { ShardDataProvider dataProvider = new LocusShardDataProvider(shard,iterator.getSourceInfo(),engine.getGenomeLocParser(),iterator.getLocus(),iterator,reference,rods); Object result = traversalEngine.traverse(walker, dataProvider, accumulator.getReduceInit()); From fee89e47ffccd57a28019766a3e3a0ddff77b6c6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 06:50:54 -0700 Subject: [PATCH 191/363] Only throws an error when there are no samples but there are reads -- Handles the case when you are running a ROD traversal and yet the LIBS is still used to return null everywhere. --- .../sting/gatk/iterators/LocusIteratorByState.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 d16502b1d1..896d6e3a2f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -280,7 +280,9 @@ public LocusIteratorByState(final Iterator samIterator, ReadPropertie this.samples = new ArrayList(samples); this.readStates = new ReadStateManager(samIterator,readInformation.getDownsamplingMethod()); - if ( this.samples.isEmpty() ) + // currently the GATK expects this LocusIteratorByState to accept empty sample lists, when + // there's no read data. So we need to throw this error only when samIterator.hasNext() is true + if ( this.samples.isEmpty() && samIterator.hasNext() ) throw new IllegalArgumentException("samples list must not be empty"); } From 343a7b6b2f93a1cc867df19a0dfb939d365af677 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 08:14:00 -0700 Subject: [PATCH 192/363] Updating UG integration tests for arbitrary impact of sample order changes on downsampling --- .../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 488b3ccd92..07b2f0566d 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("e6639ea2dc81635c706e6c35921406d7")); + Arrays.asList("b27939251539439a382538e507e03507")); executeTest("test MultiSample Pilot1", spec); } @@ -280,7 +280,7 @@ public void testWithIndelAllelesPassedIn() { 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("4be308fd9e8167ebee677f62a7a753b7")); + Arrays.asList("37e891bf1ac40caec9ea228f39c27e44")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } From e1d6c7a50ade2d6a80e539a4fc451d03885b0779 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 09:33:23 -0700 Subject: [PATCH 193/363] Updating MD5 that have changed due to sample ordering differences --- .../gatk/walkers/coverage/DepthOfCoverageIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 59ac1a41e7..411a5d0d2d 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 @@ -73,7 +73,7 @@ public void testBaseOutputNoFiltering() { spec.addAuxFile("df0ba76e0e6082c0d29fcfd68efc6b77", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); spec.addAuxFile("7dcac2e8962c778081486332a4576dc3", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); spec.addAuxFile("a50011571334f17e950ad3ed1149e350", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_statistics")); - spec.addAuxFile("6f3260504295695d765af639539585c9", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); + spec.addAuxFile("c95a7a6840334cadd0e520939615c77b", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_summary")); execute("testBaseOutputNoFiltering",spec); } @@ -90,7 +90,7 @@ public void testNoCoverageDueToFiltering() { spec.setOutputFileLocation(baseOutputFile); spec.addAuxFile("6ccd7d8970ba98cb95fe41636a070c1c",baseOutputFile); - spec.addAuxFile("0ee40f3e5091536c14e077b77557083a",createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); + spec.addAuxFile("7d87783b3d98b928cac16d383ceca807",createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); execute("testNoCoverageDueToFiltering",spec); } From 463eab760447771e53b8831f4136cafbc5def736 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 15:53:52 -0700 Subject: [PATCH 194/363] All MD5 mismatches for test are shown -- Now for tests like DoC, with 20 output md5s, you see all of the differences before failing. --- .../test/org/broadinstitute/sting/MD5DB.java | 31 ++++++++++++++----- .../org/broadinstitute/sting/WalkerTest.java | 21 ++++++++++--- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/MD5DB.java b/public/java/test/org/broadinstitute/sting/MD5DB.java index 0194e114ae..374a9f8dac 100644 --- a/public/java/test/org/broadinstitute/sting/MD5DB.java +++ b/public/java/test/org/broadinstitute/sting/MD5DB.java @@ -129,7 +129,7 @@ private static void copyFileToDB(File dbFile, final File resultsFile) { System.out.printf("##### Skipping update, cannot write file %s%n", dbFile); } } else { - System.out.printf("##### MD5 file is up to date: %s%n", dbFile.getPath()); + //System.out.printf("##### MD5 file is up to date: %s%n", dbFile.getPath()); } } @@ -170,6 +170,18 @@ private static byte[] getBytesFromFile(File file) throws IOException { return bytes; } + public static class MD5Match { + final String md5; + final String failMessage; + boolean failed; + + public MD5Match(final String md5, final String failMessage, final boolean failed) { + this.md5 = md5; + this.failMessage = failMessage; + this.failed = failed; + } + } + /** * Tests a file MD5 against an expected value, returning the MD5. NOTE: This function WILL throw an exception if the MD5s are different. * @param name Name of the test. @@ -178,18 +190,21 @@ private static byte[] getBytesFromFile(File file) throws IOException { * @param parameterize If true or if expectedMD5 is an empty string, will print out the calculated MD5 instead of error text. * @return The calculated MD5. */ - public static String assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5, final boolean parameterize) { - String filemd5sum = testFileMD5(name, resultsFile, expectedMD5, parameterize); + public static MD5Match assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5, final boolean parameterize) { + final String filemd5sum = testFileMD5(name, resultsFile, expectedMD5, parameterize); + String failMessage = null; + boolean failed = false; if (parameterize || expectedMD5.equals("")) { // Don't assert } else if ( filemd5sum.equals(expectedMD5) ) { - System.out.println(String.format(" => %s PASSED", name)); + System.out.println(String.format(" => %s PASSED (expected=%s)", name, expectedMD5)); } else { - Assert.fail(String.format("%s has mismatching MD5s: expected=%s observed=%s", name, expectedMD5, filemd5sum)); + failed = true; + failMessage = String.format("%s has mismatching MD5s: expected=%s observed=%s", name, expectedMD5, filemd5sum); } - return filemd5sum; + return new MD5Match(filemd5sum, failMessage, failed); } @@ -218,8 +233,8 @@ public static String testFileMD5(final String name, final File resultsFile, fina System.out.println(String.format("PARAMETERIZATION[%s]: file %s has md5 = %s, stated expectation is %s, equal? = %b", name, resultsFile, filemd5sum, expectedMD5, filemd5sum.equals(expectedMD5))); } else { - System.out.println(String.format("Checking MD5 for %s [calculated=%s, expected=%s]", resultsFile, filemd5sum, expectedMD5)); - System.out.flush(); + //System.out.println(String.format("Checking MD5 for %s [calculated=%s, expected=%s]", resultsFile, filemd5sum, expectedMD5)); + //System.out.flush(); if ( ! expectedMD5.equals(filemd5sum) ) { // we are going to fail for real in assertEquals (so we are counted by the testing framework). diff --git a/public/java/test/org/broadinstitute/sting/WalkerTest.java b/public/java/test/org/broadinstitute/sting/WalkerTest.java index a1817e3c70..ca7653b580 100755 --- a/public/java/test/org/broadinstitute/sting/WalkerTest.java +++ b/public/java/test/org/broadinstitute/sting/WalkerTest.java @@ -52,7 +52,7 @@ public void initializeRandomGenerator() { GenomeAnalysisEngine.resetRandomGenerator(); } - public String assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5) { + public MD5DB.MD5Match assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5) { return MD5DB.assertMatchingMD5(name, resultsFile, expectedMD5, parameterize()); } @@ -84,10 +84,23 @@ public static void assertOnDiskIndexEqualToNewlyCreatedIndex(final File indexFil public List assertMatchingMD5s(final String name, List resultFiles, List expectedMD5s) { List md5s = new ArrayList(); + List fails = new ArrayList(); + for (int i = 0; i < resultFiles.size(); i++) { - String md5 = assertMatchingMD5(name, resultFiles.get(i), expectedMD5s.get(i)); - maybeValidateSupplementaryFile(name, resultFiles.get(i)); - md5s.add(i, md5); + MD5DB.MD5Match result = assertMatchingMD5(name, resultFiles.get(i), expectedMD5s.get(i)); + if ( ! result.failed ) { + maybeValidateSupplementaryFile(name, resultFiles.get(i)); + md5s.add(result.md5); + } else { + fails.add(result); + } + } + + if ( ! fails.isEmpty() ) { + for ( final MD5DB.MD5Match fail : fails ) { + logger.warn("Fail: " + fail.failMessage); + } + Assert.fail("Test failed: " + name); } return md5s; From a45d985818d9cec38d2956847cb4f64375c71fcc Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 15:54:09 -0700 Subject: [PATCH 195/363] TODO method stubs --- .../sting/gatk/samples/SampleDB.java | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 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 4bcf3c9385..5ba2252e40 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -4,6 +4,7 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.variantcontext.Genotype; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.util.*; @@ -104,6 +105,48 @@ public int sampleCount() { return samples.size(); } + public Set getSamples() { + return new HashSet(samples.values()); + } + + public Collection getSampleNames() { + return Collections.unmodifiableCollection(samples.keySet()); + } + + + /** + * Takes a collection of sample names and returns their corresponding sample objects + * Note that, since a set is returned, if you pass in a list with duplicates names there will not be any duplicates in the returned set + * @param sampleNameList Set of sample names + * @return Corresponding set of samples + */ + public Set getSamples(Collection sampleNameList) { + HashSet samples = new HashSet(); + for (String name : sampleNameList) { + try { + samples.add(getSample(name)); + } + catch (Exception e) { + throw new StingException("Could not get sample with the following ID: " + name, e); + } + } + return samples; + } + + // -------------------------------------------------------------------------------- + // + // Higher level pedigree functions + // + // -------------------------------------------------------------------------------- + + public Set getFamilyIDs() { + throw new NotImplementedException(); + } + + public Map> getFamilies() { + throw new NotImplementedException(); + } + /** * Return all samples with a given family ID * Note that this isn't terribly efficient (linear) - it may be worth adding a new family ID data structure for this @@ -137,32 +180,4 @@ public Set getChildren(Sample sample) { } return children; } - - public Set getSamples() { - return new HashSet(samples.values()); - } - - public Collection getSampleNames() { - return Collections.unmodifiableCollection(samples.keySet()); - } - - - /** - * Takes a collection of sample names and returns their corresponding sample objects - * Note that, since a set is returned, if you pass in a list with duplicates names there will not be any duplicates in the returned set - * @param sampleNameList Set of sample names - * @return Corresponding set of samples - */ - public Set getSamples(Collection sampleNameList) { - HashSet samples = new HashSet(); - for (String name : sampleNameList) { - try { - samples.add(getSample(name)); - } - catch (Exception e) { - throw new StingException("Could not get sample with the following ID: " + name, e); - } - } - return samples; - } } From ffdfdcde3ff3340d822693cb27efc8f7b6aaeeb4 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 15:54:45 -0700 Subject: [PATCH 196/363] Updating MD5s -- Interval test now uses RG containing BAM -- DoC sample name ordering has changed. --- .../coverage/DepthOfCoverageIntegrationTest.java | 14 +++++++------- .../utils/interval/IntervalIntegrationTest.java | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) 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 411a5d0d2d..646fb5e779 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 @@ -57,11 +57,11 @@ public void testBaseOutputNoFiltering() { // now add the expected files that get generated spec.addAuxFile("423571e4c05e7934322172654ac6dbb7", baseOutputFile); spec.addAuxFile("9df5e7e07efeb34926c94a724714c219", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_counts")); - spec.addAuxFile("b9a7748e5aec4dc06daed893c901c00d", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_proportions")); + spec.addAuxFile("229b9b5bc2141c86dbc69c8acc9eba6a", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_cumulative_coverage_proportions")); spec.addAuxFile("9cd395f47b329b9dd00ad024fcac9929", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_statistics")); - spec.addAuxFile("aec669d64d9dd652dd088a5341835ea5", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_interval_summary")); - spec.addAuxFile("f6dbd74d32a48abe71ce08d300bce983", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_statistics")); - spec.addAuxFile("e3a3467ed259ee3680f8d01980f525b7", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".library_summary")); + 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")); @@ -69,10 +69,10 @@ public void testBaseOutputNoFiltering() { 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("650ee3714da7fbad7832c9d4ad49eb51", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); + spec.addAuxFile("a395dafde101971d2b9e5ddb6cd4b7d0", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_cumulative_coverage_proportions")); spec.addAuxFile("df0ba76e0e6082c0d29fcfd68efc6b77", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_statistics")); - spec.addAuxFile("7dcac2e8962c778081486332a4576dc3", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_interval_summary")); - spec.addAuxFile("a50011571334f17e950ad3ed1149e350", createTempFileFromBase(baseOutputFile.getAbsolutePath()+".sample_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")); execute("testBaseOutputNoFiltering",spec); 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 2fab1f287f..178c09fa4e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java @@ -76,7 +76,7 @@ public void testUnmappedReadInclusion() { // our base file File baseOutputFile = createTempFile("testUnmappedReadInclusion",".bam"); spec.setOutputFileLocation(baseOutputFile); - spec.addAuxFile("99c266d777e2e167b8153c858c305fda",createTempFileFromBase(baseOutputFile.getAbsolutePath())); + spec.addAuxFile("748a38ed5eb0a043dfc7b82f0d1e8063",createTempFileFromBase(baseOutputFile.getAbsolutePath())); spec.addAuxFile("fadcdf88597b9609c5f2a17f4c6eb455", createTempFileFromBase(baseOutputFile.getAbsolutePath().substring(0,baseOutputFile.getAbsolutePath().indexOf(".bam"))+".bai")); executeTest("testUnmappedReadInclusion",spec); @@ -97,7 +97,7 @@ public void testUnmappedReadExclusion() { File baseOutputFile = createTempFile("testUnmappedReadExclusion",".bam"); spec.setOutputFileLocation(baseOutputFile); spec.addAuxFile("8236f0b2df5a692e54751b08bc3836fa",createTempFileFromBase(baseOutputFile.getAbsolutePath())); - spec.addAuxFile("651b42456d31ba24e913297b71b32143", createTempFileFromBase(baseOutputFile.getAbsolutePath().substring(0,baseOutputFile.getAbsolutePath().indexOf(".bam"))+".bai")); + spec.addAuxFile("b341d808ecc33217f37c0c0cde2a3e2f", createTempFileFromBase(baseOutputFile.getAbsolutePath().substring(0,baseOutputFile.getAbsolutePath().indexOf(".bam"))+".bai")); executeTest("testUnmappedReadExclusion",spec); } From 9bd3ba4c7ed3a634a3abff5cae73a933e36ab61f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 4 Oct 2011 16:04:52 -0700 Subject: [PATCH 197/363] Missed one MD5 --- .../sting/utils/interval/IntervalIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 178c09fa4e..379d79c844 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalIntegrationTest.java @@ -96,7 +96,7 @@ public void testUnmappedReadExclusion() { // our base file File baseOutputFile = createTempFile("testUnmappedReadExclusion",".bam"); spec.setOutputFileLocation(baseOutputFile); - spec.addAuxFile("8236f0b2df5a692e54751b08bc3836fa",createTempFileFromBase(baseOutputFile.getAbsolutePath())); + spec.addAuxFile("80887ba488e53dabd9596ff93070ae75",createTempFileFromBase(baseOutputFile.getAbsolutePath())); spec.addAuxFile("b341d808ecc33217f37c0c0cde2a3e2f", createTempFileFromBase(baseOutputFile.getAbsolutePath().substring(0,baseOutputFile.getAbsolutePath().indexOf(".bam"))+".bai")); executeTest("testUnmappedReadExclusion",spec); From 51ecc20867004411b2e683d5f1281b205f5c029d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 09:55:05 -0700 Subject: [PATCH 198/363] getFamily() and associated methods implemented and tested -- Sample no longer serializable -- Sample now implements Comparable --- .../sting/gatk/samples/Sample.java | 7 ++- .../sting/gatk/samples/SampleDB.java | 47 +++++++++++------- .../sting/gatk/samples/SampleDBUnitTest.java | 49 ++++++++++++++++--- 3 files changed, 78 insertions(+), 25 deletions(-) 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 d57668715e..8d19eb246b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -9,7 +9,7 @@ /** * */ -public class Sample implements java.io.Serializable { +public class Sample implements Comparable { // implements java.io.Serializable { final private String familyID, paternalID, maternalID; final private Gender gender; final private double quantitativePhenotype; @@ -118,6 +118,11 @@ public Gender getGender() { return gender; } + @Override + public int compareTo(final Sample sample) { + return ID.compareTo(sample.getID()); + } + @Override public String toString() { return String.format("Sample %s fam=%s dad=%s mom=%s gender=%s affection=%s qt=%s props=%s", 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 5ba2252e40..2c63f93ffc 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -4,7 +4,6 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.variantcontext.Genotype; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.util.*; @@ -139,30 +138,42 @@ public Set getSamples(Collection sampleNameList) { // // -------------------------------------------------------------------------------- - public Set getFamilyIDs() { - throw new NotImplementedException(); + /** + * Returns a sorted set of the family IDs in all samples (excluding null ids) + * @return + */ + public final Set getFamilyIDs() { + return getFamilies().keySet(); } - public Map> getFamilies() { - throw new NotImplementedException(); + /** + * Returns a map from family ID -> set of family members for all samples with + * non-null family ids + * + * @return + */ + public final Map> getFamilies() { + 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); + } + } + + return families; } /** * Return all samples with a given family ID - * Note that this isn't terribly efficient (linear) - it may be worth adding a new family ID data structure for this * @param familyId * @return */ public Set getFamily(String familyId) { - HashSet familyMembers = new HashSet(); - - for (Sample sample : samples.values()) { - if (sample.getFamilyID() != null) { - if (sample.getFamilyID().equals(familyId)) - familyMembers.add(sample); - } - } - return familyMembers; + return getFamilies().get(familyId); } /** @@ -172,9 +183,9 @@ public Set getFamily(String familyId) { * @return */ public Set getChildren(Sample sample) { - HashSet children = new HashSet(); - for (Sample familyMember : getFamily(sample.getFamilyID())) { - if (familyMember.getMother() == sample || familyMember.getFather() == sample) { + final HashSet children = new HashSet(); + for ( final Sample familyMember : getFamily(sample.getFamilyID())) { + if ( familyMember.getMother() == sample || familyMember.getFather() == sample ) { children.add(familyMember); } } 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 b6b4fab548..d498ee61a1 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleDBUnitTest.java @@ -9,9 +9,7 @@ import org.testng.annotations.Test; import java.io.File; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * Created by IntelliJ IDEA. @@ -36,9 +34,17 @@ public class SampleDBUnitTest extends BaseTest { private static final String testPEDString = String.format("%s%n%s%n%s", - "fam1 kid dad mom 1 2", - "fam1 dad 0 0 1 1", - "fam1 mom 0 0 2 2"); + "fam1 kid dad mom 1 2", + "fam1 dad 0 0 1 1", + "fam1 mom 0 0 2 2"); + + private static final String testPEDMultipleFamilies = + String.format("%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", + "fam3 s1 d1 m1 2 2", + "fam2 s2 d2 m2 2 2"); private static final String testPEDStringInconsistentGender = "fam1 kid 0 0 2 2"; @@ -117,4 +123,35 @@ public void sampleInSAMHeaderNotInSamplesDB() { builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDStringInconsistentGender)); builder.getFinalSampleDB(); } + + @Test() + public void getFamilyIDs() { + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDMultipleFamilies)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(db.getFamilyIDs(), new TreeSet(Arrays.asList("fam1", "fam2", "fam3"))); + } + + @Test() + public void getFamily() { + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDMultipleFamilies)); + SampleDB db = builder.getFinalSampleDB(); + Assert.assertEquals(db.getFamily("fam1"), testPEDSamplesAsSet); + } + + @Test() + public void loadFamilyIDs() { + builder.addSamplesFromPedigreeStrings(Arrays.asList(testPEDMultipleFamilies)); + SampleDB db = builder.getFinalSampleDB(); + Map> families = db.getFamilies(); + Assert.assertEquals(families.size(), 3); + Assert.assertEquals(families.keySet(), new TreeSet(Arrays.asList("fam1", "fam2", "fam3"))); + + for ( final String famID : families.keySet() ) { + final Set fam = families.get(famID); + Assert.assertEquals(fam.size(), 3); + for ( final Sample sample : fam ) { + Assert.assertEquals(sample.getFamilyID(), famID); + } + } + } } From e7c80f7c451255c21c05116ca3ca8cc651fee109 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 12:26:33 -0700 Subject: [PATCH 199/363] Renaming quantitative trait to OtherPhenotype which is now a String not a double -- we can now use PED file to represent population data or other arbitrary phenotype data, not just doubles --- .../sting/gatk/samples/Affection.java | 8 ++--- .../sting/gatk/samples/PedReader.java | 10 ++++--- .../sting/gatk/samples/Sample.java | 30 +++++++++---------- .../sting/gatk/samples/SampleDB.java | 11 ------- .../sting/gatk/samples/PedReaderUnitTest.java | 16 +++++----- .../sting/gatk/samples/SampleUnitTest.java | 21 +++++++------ 6 files changed, 43 insertions(+), 53 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java b/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java index de0dba884f..83e31f672d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Affection.java @@ -28,8 +28,8 @@ * Categorical sample trait for association and analysis * * Samples can have unknown status, be affected or unaffected by the - * categorical trait, or they can be marked as actually having a - * quantitative trait value (stored in an associated value in the Sample class) + * categorical trait, or they can be marked as actually having an + * other trait value (stored in an associated value in the Sample class) * * @author Mark DePristo * @since Sept. 2011 @@ -41,6 +41,6 @@ public enum Affection { AFFECTED, /** Unaffected by the disease */ UNAFFECTED, - /** A quantitative trait: value of the trait is stored elsewhere */ - QUANTITATIVE + /** An "other" trait: value of the trait is stored elsewhere and is an arbitrary string */ + OTHER } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java index ec49b0f60e..c442409fbe 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedReader.java @@ -63,6 +63,8 @@ * A PED file must have 1 and only 1 phenotype in the sixth column. The phenotype can be either a * quantitative trait or an affection status column: PLINK will automatically detect which type * (i.e. based on whether a value other than 0, 1, 2 or the missing genotype code is observed). + * Note that the GATK actually supports arbitrary values for quantitative trait -- not just doubles -- + * and are actually representing these values as strings instead of doubles * * NOTE Quantitative traits with decimal points must be coded with a period/full-stop character and * not a comma, i.e. 2.394 not 2,394 @@ -212,7 +214,7 @@ public final List parse(Reader reader, EnumSet missingF splits.add(parts); lineNo++; } - logger.info("Trait is quantitative? " + isQT); + logger.info("Phenotype is other? " + isQT); // now go through and parse each record lineNo = 1; @@ -220,7 +222,7 @@ public final List parse(Reader reader, EnumSet missingF for ( final String[] parts : splits ) { String familyID = null, individualID, paternalID = null, maternalID = null; Gender sex = Gender.UNKNOWN; - double quantitativePhenotype = Sample.UNSET_QT; + String quantitativePhenotype = Sample.UNSET_QT; Affection affection = Affection.UNKNOWN; if ( familyPos != -1 ) familyID = maybeMissing(parts[familyPos]); @@ -239,8 +241,8 @@ public final List parse(Reader reader, EnumSet missingF if ( parts[phenotypePos].equals(MISSING_VALUE1) ) affection = Affection.UNKNOWN; else { - affection = Affection.QUANTITATIVE; - quantitativePhenotype = Double.valueOf(parts[phenotypePos]); + affection = Affection.OTHER; + quantitativePhenotype = parts[phenotypePos]; } } else { if ( parts[phenotypePos].equals(MISSING_VALUE1) ) affection = Affection.UNKNOWN; 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 8d19eb246b..b39fdd79d6 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -12,22 +12,22 @@ public class Sample implements Comparable { // implements java.io.Serializable { final private String familyID, paternalID, maternalID; final private Gender gender; - final private double quantitativePhenotype; + final private String otherPhenotype; final private Affection affection; final private String ID; final private SampleDB infoDB; final private Map properties = new HashMap(); - public final static double UNSET_QT = Double.NaN; + public final static String UNSET_QT = null; public Sample(final String ID, final SampleDB infoDB, final String familyID, final String paternalID, final String maternalID, - final Gender gender, final Affection affection, final double quantitativePhenotype) { + final Gender gender, final Affection affection, final String otherPhenotype) { this.familyID = familyID; this.paternalID = paternalID; this.maternalID = maternalID; this.gender = gender; - this.quantitativePhenotype = quantitativePhenotype; + this.otherPhenotype = otherPhenotype; this.affection = affection; this.ID = ID; this.infoDB = infoDB; @@ -35,8 +35,8 @@ public Sample(final String ID, final SampleDB infoDB, protected Sample(final String ID, final String familyID, final String paternalID, final String maternalID, - final Gender gender, final Affection affection, final double quantitativePhenotype) { - this(ID, null, familyID, paternalID, maternalID, gender, affection, quantitativePhenotype); + final Gender gender, final Affection affection, final String otherPhenotype) { + this(ID, null, familyID, paternalID, maternalID, gender, affection, otherPhenotype); } protected Sample(final String ID, @@ -51,8 +51,8 @@ public Sample(final String ID, final SampleDB infoDB, this(ID, infoDB, familyID, paternalID, maternalID, gender, Affection.UNKNOWN, UNSET_QT); } - public Sample(final String ID, final SampleDB infoDB, final Affection affection, final double quantitativePhenotype) { - this(ID, infoDB, null, null, null, Gender.UNKNOWN, affection, quantitativePhenotype); + public Sample(final String ID, final SampleDB infoDB, final Affection affection, final String otherPhenotype) { + this(ID, infoDB, null, null, null, Gender.UNKNOWN, affection, otherPhenotype); } public Sample(String id, SampleDB infoDB) { @@ -86,12 +86,12 @@ public Affection getAffection() { return affection; } - public boolean hasQuantitativeTrait() { - return affection == Affection.QUANTITATIVE; + public boolean hasOtherPhenotype() { + return affection == Affection.OTHER; } - public double getQuantitativePhenotype() { - return quantitativePhenotype; + public String getOtherPhenotype() { + return otherPhenotype; } /** @@ -127,7 +127,7 @@ public int compareTo(final Sample sample) { public String toString() { return String.format("Sample %s fam=%s dad=%s mom=%s gender=%s affection=%s qt=%s props=%s", getID(), getFamilyID(), getPaternalID(), getMaternalID(), getGender(), getAffection(), - getQuantitativePhenotype(), properties); + getOtherPhenotype(), properties); } // // ------------------------------------------------------------------------------------- @@ -174,7 +174,7 @@ public boolean equals(final Object o) { equalOrNull(paternalID, otherSample.paternalID) && equalOrNull(maternalID, otherSample.maternalID) && equalOrNull(gender, otherSample.gender) && - equalOrNull(quantitativePhenotype, otherSample.quantitativePhenotype) && + equalOrNull(otherPhenotype, otherSample.otherPhenotype) && equalOrNull(affection, otherSample.affection) && equalOrNull(properties, otherSample.properties); } @@ -215,7 +215,7 @@ public final static Sample mergeSamples(final Sample prev, final Sample next) { mergeValues(prev.getID(), "Material_ID", prev.getMaternalID(), next.getMaternalID(), null), mergeValues(prev.getID(), "Gender", prev.getGender(), next.getGender(), Gender.UNKNOWN), mergeValues(prev.getID(), "Affection", prev.getAffection(), next.getAffection(), Affection.UNKNOWN), - mergeValues(prev.getID(), "QuantitativeTrait", prev.getQuantitativePhenotype(), next.getQuantitativePhenotype(), UNSET_QT)); + mergeValues(prev.getID(), "OtherPhenotype", prev.getOtherPhenotype(), next.getOtherPhenotype(), UNSET_QT)); //mergeValues(prev.getID(), "ExtraProperties", prev.getExtraProperties(), next.getExtraProperties(), Collections.emptyMap())); } } 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 2c63f93ffc..ee0873c6ed 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDB.java @@ -8,17 +8,6 @@ import java.util.*; /** - * Created by IntelliJ IDEA. - * User: brett - * Date: Jul 26, 2010 - * Time: 3:30:09 PM - * - * This class stores and manages sample metadata. This data is encoded in a sample file, which can be included - * in the GATK by the "--samples" argument. This class reads and parses those files. - * - * Although there are a set of public methods for accessing sample data, they aren't used by walkers - they are really - * only used by GenomeAnalysisEngine. An instance of GenomeAnalysisEngine has one SampleDataSource. When a walker - * wants to access sample data, it asks GenomeAnalysis to fetch this data from its SampleDataSource. * */ public class SampleDB { diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index e63fc7febf..c2a94acc10 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -156,26 +156,26 @@ public Object[][] createPEDFiles() { "fam1 mom granddad2 grandma2 2 2")); // Quantitative trait - new PedReaderTest("QuantitativeTrait", + new PedReaderTest("OtherPhenotype", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.OTHER, "1.0"), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.OTHER, "10.0")), String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 10.0")); - new PedReaderTest("QuantitativeTraitWithMissing", + new PedReaderTest("OtherPhenotypeWithMissing", Arrays.asList( new Sample("s1", "fam1", null, null, Gender.MALE, Affection.UNKNOWN, Sample.UNSET_QT), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.OTHER, "10.0")), String.format("%s%n%s", "fam1 s1 0 0 1 -9", "fam2 s2 0 0 2 10.0")); - new PedReaderTest("QuantitativeTraitOnlyInts", + new PedReaderTest("OtherPhenotypeOnlyInts", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.QUANTITATIVE, 1.0), - new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.QUANTITATIVE, 10.0)), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.OTHER, "1"), + new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.OTHER, "10")), String.format("%s%n%s", "fam1 s1 0 0 1 1", "fam2 s2 0 0 2 10")); diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java index bc8a98c220..3af40adbe6 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/SampleUnitTest.java @@ -6,16 +6,13 @@ import org.testng.annotations.Test; /** - * Created by IntelliJ IDEA. - * User: brett - * Date: Sep 9, 2010 - * Time: 8:21:00 AM + * */ public class SampleUnitTest extends BaseTest { SampleDB db; static Sample fam1A, fam1B, fam1C; static Sample s1, s2; - static Sample trait1, trait2, trait3, trait4; + static Sample trait1, trait2, trait3, trait4, trait5; @BeforeClass public void init() { @@ -31,7 +28,8 @@ public void init() { trait1 = new Sample("t1", db, Affection.AFFECTED, Sample.UNSET_QT); trait2 = new Sample("t2", db, Affection.UNAFFECTED, Sample.UNSET_QT); trait3 = new Sample("t3", db, Affection.UNKNOWN, Sample.UNSET_QT); - trait4 = new Sample("t4", db, Affection.QUANTITATIVE, 1.0); + trait4 = new Sample("t4", db, Affection.OTHER, "1.0"); + trait5 = new Sample("t4", db, Affection.OTHER, "CEU"); } /** @@ -47,13 +45,14 @@ public void normalGettersTest() { Assert.assertEquals(null, fam1B.getMaternalID()); Assert.assertEquals(Affection.AFFECTED, trait1.getAffection()); - Assert.assertEquals(Sample.UNSET_QT, trait1.getQuantitativePhenotype()); + Assert.assertEquals(Sample.UNSET_QT, trait1.getOtherPhenotype()); Assert.assertEquals(Affection.UNAFFECTED, trait2.getAffection()); - Assert.assertEquals(Sample.UNSET_QT, trait2.getQuantitativePhenotype()); + Assert.assertEquals(Sample.UNSET_QT, trait2.getOtherPhenotype()); Assert.assertEquals(Affection.UNKNOWN, trait3.getAffection()); - Assert.assertEquals(Sample.UNSET_QT, trait3.getQuantitativePhenotype()); - Assert.assertEquals(Affection.QUANTITATIVE, trait4.getAffection()); - Assert.assertEquals(1.0, trait4.getQuantitativePhenotype()); + Assert.assertEquals(Sample.UNSET_QT, trait3.getOtherPhenotype()); + Assert.assertEquals(Affection.OTHER, trait4.getAffection()); + Assert.assertEquals("1.0", trait4.getOtherPhenotype()); + Assert.assertEquals("CEU", trait5.getOtherPhenotype()); } @Test() From 6a573437af6ad996864df9295bff2f0b9ed0a19e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 15:00:58 -0700 Subject: [PATCH 200/363] Details documentation arguments for -ped --- .../sting/gatk/GenomeAnalysisEngine.java | 6 +- .../arguments/GATKArgumentCollection.java | 60 ++++++++++++++++++- .../sting/gatk/samples/SampleDBBuilder.java | 2 +- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 9cfe7d48b9..a35cd36903 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -201,6 +201,9 @@ public Object execute() { // Prepare the data for traversal. initializeDataSources(); + // initialize sampleDB + initializeSampleDB(); + // initialize and validate the interval list initializeIntervals(); validateSuppliedIntervals(); @@ -689,9 +692,6 @@ protected void initializeDataSources() { // set the sequence dictionary of all of Tribble tracks to the sequence dictionary of our reference rodDataSources = getReferenceOrderedDataSources(referenceMetaDataFiles,referenceDataSource.getReference().getSequenceDictionary(),genomeLocParser,argCollection.unsafe); - - // set up sample db - initializeSampleDB(); } /** 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 c71b3ce2c1..cd9068a64b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -213,14 +213,70 @@ public static DownsamplingMethod getDefaultDownsamplingMethod() { // -------------------------------------------------------------------------------------------------------------- /** - * MARK: add documentation details + * Reads PED file-formatted tabular text files describing meta-data about the samples being + * processed in the GATK. + * + * See http://www.broadinstitute.org/mpg/tagger/faq.html + * See http://pngu.mgh.harvard.edu/~purcell/plink/data.shtml#ped + * + * The PED file is a white-space (space or tab) delimited file: the first six columns are mandatory: + * + * Family ID + * Individual ID + * Paternal ID + * Maternal ID + * Sex (1=male; 2=female; other=unknown) + * Phenotype + * + * The IDs are alphanumeric: the combination of family and individual ID should uniquely identify a person. + * A PED file must have 1 and only 1 phenotype in the sixth column. The phenotype can be either a + * quantitative trait or an affection status column: GATK will automatically detect which type + * (i.e. based on whether a value other than 0, 1, 2 or the missing genotype code is observed). + * + * If an individual's sex is unknown, then any character other than 1 or 2 can be used. + * + * You can add a comment to a PED or MAP file by starting the line with a # character. The rest of that + * line will be ignored. Do not start any family IDs with this character therefore. + * + * Affection status should be coded: + * + * -9 missing + * 0 missing + * 1 unaffected + * 2 affected + * + * If any value outside of -9,0,1,2 is detected than the samples are assumed + * to phenotype values are interpreted as string phenotype values. In this case -9 uniquely + * represents the missing value. + * + * Genotypes (column 7 onwards) cannot be specified to the GATK. + * + * For example, here are two individuals (one row = one person): + * + * FAM001 1 0 0 1 2 + * FAM001 2 0 0 1 2 + * + * Each -ped argument can be tagged with NO_FAMILY_ID, NO_PARENTS, NO_SEX, NO_PHENOTYPE to + * tell the GATK PED parser that the corresponding fields are missing from the ped file. + * + * Note that most GATK walkers do not use pedigree information. Walkers that require pedigree + * data should clearly indicate so in their arguments and will throw errors if required pedigree + * information is missing. */ @Argument(fullName="pedigree", shortName = "ped", doc="Pedigree files for samples",required=false) public List pedigreeFiles = Collections.emptyList(); + /** + * Inline PED records (see -ped argument). Each -pedString STRING can contain one or more + * valid PED records (see -ped) separated by semi-colons. Supports all tags for each pedString + * as -ped supports + */ @Argument(fullName="pedigreeString", shortName = "pedString", doc="Pedigree string for samples",required=false) public List pedigreeStrings = Collections.emptyList(); + /** + * How strict should we be in parsing the PED files? + */ @Argument(fullName="pedigreeValidationType", shortName = "pedValidationType", doc="How strict should we be in validating the pedigree information?",required=false) public PedigreeValidationType pedigreeValidationType = PedigreeValidationType.STRICT; @@ -379,7 +435,7 @@ public boolean equals(GATKArgumentCollection other) { return false; } if ((other.RODToInterval == null && RODToInterval != null) || - (other.RODToInterval != null && !other.RODToInterval.equals(RODToInterval))) { + (other.RODToInterval != null && !other.RODToInterval.equals(RODToInterval))) { return false; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java index 807b150b24..44a8600b01 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/SampleDBBuilder.java @@ -146,7 +146,7 @@ protected final void validate() { for ( final Sample dsSample : samplesFromDataSources ) if ( ! sampleNamesFromPedigrees.contains(dsSample.getID()) ) - throw new UserException("Sample " + dsSample.getID() + " found in data sources but not in pedigree files"); + throw new UserException("Sample " + dsSample.getID() + " found in data sources but not in pedigree files with STRICT pedigree validation"); } } } From be2d29ce69a2fd1b58938a9e9e47faff870c657a Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 15:17:41 -0700 Subject: [PATCH 201/363] Final PED documentation --- .../arguments/GATKArgumentCollection.java | 66 +++++++++++-------- .../gatk/samples/PedigreeValidationType.java | 8 +++ 2 files changed, 45 insertions(+), 29 deletions(-) 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 cd9068a64b..486868dc2b 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -213,55 +213,63 @@ public static DownsamplingMethod getDefaultDownsamplingMethod() { // -------------------------------------------------------------------------------------------------------------- /** - * Reads PED file-formatted tabular text files describing meta-data about the samples being - * processed in the GATK. + *

Reads PED file-formatted tabular text files describing meta-data about the samples being + * processed in the GATK.

* - * See http://www.broadinstitute.org/mpg/tagger/faq.html - * See http://pngu.mgh.harvard.edu/~purcell/plink/data.shtml#ped + * * - * The PED file is a white-space (space or tab) delimited file: the first six columns are mandatory: + *

The PED file is a white-space (space or tab) delimited file: the first six columns are mandatory:

* - * Family ID - * Individual ID - * Paternal ID - * Maternal ID - * Sex (1=male; 2=female; other=unknown) - * Phenotype + *
    + *
  • Family ID
  • + *
  • Individual ID
  • + *
  • Paternal ID
  • + *
  • Maternal ID
  • + *
  • Sex (1=male; 2=female; other=unknown)
  • + *
  • Phenotype
  • + *
* - * The IDs are alphanumeric: the combination of family and individual ID should uniquely identify a person. + *

The IDs are alphanumeric: the combination of family and individual ID should uniquely identify a person. * A PED file must have 1 and only 1 phenotype in the sixth column. The phenotype can be either a * quantitative trait or an affection status column: GATK will automatically detect which type - * (i.e. based on whether a value other than 0, 1, 2 or the missing genotype code is observed). + * (i.e. based on whether a value other than 0, 1, 2 or the missing genotype code is observed).

* - * If an individual's sex is unknown, then any character other than 1 or 2 can be used. + *

If an individual's sex is unknown, then any character other than 1 or 2 can be used.

* - * You can add a comment to a PED or MAP file by starting the line with a # character. The rest of that - * line will be ignored. Do not start any family IDs with this character therefore. + *

You can add a comment to a PED or MAP file by starting the line with a # character. The rest of that + * line will be ignored. Do not start any family IDs with this character therefore.

* - * Affection status should be coded: + *

Affection status should be coded:

* - * -9 missing - * 0 missing - * 1 unaffected - * 2 affected + *
    + *
  • -9 missing
  • + *
  • 0 missing
  • + *
  • 1 unaffected
  • + *
  • 2 affected
  • + *
* - * If any value outside of -9,0,1,2 is detected than the samples are assumed + *

If any value outside of -9,0,1,2 is detected than the samples are assumed * to phenotype values are interpreted as string phenotype values. In this case -9 uniquely - * represents the missing value. + * represents the missing value.

* - * Genotypes (column 7 onwards) cannot be specified to the GATK. + *

Genotypes (column 7 onwards) cannot be specified to the GATK.

* - * For example, here are two individuals (one row = one person): + *

For example, here are two individuals (one row = one person):

* + *
      *   FAM001  1  0 0  1  2
      *   FAM001  2  0 0  1  2
+     * 
* - * Each -ped argument can be tagged with NO_FAMILY_ID, NO_PARENTS, NO_SEX, NO_PHENOTYPE to - * tell the GATK PED parser that the corresponding fields are missing from the ped file. + *

Each -ped argument can be tagged with NO_FAMILY_ID, NO_PARENTS, NO_SEX, NO_PHENOTYPE to + * tell the GATK PED parser that the corresponding fields are missing from the ped file.

* - * Note that most GATK walkers do not use pedigree information. Walkers that require pedigree + *

Note that most GATK walkers do not use pedigree information. Walkers that require pedigree * data should clearly indicate so in their arguments and will throw errors if required pedigree - * information is missing. + * information is missing.

*/ @Argument(fullName="pedigree", shortName = "ped", doc="Pedigree files for samples",required=false) public List pedigreeFiles = Collections.emptyList(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java b/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java index 209636b54a..bbf857820f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/PedigreeValidationType.java @@ -28,6 +28,14 @@ * */ public enum PedigreeValidationType { + /** + * Require if a pedigree file is provided at all samples in the VCF or BAM files have a corresponding + * entry in the pedigree file(s). + */ STRICT, + + /** + * Do not enforce any overlap between the VCF/BAM samples and the pedigree data + * */ SILENT } From a91509e7dd7f098315c91475936e9eb29dc7f18b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 15:22:57 -0700 Subject: [PATCH 202/363] Shouldn't be public --- .../qscripts/StandardVariantEvaluation.scala | 202 ------------------ 1 file changed, 202 deletions(-) delete mode 100755 public/scala/qscript/org/broadinstitute/sting/queue/qscripts/StandardVariantEvaluation.scala diff --git a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/StandardVariantEvaluation.scala b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/StandardVariantEvaluation.scala deleted file mode 100755 index d333e1dc0d..0000000000 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/StandardVariantEvaluation.scala +++ /dev/null @@ -1,202 +0,0 @@ -package org.broadinstitute.sting.queue.qscripts - -import org.broadinstitute.sting.queue.QScript -import org.broadinstitute.sting.queue.extensions.gatk.RodBind -import org.broadinstitute.sting.queue.extensions.gatk._ - -class StandardVariantEvaluation extends QScript { - // todo -- update to released version when things stabilize - @Argument(doc="gatkJarFile", required=false) - var gatkJarFile: File = new File("/home/radon01/depristo/dev/GenomeAnalysisTKFromLaptop/trunk/dist/GenomeAnalysisTK.jar") - - @Argument(shortName = "R", doc="B37 reference sequence: defaults to broad standard location", required=false) - var referenceFile: File = new File("/humgen/1kg/reference/human_g1k_v37.fasta") - - @Argument(shortName = "intervals", doc="intervals to evaluate. Only supports evaluation on chromosome 20 now, as most evaluation data is there", required=false) - val TARGET_INTERVAL: String = "20" - - @Argument(shortName = "includeUnion", doc="If provided, we'll create a union of the evaluation data sets for evaluation", required=false) - val CREATE_UNION: Boolean = false - - @Argument(shortName = "dataDir", doc="Path to the standard evaluation data files", required=false) - val DATA_DIR = "/humgen/gsa-hpprojects/GATK/data/Comparisons/StandardForEvaluation/b37/" - - @Argument(shortName = "evalStandard1000GCalls", doc="If provided, we'll include some standard 1000G data for evaluation", required=false) - val EVAL_STANDARD_1000G_CALLS: Boolean = false - - val COMPS_DIR = DATA_DIR + "/comps/" - val EVALS_DIR = DATA_DIR + "/evals/" - - @Argument(shortName = "moreSNPsToEval", doc="Path to additional SNP call sets for evaluation", required=false) - val moreSNPsToEval: List[File] = Nil - - @Argument(shortName = "moreIndelsToEval", doc="Path to additional Indel call sets for evaluation", required=false) - val moreIndelsToEval: List[File] = Nil - - val VARIANT_TYPES: List[String] = List("indels", "snps") - val VARIANT_TYPE_VT: Map[String, List[org.broad.tribble.util.variantcontext.VariantContext.Type]] = Map( - "indels" -> List(org.broad.tribble.util.variantcontext.VariantContext.Type.INDEL, org.broad.tribble.util.variantcontext.VariantContext.Type.MIXED, org.broad.tribble.util.variantcontext.VariantContext.Type.NO_VARIATION), - "snps" -> List(org.broad.tribble.util.variantcontext.VariantContext.Type.SNP, org.broad.tribble.util.variantcontext.VariantContext.Type.NO_VARIATION) - ) - - val SITES_DIR: String = "sitesFiles" - - // path to b37 DBSNP - @Argument(shortName = "dbsnp", doc="Path to DBSNP **VCF** for evaluation", required=false) - val MY_DBSNP: File = new File("/humgen/gsa-hpprojects/GATK/data/Comparisons/Validated/dbSNP/dbsnp_129_b37.leftAligned.vcf") - //val MY_DBSNP: File = new File("/humgen/gsa-hpprojects/GATK/data/dbsnp_132_b37.leftAligned.vcf"); - - class Comp(val name: String, val evalType: String, val filename: String, val MakeHomVar: Boolean = false) { - val originalFile = new File(COMPS_DIR + filename) - val file: File = if ( MakeHomVar ) swapExt(originalFile, ".vcf",".homvar.vcf") else originalFile - val sitesFile = new File(SITES_DIR + "/" + swapExt(file, ".vcf", ".sites.vcf").getName) - } - - class Eval(val name: String, val evalType: String, val filename: String, val overrideFile: File = null ) { - val file: File = if ( overrideFile != null ) overrideFile else new File(EVALS_DIR + "/" + filename) - } - - var COMPS: List[Comp] = Nil - def addComp(comp: Comp) { COMPS = comp :: COMPS } - - var EVALS: List[Eval] = Nil - def addEval(eval: Eval) { EVALS = eval :: EVALS } - def addEvalFromCMD(file: File, t: String) { addEval(new Eval(file.getName, t, null, file)) } - - trait UNIVERSAL_GATK_ARGS extends CommandLineGATK { - this.logging_level = "INFO"; - this.jarFile = gatkJarFile; - this.intervalsString = List(TARGET_INTERVAL); - this.reference_sequence = referenceFile; - this.memoryLimit = 2 - } - - def initializeStandardDataFiles() = { - // - // Standard evaluation files for indels - // - addComp(new Comp("NA12878.homvar.GATK", "indels", "Indels.NA12878_WGS.filtered_Q50.0_QD5.0_SB-1.0_HR18.vcf", true)) - addComp(new Comp("CG.38samples", "indels", "CG.Indels.leftAligned.b37.vcf")) - addComp(new Comp("NA12878.homvar.CG", "indels", "NA12878.CG.b37.indels.vcf", true)) - addComp(new Comp("g1k.pilot1.validation", "indels", "pilot1_indel_validation_2009.b37.vcf")) - addComp(new Comp("NA12878.hand_curated", "indels", "NA12878.validated.curated.polymorphic.indels.vcf")) - addComp(new Comp("NA12878.Mullikin", "indels", "NA12878.DIPline.NQScm.expanded.chr20.b37.minReads_2_or_gt2bp.vcf")) - - - // - // INDEL call sets - // - if ( EVAL_STANDARD_1000G_CALLS ) { - addEval(new Eval("dindel", "indels", "20110208.chr20.dindel2.EUR.sites.vcf")) - addEval(new Eval("si", "indels", "20101123.chr20.si.v2.EUR.sites.vcf")) - addEval(new Eval("gatk", "indels", "EUR.phase1.chr20.broad.filtered.indels.sites.vcf")) - } - - // - // Standard evaluation files for SNPs - // - addComp(new Comp("NA12878.homvar.GATK", "snps", "NA12878.HiSeq19.cut.vcf", true)) - addComp(new Comp("CG.38samples", "snps", "CG.38samples.b37.vcf")) - addComp(new Comp("NA12878.homvar.CG", "snps", "NA12878.CG.b37.snps.vcf", true)) - addComp(new Comp("HapMap3.3", "snps", "hapmap3.3.sites_r27_nr.b37_fwd.vcf")) - addComp(new Comp("OMNI.2.5M", "snps", "omni2.5.1212samples.b37.sites.chr20.monoAreAC0.vcf")) - addComp(new Comp("g1k.pilot1.validation", "snps", "1000G.snp.validation.b37.vcf")) - - // - // SNP call sets - // - if ( EVAL_STANDARD_1000G_CALLS ) { - addEval(new Eval("1000G.gatk.eurPlus.phase1", "snps", "EUR+.phase1.chr20.broad.recal.vrcut1p0.sites.vcf")) - addEval(new Eval("1000G.high_specificity.phase1", "snps", "ALL.phase1.chr20.projectConsensus.highSpecificity.snps.genotypes.sites.vcf")) - } - } - - def script = { - val sitesDir = new File(SITES_DIR) - if ( ! sitesDir.exists ) sitesDir.mkdirs() - - initializeStandardDataFiles(); - - // add additional files for evaluation, if necessary - moreSNPsToEval.foreach(addEvalFromCMD(_, "snps")) - moreIndelsToEval.foreach(addEvalFromCMD(_, "indels")) - - // - // create hom-var versions of key files - // - for ( comp <- COMPS ) - if ( comp.MakeHomVar ) - add(new SelectHomVars(comp.originalFile, comp.file)) - - for ( comp <- COMPS ) - add(new JustSites(comp.file, comp.sitesFile)) - - // - // Loop over evaluation types - // - for ( evalType <- VARIANT_TYPES ) { - var evalsOfType = EVALS.filter(_.evalType == evalType) - val compsOfType = COMPS.filter(_.evalType == evalType) - - if ( evalsOfType.size > 0 ) { - - // if desired and possible, create a union.X.vcf file - if ( CREATE_UNION && evalsOfType.size > 1 ) { - val union: File = new File("union.%s.vcf".format(evalType)) - add(new MyCombine(evalsOfType.map(_.file), union)); - evalsOfType = new Eval("union", evalType, null, union) :: evalsOfType - } - - // our root VE - val VE = new MyEval() - VE.VT = VARIANT_TYPE_VT(evalType) - VE.o = new File(evalType + ".eval") - - // add evals - for ( calls <- evalsOfType ) - VE.rodBind :+= RodBind("eval_" + calls.name, "VCF", calls.file) - - // add comps - //VE.rodBind :+= RodBind("dbsnp", "VCF", MY_DBSNP) - for ( comp <- compsOfType ) - VE.rodBind :+= RodBind("comp_" + comp.name, "VCF", comp.sitesFile) - - add(VE) - } - } - } - - /** - * Select homozygous non-reference sites from a single deep data set - */ - class SelectHomVars(@Input(doc="foo") vcf: File, @Output(doc="foo") out: File) extends SelectVariants with UNIVERSAL_GATK_ARGS { - this.rodBind :+= RodBind("variant", "VCF", vcf) - this.o = out - this.select ++= List("\"AC == 2\"") - } - - /** - * A simple union - */ - class MyCombine(@Input(doc="foo") vcfs: List[File], @Output(doc="foo") out: File) extends CombineVariants with UNIVERSAL_GATK_ARGS { - for ( vcf <- vcfs ) - this.rodBind :+= RodBind(vcf.getName, "VCF", vcf) - this.o = out - } - - /** - * A command line (cut) that removes all genotyping information from a file - */ - class JustSites(@Input(doc="foo") in: File, @Output(doc="foo") out: File) extends CommandLineFunction { - def commandLine = "cut -f 1-8 %s > %s".format(in, out) - } - - /** - * Base class for VariantEval used here - */ - class MyEval() extends VariantEval with UNIVERSAL_GATK_ARGS { - this.noST = true - this.evalModule :+= "ValidationReport" - } -} - From 6d6149b9a2ecdb6c401460ac10db6ee90de27ede Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Wed, 5 Oct 2011 18:30:40 -0400 Subject: [PATCH 203/363] Updated gsalib gsa.read.gatkreport to return all reports, even those beginning with '.'. In PreQC using geom_blank() so MEDIAN_INSERT_SIZE plot doesn't crash on facet_grid(scales='free') when data doesn't contain points for 'RF' or 'TANDEM'. --- public/R/src/gsalib/R/gsa.read.gatkreport.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/R/src/gsalib/R/gsa.read.gatkreport.R b/public/R/src/gsalib/R/gsa.read.gatkreport.R index 011b5240d8..46bbf7eda5 100644 --- a/public/R/src/gsalib/R/gsa.read.gatkreport.R +++ b/public/R/src/gsalib/R/gsa.read.gatkreport.R @@ -99,5 +99,5 @@ gsa.read.gatkreport <- function(filename) { .gsa.assignGATKTableToEnvironment(tableName, tableHeader, tableRows, tableEnv); } - gatkreport = as.list(tableEnv); + gatkreport = as.list(tableEnv, all.names=TRUE); } From b945e97de19f6f4b29aae9c14b5343cf1b1de140 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 17:12:48 -0700 Subject: [PATCH 204/363] Shouldn't have committed the non-fetching version by default --- build.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index f3f88fdf74..ccd7a8aa09 100644 --- a/build.xml +++ b/build.xml @@ -147,12 +147,12 @@ - - - - - - + + From a3c5a316860d9ed4d98dd4a92c8e7b0031755613 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 21:09:08 -0700 Subject: [PATCH 205/363] Oops, forgot the PED test file --- public/testdata/ceutrio.ped | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 public/testdata/ceutrio.ped diff --git a/public/testdata/ceutrio.ped b/public/testdata/ceutrio.ped new file mode 100644 index 0000000000..1302e1a2de --- /dev/null +++ b/public/testdata/ceutrio.ped @@ -0,0 +1,3 @@ +fam1 kid dad mom 1 2 +fam1 dad 0 0 1 1 +fam1 mom 0 0 2 2 From 8e6845806ad31bb1f7b42714828d27eee6c20377 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 5 Oct 2011 21:26:21 -0700 Subject: [PATCH 206/363] Allowing empty samples list in LIBS -- Right now we cannot process BAM files without read groups because we enforce the samples list to not be empty when there's a SAM record. Now if there are reads and there are no samples we add the "null" sample so that LIBS walks the reads properly --- .../sting/gatk/iterators/LocusIteratorByState.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 896d6e3a2f..eb5b51b33f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -282,8 +282,12 @@ public LocusIteratorByState(final Iterator samIterator, ReadPropertie // currently the GATK expects this LocusIteratorByState to accept empty sample lists, when // there's no read data. So we need to throw this error only when samIterator.hasNext() is true - if ( this.samples.isEmpty() && samIterator.hasNext() ) - throw new IllegalArgumentException("samples list must not be empty"); + if ( this.samples.isEmpty() && samIterator.hasNext() ) { + // actually we cannot process BAMs without read groups unless we tolerate empty + // sample lists. In the empty case we need to add the null element to the samples + this.samples.add(null); + //throw new IllegalArgumentException("samples list must not be empty"); + } } /** From 93f7e632bd2febd5f8af2e846bf054893997dee0 Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Thu, 6 Oct 2011 10:07:46 -0400 Subject: [PATCH 207/363] Minor fix/enhancement for VariantEval: if a vcf has symbolic alleles, program would crash ungracefully - now we'll just skip record without processing. This is a big issue since we can't process 1000G integration files with code as is. --- .../gatk/walkers/varianteval/evaluators/CountVariants.java | 4 ++++ 1 file changed, 4 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 72058ba7bc..e834340370 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 @@ -130,6 +130,10 @@ else if (vc1.isSimpleDeletion()) nVariantLoci++; nMixed++; break; + case SYMBOLIC: + // ignore symbolic alleles, but don't fail + // todo - consistent way of treating symbolic alleles thgoughout codebase? + break; default: throw new ReviewedStingException("Unexpected VariantContext type " + vc1.getType()); } From daa5999489fea84502cafab7c6d5bea2a0e56bac Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 08:16:25 -0700 Subject: [PATCH 208/363] Fixed typo in argument description --- .../sting/gatk/walkers/variantutils/VariantsToTable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c44d84136e..81d0c36acf 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 @@ -133,7 +133,7 @@ public class VariantsToTable extends RodWalker { /** * By default, this tool throws a UserException when it encounters a field without a value in some record. This - * is generally useful when you mistype -F CHRMO, so that you get a friendly warning about CHRMO not being + * is generally useful when you mistype -F CHROM, so that you get a friendly warning about CHRMO not being * found before the tool runs through 40M 1000G records. However, in some cases you genuinely want to allow such * fields (e.g., AC not being calculated for filtered records, if included). When provided, this argument * will cause VariantsToTable to write out NA values for missing fields instead of throwing an error. From 4b5b9155a9be837f0cb799cb6605a5328f4b25ec Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 08:16:47 -0700 Subject: [PATCH 209/363] Fixed bad expected value in PedReaderUnitTest --- .../broadinstitute/sting/gatk/samples/PedReaderUnitTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java index c2a94acc10..1601845cd3 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/samples/PedReaderUnitTest.java @@ -158,7 +158,7 @@ public Object[][] createPEDFiles() { // Quantitative trait new PedReaderTest("OtherPhenotype", Arrays.asList( - new Sample("s1", "fam1", null, null, Gender.MALE, Affection.OTHER, "1.0"), + new Sample("s1", "fam1", null, null, Gender.MALE, Affection.OTHER, "1"), new Sample("s2", "fam2", null, null, Gender.FEMALE, Affection.OTHER, "10.0")), String.format("%s%n%s", "fam1 s1 0 0 1 1", @@ -188,7 +188,7 @@ private static final void runTest(PedReaderTest test, String myFileContents, Enu PedReader reader = new PedReader(); SampleDB sampleDB = new SampleDB(); List readSamples = reader.parse(myFileContents, missing, sampleDB); - Assert.assertEquals(new HashSet(test.expectedSamples), new HashSet(readSamples), "Parsed incorrect number of samples"); + Assert.assertEquals(new HashSet(test.expectedSamples), new HashSet(readSamples)); } @Test(enabled = true, dataProvider = "readerTest") From 73f9d1f2174a54ee2fd6d796bfefdd8d43540ebf Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 08:40:35 -0700 Subject: [PATCH 210/363] GATK read group requirement iron hand -- The GATK will now throw a user exception if it opens a SAM/BAM file that doesn't have at least one RG defined -- LIBS again throws an error if the complete list of samples isn't provided -- Updating ExmpleCountLociPipeline test to use the well-formated versions of the exampleBAM and exampleFASTA files in testdata, instead of the old broken ones in validation_data. -- Convenience constructors for UserExceptions.MalformedBAM --- .../sting/gatk/datasources/reads/SAMDataSource.java | 6 ++++++ .../sting/gatk/iterators/LocusIteratorByState.java | 5 +---- .../sting/utils/exceptions/UserException.java | 10 +++++++++- .../examples/ExampleCountLociPipelineTest.scala | 4 ++-- 4 files changed, 18 insertions(+), 7 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 5729703497..74d39ecb0d 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 @@ -235,6 +235,12 @@ public SAMDataSource( for(SAMFileReader reader: readers.values()) { // Get the sort order, forcing it to coordinate if unsorted. SAMFileHeader header = reader.getFileHeader(); + + 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"); + } + SAMFileHeader.SortOrder sortOrder = header.getSortOrder() != SAMFileHeader.SortOrder.unsorted ? header.getSortOrder() : SAMFileHeader.SortOrder.coordinate; // Validate that all input files are sorted in the same order. 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 eb5b51b33f..2f3652d6a5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/LocusIteratorByState.java @@ -283,10 +283,7 @@ public LocusIteratorByState(final Iterator samIterator, ReadPropertie // currently the GATK expects this LocusIteratorByState to accept empty sample lists, when // there's no read data. So we need to throw this error only when samIterator.hasNext() is true if ( this.samples.isEmpty() && samIterator.hasNext() ) { - // actually we cannot process BAMs without read groups unless we tolerate empty - // sample lists. In the empty case we need to add the null element to the samples - this.samples.add(null); - //throw new IllegalArgumentException("samples list must not be empty"); + throw new IllegalArgumentException("samples list must not be empty"); } } 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 77f1ed6c06..1dea726ae4 100755 --- a/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java +++ b/public/java/src/org/broadinstitute/sting/utils/exceptions/UserException.java @@ -160,7 +160,15 @@ public MissortedBAM(String message) { public static class MalformedBAM extends UserException { public MalformedBAM(SAMRecord read, String message) { - super(String.format("SAM/BAM file %s is malformed: %s", read.getFileSource() != null ? read.getFileSource().getReader() : "(none)", message)); + this(read.getFileSource() != null ? read.getFileSource().getReader().toString() : "(none)", message); + } + + public MalformedBAM(File file, String message) { + this(file.toString(), message); + } + + public MalformedBAM(String source, String message) { + super(String.format("SAM/BAM file %s is malformed: %s", source, message)); } } 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 1e6c93cffe..5901cab467 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 @@ -36,8 +36,8 @@ class ExampleCountLociPipelineTest { spec.name = "countloci" spec.args = Array( " -S public/scala/qscript/org/broadinstitute/sting/queue/qscripts/examples/ExampleCountLoci.scala", - " -R " + BaseTest.hg18Reference, - " -I " + BaseTest.validationDataLocation + "small_bam_for_countloci.bam", + " -R " + BaseTest.testDir + "exampleFASTA.fasta", + " -I " + BaseTest.testDir + "exampleBAM.bam", " -o " + testOut).mkString spec.fileMD5s += testOut -> "67823e4722495eb10a5e4c42c267b3a6" PipelineTest.executeTest(spec) From 1dbe937396bb7243c4fc382d5c5d001ff2ff0e2a Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Thu, 6 Oct 2011 12:00:32 -0400 Subject: [PATCH 211/363] Add in argument for downsampling threshold From c4dfc1fb8bf9b69bd620bcb1b1b2490a31751140 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 6 Oct 2011 13:41:36 -0400 Subject: [PATCH 212/363] Temporary commit of parallelization support for RealignerTargetCreator. Tim begged us for this and I got assurances from Khalid/Matt that this would also be extremely helpful for the whole genome calling pipeline, so I spent a while working on this. Needs to be fixed up though because apparently only the leaves in the hierarchical reduce get their output aggregated. Worked out a better solution with Matt. --- .../indels/RealignerTargetCreator.java | 119 +++++++++++++++--- 1 file changed, 99 insertions(+), 20 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java index bede50a0b0..e83667e8c8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java @@ -103,7 +103,7 @@ @Allows(value={DataSource.READS, DataSource.REFERENCE}) @By(DataSource.REFERENCE) @BAQMode(ApplicationTime = BAQ.ApplicationTime.FORBIDDEN) -public class RealignerTargetCreator extends RodWalker { +public class RealignerTargetCreator extends RodWalker implements TreeReducible { /** * The target intervals for realignment. @@ -251,38 +251,117 @@ else if ( hasIndel && !context.hasBasePileup() ) return new Event(eventLoc, furthestStopPos, eventType); } - public void onTraversalDone(Event sum) { - if ( sum != null && sum.isReportableEvent() ) - out.println(sum.toString()); + public void onTraversalDone(EventPair sum) { + if ( sum.left != null && sum.left.isReportableEvent() ) + out.println(sum.left.toString()); + if ( sum.right != null && sum.right.isReportableEvent() ) + out.println(sum.right.toString()); } - public Event reduceInit() { - return null; + public EventPair reduceInit() { + return new EventPair(null, null); } - public Event reduce(Event value, Event sum) { - // ignore no new events - if ( value == null ) - return sum; + public EventPair treeReduce(EventPair lhs, EventPair rhs) { + EventPair result; + + if ( lhs.left == null ) { + result = rhs; + } else if ( rhs.left == null ) { + result = lhs; + } else if ( lhs.right == null ) { + if ( rhs.right == null ) { + if ( canBeMerged(lhs.left, rhs.left) ) + result = new EventPair(mergeEvents(lhs.left, rhs.left), null); + else + result = new EventPair(lhs.left, rhs.left); + } else { + if ( canBeMerged(lhs.left, rhs.left) ) + result = new EventPair(mergeEvents(lhs.left, rhs.left), rhs.right); + else { + if ( rhs.left.isReportableEvent() ) + out.println(rhs.left.toString()); + result = new EventPair(lhs.left, rhs.right); + } + } + } else if ( rhs.right == null ) { + if ( canBeMerged(lhs.right, rhs.left) ) + result = new EventPair(lhs.left, mergeEvents(lhs.right, rhs.left)); + else { + if ( lhs.right.isReportableEvent() ) + out.println(lhs.right.toString()); + result = new EventPair(lhs.left, rhs.left); + } + } else { + if ( canBeMerged(lhs.right, rhs.left) ) { + Event merge = mergeEvents(lhs.right, rhs.left); + if ( merge.isReportableEvent() ) + out.println(merge.toString()); + } else { + if ( lhs.right.isReportableEvent() ) + out.println(lhs.right.toString()); + if ( rhs.left.isReportableEvent() ) + out.println(rhs.left.toString()); + } - // if it's the first good value, use it - if ( sum == null ) - return value; + result = new EventPair(lhs.left, rhs.right); + } + + return result; + } - // if we hit a new contig or they have no overlapping reads, then they are separate events - so clear sum - if ( sum.loc.getContigIndex() != value.loc.getContigIndex() || sum.furthestStopPos < value.loc.getStart() ) { - if ( sum.isReportableEvent() ) - out.println(sum.toString()); - return value; + public EventPair reduce(Event value, EventPair sum) { + if ( value == null ) { + ; // do nothing + } else if ( sum.left == null ) { + sum.left = value; + } else if ( sum.right == null ) { + if ( canBeMerged(sum.left, value) ) + sum.left = mergeEvents(sum.left, value); + // While ideally we shouldn't do anything differently depending on the number of threads, there is a practical reason for doing so: + // if we know that we are single-threaded then we can ensure that the intervals are emitted in order, which leads to a large + // performance improvement (esp. memory) in the IndelRealigner (because we don't have to store all of the intervals in memory to sort them). + else if ( getToolkit().getArguments().numberOfThreads > 1 ) + sum.right = value; + else { + if ( sum.left.isReportableEvent() ) + out.println(sum.left.toString()); + sum.left = value; + } + } else { + if ( canBeMerged(sum.right, value) ) + sum.right = mergeEvents(sum.right, value); + else { + if ( sum.right.isReportableEvent() ) + out.println(sum.right.toString()); + sum.right = value; + } } - // otherwise, merge the two events - sum.merge(value); return sum; } + static private boolean canBeMerged(Event left, Event right) { + return left.loc.getContigIndex() == right.loc.getContigIndex() && left.furthestStopPos >= right.loc.getStart(); + } + + @com.google.java.contract.Requires({"left != null", "right != null"}) + static private Event mergeEvents(Event left, Event right) { + left.merge(right); + return left; + } + private enum EVENT_TYPE { POINT_EVENT, INDEL_EVENT, BOTH } + class EventPair { + public Event left, right; + + public EventPair(Event left, Event right) { + this.left = left; + this.right = right; + } + } + class Event { public int furthestStopPos; From c5282335785c7777c7eeaeaa980852966f4e145e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 12:03:45 -0700 Subject: [PATCH 213/363] Now uses exampleBAM and exampleFASTA instead of the misc. copies in validation_data From 6d9c21046083d2ded085228f8052d226cf8e8c2d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 12:15:48 -0700 Subject: [PATCH 214/363] Updating MD5s for updated BAM with read groups --- .../ClipReadsWalkersIntegrationTest.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/ClipReadsWalkersIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/ClipReadsWalkersIntegrationTest.java index 1565c419b2..216026a523 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/ClipReadsWalkersIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/ClipReadsWalkersIntegrationTest.java @@ -36,7 +36,7 @@ public void testClipper(String name, String args, String md51, String md52) { WalkerTestSpec spec = new WalkerTestSpec( "-R " + hg18Reference + " -T ClipReads " + - "-I " + validationDataLocation + "clippingReadsTest.bam " + + "-I " + validationDataLocation + "clippingReadsTest.withRG.bam " + "-os %s " + "-o %s " + args, 2, // just one output file @@ -46,23 +46,22 @@ public void testClipper(String name, String args, String md51, String md52) { } final static String Q10ClipOutput = "b29c5bc1cb9006ed9306d826a11d444f"; - @Test public void testQClip0() { testClipper("clipQSum0", "-QT 0", "117a4760b54308f81789c39b1c9de578", "4deca83d80dfa3f093e0dc27d27d1352"); } - @Test public void testQClip2() { testClipper("clipQSum2", "-QT 2", Q10ClipOutput, "1e123233ebd2f35ac3f41b1b7d2c8199"); } - @Test public void testQClip10() { testClipper("clipQSum10", "-QT 10", "b29c5bc1cb9006ed9306d826a11d444f", "1e123233ebd2f35ac3f41b1b7d2c8199"); } - @Test public void testQClip20() { testClipper("clipQSum20", "-QT 20", "6c3434dce66ae5c9eeea502f10fb9bee", "b950538d2d8fac1bcee11850c452bd6a"); } - @Test public void testQClip30() { testClipper("clipQSum30", "-QT 20", "6c3434dce66ae5c9eeea502f10fb9bee", "b950538d2d8fac1bcee11850c452bd6a"); } + @Test public void testQClip0() { testClipper("clipQSum0", "-QT 0", "117a4760b54308f81789c39b1c9de578", "33e781084379aae538954e30919e8fd3"); } + @Test public void testQClip2() { testClipper("clipQSum2", "-QT 2", Q10ClipOutput, "57c05b6241db7110148a91fde2d431d0"); } + @Test public void testQClip10() { testClipper("clipQSum10", "-QT 10", "b29c5bc1cb9006ed9306d826a11d444f", "57c05b6241db7110148a91fde2d431d0"); } + @Test public void testQClip20() { testClipper("clipQSum20", "-QT 20", "6c3434dce66ae5c9eeea502f10fb9bee", "67263a39d5127f2660a5b638ff32056a"); } - @Test public void testClipRange1() { testClipper("clipRange1", "-CT 1-5", "b5acd753226e25b1e088838c1aab9117", "9f70540b795f227668dcf78edcb35c09"); } - @Test public void testClipRange2() { testClipper("clipRange2", "-CT 1-5,11-15", "be4fcad5b666a5540028b774169cbad7", "a22347a741640fc6df92700e0e8d6f61"); } + @Test public void testClipRange1() { testClipper("clipRange1", "-CT 1-5", "b5acd753226e25b1e088838c1aab9117", "764846d0592f346a33525af674fd7a10"); } + @Test public void testClipRange2() { testClipper("clipRange2", "-CT 1-5,11-15", "be4fcad5b666a5540028b774169cbad7", "3061cf742f9e5526a61130128ae761a3"); } - @Test public void testClipSeq() { testClipper("clipSeqX", "-X CCCCC", "db199bd06561c9f2122f6ffb07941fbc", "f49e9e61a44115e2be59330259966f53"); } - @Test public void testClipSeqFile() { testClipper("clipSeqXF", "-XF " + validationDataLocation + "seqsToClip.fasta", "d011a3152b31822475afbe0281491f8d", "5c977f261442ab6122d5198fa4086e67"); } + @Test public void testClipSeq() { testClipper("clipSeqX", "-X CCCCC", "db199bd06561c9f2122f6ffb07941fbc", "b89459f373e40f0b835c1faff2208839"); } + @Test public void testClipSeqFile() { testClipper("clipSeqXF", "-XF " + validationDataLocation + "seqsToClip.fasta", "d011a3152b31822475afbe0281491f8d", "24e19116ef16a37a6d095ed5c22c2466"); } - @Test public void testClipMulti() { testClipper("clipSeqMulti", "-QT 10 -CT 1-5 -XF " + validationDataLocation + "seqsToClip.fasta -X CCCCC", "a23187bd9bfb06557f799706d98441de", "38d5f33d198aeee7eebec9feb7b11199"); } + @Test public void testClipMulti() { testClipper("clipSeqMulti", "-QT 10 -CT 1-5 -XF " + validationDataLocation + "seqsToClip.fasta -X CCCCC", "a23187bd9bfb06557f799706d98441de", "ad8d30300cb43d5e300fcc4d2450da8e"); } - @Test public void testClipNs() { testClipper("testClipNs", "-QT 10 -CR WRITE_NS", Q10ClipOutput, "1e123233ebd2f35ac3f41b1b7d2c8199"); } - @Test public void testClipQ0s() { testClipper("testClipQs", "-QT 10 -CR WRITE_Q0S", Q10ClipOutput, "d44cab2e3b70f5492a0f5b59f0b80043"); } - @Test public void testClipSoft() { testClipper("testClipSoft", "-QT 10 -CR SOFTCLIP_BASES", Q10ClipOutput, "b86374a7e6f59e3dd35781e9e8006702"); } + @Test public void testClipNs() { testClipper("testClipNs", "-QT 10 -CR WRITE_NS", Q10ClipOutput, "57c05b6241db7110148a91fde2d431d0"); } + @Test public void testClipQ0s() { testClipper("testClipQs", "-QT 10 -CR WRITE_Q0S", Q10ClipOutput, "2a1a3153e0942ab355fd8a6e082b30e0"); } + @Test public void testClipSoft() { testClipper("testClipSoft", "-QT 10 -CR SOFTCLIP_BASES", Q10ClipOutput, "50d43d63d8e39f67a87a6359963c6f52"); } @Test public void testUseOriginalQuals() { From 6eb87bf58a60e65abf89f7096ecbe06bef790ee4 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 6 Oct 2011 15:57:49 -0400 Subject: [PATCH 215/363] RTC now caches all intervals as GenomeLocs (which is expected to take < 1Gb whole genome based on back of the envelope calculations with Matt) so that 1) we don't have to worry about emitting outside of the leaves in the hierarchical reductions and 2) we can emit the intervals in sorted order which is a big performance plus for the realigner. Integration tests change only because intervals whose start=stop are now printed as chr:start instead of chr:start-stop. --- .../indels/RealignerTargetCreator.java | 60 ++++++++++--------- ...RealignerTargetCreatorIntegrationTest.java | 4 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java index e83667e8c8..e1333b7f29 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.TreeSet; /** * Emits intervals for the Local Indel Realigner to target for realignment. @@ -253,9 +254,12 @@ else if ( hasIndel && !context.hasBasePileup() ) public void onTraversalDone(EventPair sum) { if ( sum.left != null && sum.left.isReportableEvent() ) - out.println(sum.left.toString()); + sum.intervals.add(sum.left.getLoc()); if ( sum.right != null && sum.right.isReportableEvent() ) - out.println(sum.right.toString()); + sum.intervals.add(sum.right.getLoc()); + + for ( GenomeLoc loc : sum.intervals ) + out.println(loc); } public EventPair reduceInit() { @@ -272,39 +276,39 @@ public EventPair treeReduce(EventPair lhs, EventPair rhs) { } else if ( lhs.right == null ) { if ( rhs.right == null ) { if ( canBeMerged(lhs.left, rhs.left) ) - result = new EventPair(mergeEvents(lhs.left, rhs.left), null); + result = new EventPair(mergeEvents(lhs.left, rhs.left), null, lhs.intervals, rhs.intervals); else - result = new EventPair(lhs.left, rhs.left); + result = new EventPair(lhs.left, rhs.left, lhs.intervals, rhs.intervals); } else { if ( canBeMerged(lhs.left, rhs.left) ) - result = new EventPair(mergeEvents(lhs.left, rhs.left), rhs.right); + result = new EventPair(mergeEvents(lhs.left, rhs.left), rhs.right, lhs.intervals, rhs.intervals); else { if ( rhs.left.isReportableEvent() ) - out.println(rhs.left.toString()); - result = new EventPair(lhs.left, rhs.right); + rhs.intervals.add(rhs.left.getLoc()); + result = new EventPair(lhs.left, rhs.right, lhs.intervals, rhs.intervals); } } } else if ( rhs.right == null ) { if ( canBeMerged(lhs.right, rhs.left) ) - result = new EventPair(lhs.left, mergeEvents(lhs.right, rhs.left)); + result = new EventPair(lhs.left, mergeEvents(lhs.right, rhs.left), lhs.intervals, rhs.intervals); else { if ( lhs.right.isReportableEvent() ) - out.println(lhs.right.toString()); - result = new EventPair(lhs.left, rhs.left); + lhs.intervals.add(lhs.right.getLoc()); + result = new EventPair(lhs.left, rhs.left, lhs.intervals, rhs.intervals); } } else { if ( canBeMerged(lhs.right, rhs.left) ) { Event merge = mergeEvents(lhs.right, rhs.left); if ( merge.isReportableEvent() ) - out.println(merge.toString()); + lhs.intervals.add(merge.getLoc()); } else { if ( lhs.right.isReportableEvent() ) - out.println(lhs.right.toString()); + lhs.intervals.add(lhs.right.getLoc()); if ( rhs.left.isReportableEvent() ) - out.println(rhs.left.toString()); + rhs.intervals.add(rhs.left.getLoc()); } - result = new EventPair(lhs.left, rhs.right); + result = new EventPair(lhs.left, rhs.right, lhs.intervals, rhs.intervals); } return result; @@ -318,22 +322,14 @@ public EventPair reduce(Event value, EventPair sum) { } else if ( sum.right == null ) { if ( canBeMerged(sum.left, value) ) sum.left = mergeEvents(sum.left, value); - // While ideally we shouldn't do anything differently depending on the number of threads, there is a practical reason for doing so: - // if we know that we are single-threaded then we can ensure that the intervals are emitted in order, which leads to a large - // performance improvement (esp. memory) in the IndelRealigner (because we don't have to store all of the intervals in memory to sort them). - else if ( getToolkit().getArguments().numberOfThreads > 1 ) + else sum.right = value; - else { - if ( sum.left.isReportableEvent() ) - out.println(sum.left.toString()); - sum.left = value; - } } else { if ( canBeMerged(sum.right, value) ) sum.right = mergeEvents(sum.right, value); else { if ( sum.right.isReportableEvent() ) - out.println(sum.right.toString()); + sum.intervals.add(sum.right.getLoc()); sum.right = value; } } @@ -355,18 +351,26 @@ private enum EVENT_TYPE { POINT_EVENT, INDEL_EVENT, BOTH } class EventPair { public Event left, right; + public TreeSet intervals = new TreeSet(); public EventPair(Event left, Event right) { this.left = left; this.right = right; } + + public EventPair(Event left, Event right, TreeSet set1, TreeSet set2) { + this.left = left; + this.right = right; + intervals.addAll(set1); + intervals.addAll(set2); + } } class Event { public int furthestStopPos; - public GenomeLoc loc; - public int eventStartPos; + private GenomeLoc loc; + private int eventStartPos; private int eventStopPos; private EVENT_TYPE type; private ArrayList pointEvents = new ArrayList(); @@ -421,8 +425,8 @@ public boolean isReportableEvent() { return getToolkit().getGenomeLocParser().isValidGenomeLoc(loc.getContig(), eventStartPos, eventStopPos, true) && eventStopPos >= 0 && eventStopPos - eventStartPos < maxIntervalSize; } - public String toString() { - return String.format("%s:%d-%d", loc.getContig(), eventStartPos, eventStopPos); + public GenomeLoc getLoc() { + return getToolkit().getGenomeLocParser().createGenomeLoc(loc.getContig(), eventStartPos, eventStopPos); } } } \ No newline at end of file diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java index 1873ccbe29..fdb3104285 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java @@ -13,13 +13,13 @@ public void testIntervals() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( "-T RealignerTargetCreator -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam --mismatchFraction 0.15 -L 1:10,000,000-10,050,000 -o %s", 1, - Arrays.asList("e7accfa58415d6da80383953b1a3a986")); + Arrays.asList("3f0b63a393104d0c4158c7d1538153b8")); executeTest("test standard", spec1); WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( "-T RealignerTargetCreator --known " + b36dbSNP129 + " -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000 -o %s", 1, - Arrays.asList("0367d39a122c8ac0899fb868a82ef728")); + Arrays.asList("5085054c78e256432dc75c85a9ac631c")); executeTest("test dbsnp", spec2); WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( From c61804a450e9f5e7672b4f0a5e0e29ab8d1771da Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 6 Oct 2011 16:14:04 -0400 Subject: [PATCH 216/363] Rename the long version of the argument name to more accurately reflect its purpose. --- .../sting/gatk/walkers/indels/IndelRealigner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 36e4db1c5f..41fa755b84 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 @@ -138,7 +138,7 @@ public enum ConsensusDeterminationModel { * Any number of VCF files representing known indels to be used for constructing alternate consenses. * Could be e.g. dbSNP and/or official 1000 Genomes indel calls. Non-indel variants in these files will be ignored. */ - @Input(fullName="known", shortName = "known", doc="Input VCF file(s) with known indels", required=false) + @Input(fullName="knownAlleles", shortName = "known", doc="Input VCF file(s) with known indels", required=false) public List> known = Collections.emptyList(); /** From d367945dec9de98e722c2809df626166ed25d171 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 15:14:17 -0700 Subject: [PATCH 217/363] Extending InsertSizeDistribution to compute stats by sample and by read group From d1e70d6ec21a8015596ac7aa4c3163c6b4af28ef Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 18:29:26 -0700 Subject: [PATCH 218/363] Removed Nx counting of reads in metrics with -nt > 1 --- .../broadinstitute/sting/gatk/executive/ShardTraverser.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java b/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java index 11e51d99bc..badd398609 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/ShardTraverser.java @@ -66,15 +66,13 @@ public Object call() { microScheduler.getReadIterator(shard), shard.getGenomeLocs(), microScheduler.engine.getSampleDB().getSampleNames()); // todo: microScheduler.engine is protected - is it okay to user it here? - ShardDataProvider dataProvider = null; for(WindowMaker.WindowMakerIterator iterator: windowMaker) { - dataProvider = new LocusShardDataProvider(shard,iterator.getSourceInfo(),microScheduler.getEngine().getGenomeLocParser(),iterator.getLocus(),iterator,microScheduler.reference,microScheduler.rods); + final ShardDataProvider dataProvider = new LocusShardDataProvider(shard,iterator.getSourceInfo(),microScheduler.getEngine().getGenomeLocParser(),iterator.getLocus(),iterator,microScheduler.reference,microScheduler.rods); accumulator = traversalEngine.traverse( walker, dataProvider, accumulator ); dataProvider.close(); } - if (dataProvider != null) dataProvider.close(); windowMaker.close(); outputMergeTask = outputTracker.closeStorage(); From 0b88af4af986a2b830d8878644339f1cc954d776 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 18:42:26 -0700 Subject: [PATCH 219/363] Counts of records failing filters are displayed sorted -- Stops random ordering of the output, as the counts are returned sorted by string name of the class -- Deleted now unused sh*tty assessors in Utils --- .../sting/gatk/ReadMetrics.java | 9 +++++-- .../gatk/traversals/TraversalEngine.java | 8 +++--- .../org/broadinstitute/sting/utils/Utils.java | 27 ------------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/ReadMetrics.java b/public/java/src/org/broadinstitute/sting/gatk/ReadMetrics.java index 7cb615f7fb..ceaa30f015 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/ReadMetrics.java +++ b/public/java/src/org/broadinstitute/sting/gatk/ReadMetrics.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; /** * Holds a bunch of basic information about the traversal. @@ -102,8 +103,12 @@ public void incrementFilter(SamRecordFilter filter) { counter.put(filter.getClass(), c + 1L); } - public Map getCountsByFilter() { - return Collections.unmodifiableMap(counter); + public Map getCountsByFilter() { + final TreeMap sortedCounts = new TreeMap(); + for(Map.Entry counterEntry: counter.entrySet()) { + sortedCounts.put(counterEntry.getKey().getSimpleName(),counterEntry.getValue()); + } + return sortedCounts; } /** 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 c6321e2ad7..fd691735f7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraversalEngine.java @@ -364,8 +364,8 @@ public void printOnTraversalDone() { // count up the number of skipped reads by summing over all filters long nSkippedReads = 0L; - for ( Map.Entry countsByFilter: cumulativeMetrics.getCountsByFilter().entrySet()) - nSkippedReads += countsByFilter.getValue(); + for ( final long countsByFilter : cumulativeMetrics.getCountsByFilter().values()) + nSkippedReads += countsByFilter; logger.info(String.format("Total runtime %.2f secs, %.2f min, %.2f hours", elapsed, elapsed / 60, elapsed / 3600)); if ( cumulativeMetrics.getNumReadsSeen() > 0 ) @@ -373,10 +373,10 @@ public void printOnTraversalDone() { nSkippedReads, cumulativeMetrics.getNumReadsSeen(), 100.0 * MathUtils.ratio(nSkippedReads,cumulativeMetrics.getNumReadsSeen()))); - for ( Map.Entry filterCounts : cumulativeMetrics.getCountsByFilter().entrySet() ) { + for ( Map.Entry filterCounts : cumulativeMetrics.getCountsByFilter().entrySet() ) { long count = filterCounts.getValue(); logger.info(String.format(" -> %d reads (%.2f%% of total) failing %s", - count, 100.0 * MathUtils.ratio(count,cumulativeMetrics.getNumReadsSeen()), Utils.getClassName(filterCounts.getKey()))); + count, 100.0 * MathUtils.ratio(count,cumulativeMetrics.getNumReadsSeen()), filterCounts.getKey())); } if ( performanceLog != null ) performanceLog.close(); diff --git a/public/java/src/org/broadinstitute/sting/utils/Utils.java b/public/java/src/org/broadinstitute/sting/utils/Utils.java index 6ce492c636..cbd2c52d6d 100755 --- a/public/java/src/org/broadinstitute/sting/utils/Utils.java +++ b/public/java/src/org/broadinstitute/sting/utils/Utils.java @@ -58,33 +58,6 @@ public static int optimumHashSize ( int maxElements ) { return (int)(maxElements / JAVA_DEFAULT_HASH_LOAD_FACTOR) + 2; } - public static String getClassName(Class c) { - String FQClassName = c.getName(); - int firstChar; - firstChar = FQClassName.lastIndexOf ('.') + 1; - if ( firstChar > 0 ) { - FQClassName = FQClassName.substring ( firstChar ); - } - return FQClassName; - } - - - // returns package and class name - public static String getFullClassName(Class c) { - return c.getName(); - } - - // returns the package without the classname, empty string if - // there is no package - public static String getPackageName(Class c) { - String fullyQualifiedName = c.getName(); - int lastDot = fullyQualifiedName.lastIndexOf ('.'); - if (lastDot==-1){ return ""; } - return fullyQualifiedName.substring (0, lastDot); - } - - - /** * Compares two objects, either of which might be null. * From c7864c7256c4d281bec6c25f292357be945263eb Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 6 Oct 2011 18:51:40 -0700 Subject: [PATCH 220/363] Filter application order is now deterministic, in the order defined by the walker -- For no apparent reason we were using a HashSet to store the ReadFilters, so the order of operations was really arbitrarily applied. The order now is (1) the order of the walker intrinsic filters (2) read group black list (if provided) (3) command line filters (if provided) --- .../broadinstitute/sting/gatk/GenomeAnalysisEngine.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index a35cd36903..7bc3daa9a8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -257,13 +257,12 @@ public String getName() { * @return A collection of available filters. */ public Collection createFilters() { - Set filters = new HashSet(); - filters.addAll(WalkerManager.getReadFilters(walker,this.getFilterManager())); + final List filters = WalkerManager.getReadFilters(walker,this.getFilterManager()); if (this.getArguments().readGroupBlackList != null && this.getArguments().readGroupBlackList.size() > 0) filters.add(new ReadGroupBlackListFilter(this.getArguments().readGroupBlackList)); - for(String filterName: this.getArguments().readFilters) + for(final String filterName: this.getArguments().readFilters) filters.add(this.getFilterManager().createByName(filterName)); - return Collections.unmodifiableSet(filters); + return Collections.unmodifiableList(filters); } /** From f91b015e0e79f7a5aeb839efce42f629a4d498f2 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 6 Oct 2011 22:33:21 -0400 Subject: [PATCH 221/363] Made the BaseTest.testDir absolute --- public/java/test/org/broadinstitute/sting/BaseTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/BaseTest.java b/public/java/test/org/broadinstitute/sting/BaseTest.java index 35a81770d7..8e11add337 100755 --- a/public/java/test/org/broadinstitute/sting/BaseTest.java +++ b/public/java/test/org/broadinstitute/sting/BaseTest.java @@ -80,7 +80,8 @@ public abstract class BaseTest { public static final String networkTempDir = "/broad/shptmp/"; public static final File networkTempDirFile = new File(networkTempDir); - public static final String testDir = "public/testdata/"; + public static final File testDirFile = new File("public/testdata/"); + public static final String testDir = testDirFile.getAbsolutePath() + "/"; /** before the class starts up */ static { From ca9cd9b688cfd23804e341daea0fe409f4278d7f Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 6 Oct 2011 22:38:44 -0400 Subject: [PATCH 222/363] Minor fix for merging intervals which hadn't been necessary when only merging from the left to right. Added integration tests to cover the parallelization of RTC. --- .../indels/RealignerTargetCreator.java | 4 +++ ...RealignerTargetCreatorIntegrationTest.java | 33 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java index e1333b7f29..56ce604499 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreator.java @@ -415,6 +415,10 @@ public void merge(Event e) { eventStartPos = lastPosition; else eventStartPos = Math.min(eventStartPos, lastPosition); + } else if ( eventStartPos == -1 && e.eventStartPos != -1 ) { + eventStartPos = e.eventStartPos; + eventStopPos = e.eventStopPos; + furthestStopPos = e.furthestStopPos; } } pointEvents.add(newPosition); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java index fdb3104285..78b24d6469 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/indels/RealignerTargetCreatorIntegrationTest.java @@ -8,20 +8,41 @@ public class RealignerTargetCreatorIntegrationTest extends WalkerTest { @Test - public void testIntervals() { + public void testIntervals1() { + String md5 = "3f0b63a393104d0c4158c7d1538153b8"; WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( "-T RealignerTargetCreator -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam --mismatchFraction 0.15 -L 1:10,000,000-10,050,000 -o %s", 1, - Arrays.asList("3f0b63a393104d0c4158c7d1538153b8")); - executeTest("test standard", spec1); + Arrays.asList(md5)); + executeTest("test standard nt=1", spec1); WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( - "-T RealignerTargetCreator --known " + b36dbSNP129 + " -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000 -o %s", + "-nt 4 -T RealignerTargetCreator -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam --mismatchFraction 0.15 -L 1:10,000,000-10,050,000 -o %s", 1, - Arrays.asList("5085054c78e256432dc75c85a9ac631c")); - executeTest("test dbsnp", spec2); + Arrays.asList(md5)); + executeTest("test standard nt=4", spec2); + } + + @Test + public void testIntervals2() { + String md5 = "e0f745b79b679c225314a2abef4919ff"; + + WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( + "-T RealignerTargetCreator --known " + b36dbSNP129 + " -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,200,000 -o %s", + 1, + Arrays.asList(md5)); + executeTest("test with dbsnp nt=1", spec1); + WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( + "-nt 4 -T RealignerTargetCreator --known " + b36dbSNP129 + " -R " + b36KGReference + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,200,000 -o %s", + 1, + Arrays.asList(md5)); + executeTest("test with dbsnp nt=4", spec2); + } + + @Test + public void testKnownsOnly() { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( "-T RealignerTargetCreator -R " + b36KGReference + " --known " + validationDataLocation + "NA12878.chr1_10mb_11mb.slx.indels.vcf4 -BTI known -o %s", 1, From 4514bc350f3a05cdb5bba94fe092c819cb29bb4d Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Fri, 7 Oct 2011 11:19:29 -0400 Subject: [PATCH 223/363] More reliable way of finding the Tribble jar. --- .../VCFJarClassLoadingUnitTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VCFJarClassLoadingUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VCFJarClassLoadingUnitTest.java index 50eebe1795..c99afb9609 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VCFJarClassLoadingUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VCFJarClassLoadingUnitTest.java @@ -24,6 +24,7 @@ package org.broadinstitute.sting.utils.variantcontext; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.testng.annotations.Test; import java.io.File; @@ -39,7 +40,7 @@ public class VCFJarClassLoadingUnitTest { @Test public void testVCFJarClassLoading() throws ClassNotFoundException, MalformedURLException { URI vcfURI = new File("dist/vcf.jar").toURI(); - URI tribbleURI = new File("lib/tribble-24.jar").toURI(); + URI tribbleURI = getTribbleJarFile().toURI(); ClassLoader classLoader = new URLClassLoader(new URL[] {vcfURI.toURL(),tribbleURI.toURL()}, null); classLoader.loadClass("org.broadinstitute.sting.utils.variantcontext.VariantContext"); @@ -48,4 +49,21 @@ public void testVCFJarClassLoading() throws ClassNotFoundException, MalformedURL classLoader.loadClass("org.broadinstitute.sting.utils.codecs.vcf.VCFWriter"); classLoader.loadClass("org.broadinstitute.sting.utils.codecs.vcf.StandardVCFWriter"); } + + /** + * A very unsafe way of determining the current location of the Tribble jar file. Assumes that + * the tribble jar (as opposed to the constituent tribble classes) is on the classpath. + * + * This method might or might not work when built via IntelliJ's debugger. + * + * @return The file representing the tribble jar. + */ + private File getTribbleJarFile() { + String[] classPath = System.getProperty("java.class.path").split(File.pathSeparator); + for(String classPathEntry: classPath) { + if(classPathEntry.contains("tribble")) + return new File(classPathEntry); + } + throw new ReviewedStingException("Unable to find Tribble jar file"); + } } From ff3dccd0624d3ffe268d05dcbb163b4d3849e45f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 7 Oct 2011 12:04:53 -0700 Subject: [PATCH 224/363] Fixing errors in queueJobReport runtime unit --- public/R/queueJobReport.R | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/R/queueJobReport.R b/public/R/queueJobReport.R index de6aa045ee..866766c2c0 100644 --- a/public/R/queueJobReport.R +++ b/public/R/queueJobReport.R @@ -12,20 +12,20 @@ if ( onCMDLine ) { inputFileName = args[1] outputPDF = args[2] } else { - inputFileName = "~/Desktop/Q-30033@gsa1.jobreport.txt" + inputFileName = "~/Desktop/broadLocal/GATK/unstable/wgs.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 } RUNTIME_UNITS = "(hours)" -ORIGINAL_UNITS_TO_SECONDS = 1/1000/60/60 +ORIGINAL_UNITS_TO_RUNTIME_UNITS = 1/1000/60/60 # # Helper function to aggregate all of the jobs in the report across all tables # allJobsFromReport <- function(report) { - names <- c("jobName", "startTime", "analysisName", "doneTime", "exechosts") + names <- c("jobName", "startTime", "analysisName", "doneTime", "exechosts", "runtime") sub <- lapply(report, function(table) table[,names]) do.call("rbind", sub) } @@ -44,8 +44,8 @@ plotJobsGantt <- function(gatkReport, sortOverall, includeText) { } allJobs$index = 1:nrow(allJobs) minTime = min(allJobs$startTime) - allJobs$relStartTime = (allJobs$startTime - minTime) * ORIGINAL_UNITS_TO_SECONDS - allJobs$relDoneTime = (allJobs$doneTime - minTime) * ORIGINAL_UNITS_TO_SECONDS + allJobs$relStartTime = allJobs$startTime - minTime + allJobs$relDoneTime = allJobs$doneTime - minTime allJobs$ganttName = paste(allJobs$jobName, "@", allJobs$exechosts) maxRelTime = max(allJobs$relDoneTime) p <- ggplot(data=allJobs, aes(x=relStartTime, y=index, color=analysisName)) @@ -54,7 +54,7 @@ plotJobsGantt <- function(gatkReport, sortOverall, includeText) { if ( includeText ) p <- p + geom_text(aes(x=relDoneTime, label=ganttName, hjust=-0.2), size=2) p <- p + xlim(0, maxRelTime * 1.1) - p <- p + xlab(paste("Start time (relative to first job)", RUNTIME_UNITS)) + p <- p + xlab(paste("Start time, relative to first job", RUNTIME_UNITS)) p <- p + ylab("Job number") p <- p + opts(title=title) print(p) @@ -121,7 +121,7 @@ plotGroup <- function(groupTable) { if ( length(groupAnnotations) == 1 && dim(sub)[1] > 1 ) { # todo -- how do we group by annotations? p <- ggplot(data=sub, aes(x=runtime)) + geom_histogram() - p <- p + xlab("runtime in seconds") + ylab("No. of jobs") + p <- p + xlab(paste("runtime", RUNTIME_UNITS)) + ylab("No. of jobs") p <- p + opts(title=paste("Job runtime histogram for", name)) print(p) } @@ -141,9 +141,9 @@ print(paste("Project :", inputFileName)) convertUnits <- function(gatkReportData) { convertGroup <- function(g) { - g$runtime = g$runtime * ORIGINAL_UNITS_TO_SECONDS - g$startTime = g$startTime * ORIGINAL_UNITS_TO_SECONDS - g$doneTime = g$doneTime * ORIGINAL_UNITS_TO_SECONDS + g$runtime = g$runtime * ORIGINAL_UNITS_TO_RUNTIME_UNITS + g$startTime = g$startTime * ORIGINAL_UNITS_TO_RUNTIME_UNITS + g$doneTime = g$doneTime * ORIGINAL_UNITS_TO_RUNTIME_UNITS g } lapply(gatkReportData, convertGroup) From e94e6ba101fc30f3364782db2cec5efc3bffe5f3 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 8 Oct 2011 08:47:58 -0700 Subject: [PATCH 225/363] A UnitTest to ensure that the order of alleles is maintained -> A, C, T and A, T, C are different and must be maintained. The constructors were doing this appropriately, so nothing needed to be changed --- .../variantcontext/VariantContextUnitTest.java | 14 +++++++++++++- 1 file changed, 13 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 663eb9ef6d..cee7a4782f 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -11,12 +11,13 @@ import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class VariantContextUnitTest extends BaseTest { - Allele A, Aref, T, Tref; + Allele A, Aref, C, T, Tref; Allele del, delRef, ATC, ATCref; // A [ref] / T at 10 @@ -45,6 +46,7 @@ public void before() { delRef = Allele.create("-", true); A = Allele.create("A"); + C = Allele.create("C"); Aref = Allele.create("A", true); T = Allele.create("T"); Tref = Allele.create("T", true); @@ -132,6 +134,16 @@ public void testDetermineTypes() { Assert.assertEquals(vc.getType(), VariantContext.Type.SYMBOLIC); } + @Test + 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); + Assert.assertEquals(new ArrayList(naturalVC.getAlleles()), allelesNaturalOrder); + Assert.assertEquals(new ArrayList(unnaturalVC.getAlleles()), allelesUnnaturalOrder); + } + @Test public void testCreatingSNPVariantContext() { From c67f6c076b55e79655c1701e0f5f7fe55d6c893b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 8 Oct 2011 17:39:53 -0700 Subject: [PATCH 226/363] simpleMerge now preserves allele order -- UnitTests for dangerous PL merging cases in the multi-allelic case. The new behavior is correct --- .../variantcontext/VariantContextUtils.java | 29 ++++++++-- .../VariantContextUtilsUnitTest.java | 53 +++++++++++++++---- 2 files changed, 70 insertions(+), 12 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 ae648d547b..437693a462 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -29,6 +29,7 @@ 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; @@ -44,6 +45,7 @@ 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"; @@ -511,7 +513,7 @@ public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, final String name = first.getSource(); final Allele refAllele = determineReferenceAllele(VCs); - final Set alleles = new TreeSet(); + final Set alleles = new LinkedHashSet(); final Set filters = new TreeSet(); final Map attributes = new TreeMap(); final Set inconsistentAttributes = new HashSet(); @@ -604,11 +606,14 @@ public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, } } - // 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) + // 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) for ( VariantContext vc : VCs ) { if (vc.alleles.size() == 1) continue; - if ( vc.alleles.size() != alleles.size()) { + 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); break; } @@ -661,6 +666,24 @@ else if ( variantSources.isEmpty() ) // everyone was reference return merged; } + private static final boolean hasPLIncompatibleAlleles(final Set alleleSet1, final Set 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())) 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 08db7bcde2..c8cbbfd8eb 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -66,8 +66,8 @@ private Genotype makeG(String sample, Allele a1, Allele a2) { return new Genotype(sample, Arrays.asList(a1, a2)); } - private Genotype makeG(String sample, Allele a1, Allele a2, double log10pError, double l1, double l2, double l3) { - return new Genotype(sample, Arrays.asList(a1, a2), log10pError, new double[]{l1,l2,l3}); + private Genotype makeG(String sample, Allele a1, Allele a2, double log10pError, double... pls) { + return new Genotype(sample, Arrays.asList(a1, a2), log10pError, pls); } @@ -144,15 +144,18 @@ public Object[][] mergeAllelesData() { new MergeAllelesTest(Arrays.asList(Aref, T), Arrays.asList(Aref, C), - Arrays.asList(Aref, C, T)); // sorted by allele + Arrays.asList(Aref, T, C)); // in order of appearence new MergeAllelesTest(Arrays.asList(Aref, C, T), Arrays.asList(Aref, C), Arrays.asList(Aref, C, T)); + new MergeAllelesTest(Arrays.asList(Aref, C, T), Arrays.asList(Aref, C, T)); + new MergeAllelesTest(Arrays.asList(Aref, T, C), Arrays.asList(Aref, T, C)); + new MergeAllelesTest(Arrays.asList(Aref, T, C), Arrays.asList(Aref, C), - Arrays.asList(Aref, C, T)); // sorted by allele + Arrays.asList(Aref, T, C)); // in order of appearence // The following is actually a pathological case - there's no way on a vcf to represent a null allele that's non-variant. // The code converts this (correctly) to a single-base non-variant vc with whatever base was there as a reference. @@ -167,10 +170,12 @@ public Object[][] mergeAllelesData() { Arrays.asList(delRef, ATC, ATCATC), Arrays.asList(delRef, ATC, ATCATC)); + // alleles in the order we see them new MergeAllelesTest(Arrays.asList(delRef, ATCATC), Arrays.asList(delRef, ATC, ATCATC), - Arrays.asList(delRef, ATC, ATCATC)); + Arrays.asList(delRef, ATCATC, ATC)); + // same new MergeAllelesTest(Arrays.asList(delRef, ATC), Arrays.asList(delRef, ATCATC), Arrays.asList(delRef, ATC, ATCATC)); @@ -446,7 +451,31 @@ public Object[][] mergeGenotypesData() { 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 + // + + // 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))); + + // 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))); + + // 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))); + + // 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))); + 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)), @@ -458,6 +487,12 @@ public Object[][] mergeGenotypesData() { // 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))); + 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)), + // 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))); + return MergeGenotypesTest.getTests(MergeGenotypesTest.class); } @@ -493,11 +528,11 @@ private void assertGenotypesAreMostlyEqual(Map actual, Map Date: Sun, 9 Oct 2011 10:36:14 -0700 Subject: [PATCH 227/363] UnitTests for allele getting functions in VC in prep for move from set to list --- .../VariantContextUnitTest.java | 72 ++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) 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 cee7a4782f..39f8c0f39f 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -6,10 +6,10 @@ import org.broadinstitute.sting.BaseTest; -import org.testng.Assert; import org.testng.annotations.BeforeSuite; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.testng.Assert; import java.util.ArrayList; import java.util.Arrays; @@ -455,14 +455,70 @@ public void testVCromGenotypes() { Assert.assertEquals(0, vc5.getChromosomeCount(Aref)); } + // -------------------------------------------------------------------------------- + // + // Test allele merging + // + // -------------------------------------------------------------------------------- - @Test - public void testManipulatingAlleles() { - // todo -- add tests that call add/set/remove + private class GetAllelesTest extends TestDataProvider { + List alleles; + + private GetAllelesTest(String name, Allele... arg) { + super(GetAllelesTest.class, name); + this.alleles = Arrays.asList(arg); + } + + public String toString() { + return String.format("%s input=%s", super.toString(), alleles); + } } - @Test - public void testManipulatingGenotypes() { - // todo -- add tests that call add/set/remove + @DataProvider(name = "getAlleles") + public Object[][] mergeAllelesData() { + new GetAllelesTest("A*", Aref); + new GetAllelesTest("-*", delRef); + new GetAllelesTest("A*/C", Aref, C); + new GetAllelesTest("A*/C/T", Aref, C, T); + new GetAllelesTest("A*/T/C", Aref, T, C); + new GetAllelesTest("A*/C/T/-", Aref, C, T, del); + new GetAllelesTest("A*/T/C/-", Aref, T, C, del); + new GetAllelesTest("A*/-/T/C", Aref, del, T, C); + + return GetAllelesTest.getTests(GetAllelesTest.class); + } + + @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'); + + 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"); + Assert.assertEquals(vc.getAlternateAlleles(), altAlleles, "VC alt alleles not the same as input alt alleles"); + + + for ( int i = 0; i < cfg.alleles.size(); i++ ) { + final Allele inputAllele = cfg.alleles.get(i); + + Assert.assertTrue(vc.hasAllele(inputAllele)); + if ( inputAllele.isReference() ) { + final Allele nonRefVersion = Allele.create(inputAllele.getBases(), false); + Assert.assertTrue(vc.hasAllele(nonRefVersion, true)); + Assert.assertFalse(vc.hasAllele(nonRefVersion, false)); + } + + Assert.assertEquals(inputAllele, vc.getAllele(inputAllele.getBaseString())); + Assert.assertEquals(inputAllele, vc.getAllele(inputAllele.getBases())); + + if ( i > 0 ) { // it's an alt allele + Assert.assertEquals(inputAllele, vc.getAlternateAllele(i-1)); + } + } + + final Allele missingAllele = Allele.create("AACCGGTT"); // does not exist + Assert.assertNull(vc.getAllele(missingAllele.getBases())); + Assert.assertFalse(vc.hasAllele(missingAllele)); + Assert.assertFalse(vc.hasAllele(missingAllele, true)); } } From 46e7370128cdc237d90f97a45167f25f8a3b6f39 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sun, 9 Oct 2011 11:45:55 -0700 Subject: [PATCH 228/363] this.allele, getAlleles(), and getAltAlleles() now return List not set -- Changes associated code throughout the codebase -- Updated necessary (but minimal) UnitTests to reflect new behavior -- Much better makealleles() function in VC.java that enforces a lot of key constraints in VC --- .../annotator/AlleleBalanceBySample.java | 2 +- .../AlleleFrequencyCalculationModel.java | 3 +- .../genotyper/ExactAFCalculationModel.java | 9 +-- .../genotyper/GridSearchAFEstimation.java | 2 +- .../genotyper/UnifiedGenotyperEngine.java | 4 +- .../evaluators/ValidationReport.java | 5 +- .../VariantValidationAssessor.java | 2 +- .../utils/variantcontext/VariantContext.java | 71 +++++++++---------- .../variantcontext/VariantContextUtils.java | 2 +- .../diffengine/DiffableReaderUnitTest.java | 4 +- .../VariantContextUnitTest.java | 8 ++- .../VariantContextUtilsUnitTest.java | 2 +- 12 files changed, 55 insertions(+), 59 deletions(-) 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 75c4037d5b..820fd248ae 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 @@ -47,7 +47,7 @@ private Double annotateSNP(AlignmentContext stratifiedContext, VariantContext vc if (!g.isHet()) return null; - Set altAlleles = vc.getAlternateAlleles(); + Collection altAlleles = vc.getAlternateAlleles(); if ( altAlleles.size() == 0 ) return null; 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 70f3c6a1a7..1bb6970561 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 @@ -33,6 +33,7 @@ import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.io.PrintStream; +import java.util.List; import java.util.Map; import java.util.Set; @@ -76,7 +77,7 @@ protected AlleleFrequencyCalculationModel(UnifiedArgumentCollection UAC, int N, */ protected abstract void getLog10PNonRef(RefMetaDataTracker tracker, ReferenceContext ref, - Map GLs, Set Alleles, + Map 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 8a3a978237..f87eae7814 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,19 +29,14 @@ 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.SimpleTimer; 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.VariantContext; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.io.PrintStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; public class ExactAFCalculationModel extends AlleleFrequencyCalculationModel { // @@ -58,7 +53,7 @@ protected ExactAFCalculationModel(UnifiedArgumentCollection UAC, int N, Logger l public void getLog10PNonRef(RefMetaDataTracker tracker, ReferenceContext ref, - Map GLs, Setalleles, + Map GLs, List alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); 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 10b646d637..f4195e5f08 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 @@ -54,7 +54,7 @@ protected GridSearchAFEstimation(UnifiedArgumentCollection UAC, int N, Logger lo protected void getLog10PNonRef(RefMetaDataTracker tracker, ReferenceContext ref, - Map GLs, Setalleles, + Map GLs, List alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { initializeAFMatrix(GLs); 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 9e8cc07a6b..b72b68f9ff 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 @@ -423,7 +423,7 @@ private VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, Refere int endLoc = calculateEndPos(vc.getAlleles(), vc.getReference(), loc); - Set myAlleles = vc.getAlleles(); + 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); @@ -447,7 +447,7 @@ else if (rawContext.hasBasePileup()) return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } - private int calculateEndPos(Set alleles, Allele refAllele, GenomeLoc loc) { + 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; 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 c60586017a..3b4967cad7 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 @@ -10,6 +10,7 @@ import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; +import java.util.Collection; import java.util.Set; /** @@ -142,8 +143,8 @@ public SiteStatus calcSiteStatus(VariantContext vc) { public boolean haveDifferentAltAlleles(VariantContext eval, VariantContext comp) { - Set evalAlts = eval.getAlternateAlleles(); - Set compAlts = comp.getAlternateAlleles(); + Collection evalAlts = eval.getAlternateAlleles(); + Collection compAlts = comp.getAlternateAlleles(); if ( evalAlts.size() != compAlts.size() ) { return true; } else { 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 8eaf976d02..4e6cc722db 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 @@ -237,7 +237,7 @@ private VariantContext addVariantInformationToCall(VariantContext vContext) { 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)); - Set altAlleles = vContext.getAlternateAlleles(); + Collection altAlleles = vContext.getAlternateAlleles(); int altAlleleCount = altAlleles.size() == 0 ? 0 : vContext.getChromosomeCount(altAlleles.iterator().next()); if ( !isViolation && altAlleleCount > 0 ) numTrueVariants++; 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 05e21c8b8b..eac6d70b59 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -181,7 +181,7 @@ public class VariantContext implements Feature { // to enable tribble intergrati protected Type type = null; /** A set of the alleles segregating in this context */ - protected LinkedHashSet alleles = null; + final protected List alleles; /** A mapping from sampleName -> genotype objects for all genotypes associated with this context */ protected Map genotypes = null; @@ -355,7 +355,7 @@ private VariantContext(String source, String contig, long start, long stop, 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 = alleleCollectionToSet(new LinkedHashSet(), alleles); + this.alleles = makeAlleles(alleles); if ( genotypes == null ) { genotypes = NO_GENOTYPES; } @@ -445,7 +445,7 @@ public VariantContext subContextFromGenotypes(Collection 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, Set alleles) { + 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()); } @@ -687,17 +687,6 @@ public Allele getReference() { return ref; } - /** Private helper routine that grabs the reference allele but doesn't throw an error if there's no such allele */ - -// private Allele getReferenceWithoutError() { -// for ( Allele allele : getAlleles() ) { -// if ( allele.isReference() ) { -// return allele; -// } -// } -// -// return null; -// } /** * @return true if the context is strictly bi-allelic @@ -754,7 +743,7 @@ public boolean hasAllele(Allele allele, boolean ignoreRefState) { * * @return the set of alleles */ - public Set getAlleles() { return alleles; } + public List getAlleles() { return alleles; } /** * Gets the alternate alleles. This method should return all the alleles present at the location, @@ -763,14 +752,8 @@ public boolean hasAllele(Allele allele, boolean ignoreRefState) { * * @return the set of alternate alleles */ - public Set getAlternateAlleles() { - LinkedHashSet altAlleles = new LinkedHashSet(); - for ( Allele allele : alleles ) { - if ( allele.isNonReference() ) - altAlleles.add(allele); - } - - return Collections.unmodifiableSet(altAlleles); + public List getAlternateAlleles() { + return alleles.subList(1, alleles.size()); } /** @@ -797,14 +780,7 @@ public List getIndelLengths() { * @throws IllegalArgumentException if i is invalid */ public Allele getAlternateAllele(int i) { - int n = 0; - - for ( Allele allele : alleles ) { - if ( allele.isNonReference() && n++ == i ) - return allele; - } - - throw new IllegalArgumentException("Requested " + i + " alternative allele but there are only " + n + " alternative alleles " + this); + return alleles.get(i+1); } /** @@ -813,8 +789,8 @@ public Allele getAlternateAllele(int i) { * regardless of ordering. Otherwise returns false. */ public boolean hasSameAlternateAllelesAs ( VariantContext other ) { - Set thisAlternateAlleles = getAlternateAlleles(); - Set otherAlternateAlleles = other.getAlternateAlleles(); + List thisAlternateAlleles = getAlternateAlleles(); + List otherAlternateAlleles = other.getAlternateAlleles(); if ( thisAlternateAlleles.size() != otherAlternateAlleles.size() ) { return false; @@ -1121,7 +1097,7 @@ public void validateAlternateAlleles() { if ( !hasGenotypes() ) return; - Set reportedAlleles = getAlleles(); + List reportedAlleles = getAlleles(); Set observedAlleles = new HashSet(); observedAlleles.add(getReference()); for ( Genotype g : getGenotypes().values() ) { @@ -1371,17 +1347,34 @@ public String toString() { } // protected basic manipulation routines - private static LinkedHashSet alleleCollectionToSet(LinkedHashSet dest, Collection alleles) { - for ( Allele a : alleles ) { - for ( Allele b : dest ) { + 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); } - dest.add(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 a single reference allele: " + alleles); + alleleList.add(0, a); + sawRef = true; + } + else + alleleList.add(a); } - return dest; + 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 a single reference allele: " + alleles); + + return alleleList; } public static Map genotypeCollectionToMap(Map dest, Collection genotypes) { 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 437693a462..74ab7074aa 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -666,7 +666,7 @@ else if ( variantSources.isEmpty() ) // everyone was reference return merged; } - private static final boolean hasPLIncompatibleAlleles(final Set alleleSet1, final Set alleleSet2) { + private static final boolean hasPLIncompatibleAlleles(final Collection alleleSet1, final Collection alleleSet2) { final Iterator it1 = alleleSet1.iterator(); final Iterator it2 = alleleSet2.iterator(); diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffableReaderUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffableReaderUnitTest.java index dee7bbd884..46b0df5b47 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffableReaderUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/diffengine/DiffableReaderUnitTest.java @@ -70,7 +70,7 @@ public void testPluggableDiffableReaders() { private static void testLeaf(DiffNode rec, String field, Object expected) { DiffElement value = rec.getElement(field); Assert.assertNotNull(value, "Expected to see leaf named " + field + " in rec " + rec); - Assert.assertEquals(value.getValue().getValue(), expected, "Expected to leaf named " + field + " to have value " + expected + " in rec " + rec); + Assert.assertEquals(value.getValue().getValue(), expected, "Expected to see leaf named " + field + " to have value " + expected + " in rec " + rec + " but got instead " + value.getValue().getValue()); } @Test(enabled = true, dependsOnMethods = "testPluggableDiffableReaders") @@ -95,7 +95,7 @@ public void testVCF1() { testLeaf(rec1, "POS", 2646); testLeaf(rec1, "ID", "rs62635284"); testLeaf(rec1, "REF", Allele.create("G", true)); - testLeaf(rec1, "ALT", new HashSet(Arrays.asList(Allele.create("A")))); + testLeaf(rec1, "ALT", Arrays.asList(Allele.create("A"))); testLeaf(rec1, "QUAL", 0.15); testLeaf(rec1, "FILTER", Collections.emptySet()); testLeaf(rec1, "AC", "2"); 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 39f8c0f39f..9026f09d65 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; @@ -261,11 +262,16 @@ public void testBadConstructorArgs2() { new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, del)); } - @Test (expectedExceptions = IllegalStateException.class) + @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs3() { new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(del)); } + @Test (expectedExceptions = IllegalArgumentException.class) + public void testBadConstructorArgs4() { + new VariantContext("test", insLoc, insLocStart, insLocStop, Collections.emptyList()); + } + @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgsDuplicateAlleles1() { new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(Aref, T, T)); 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 c8cbbfd8eb..845d9c216d 100644 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUtilsUnitTest.java @@ -439,7 +439,7 @@ public Object[][] mergeGenotypesData() { 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, C, T), makeG("s1", Aref, T, 1), 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)), From a4bb842958f736a13af78a087588041e96b4cc0f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 10 Oct 2011 11:04:07 -0400 Subject: [PATCH 229/363] RankSum tests have lightly different MD5 results based on allele order -- UG GENOTYPE_GIVEN_ALLELES now uses the order of alleles in the VCF, so this changes the MD5 --- .../UnifiedGenotyperIntegrationTest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 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 07b2f0566d..ec62c4bfd4 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 @@ -257,34 +257,40 @@ public void testMultiTechnologyIndels() { } @Test - public void testWithIndelAllelesPassedIn() { + public void testWithIndelAllelesPassedIn1() { 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("408d3aba4d094c067fc00a43992c2292")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); + } + @Test + public void testWithIndelAllelesPassedIn2() { WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( 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("94977d6e42e764280e9deaf4e3ac8c80")); + Arrays.asList("5e4e09354410b76fc0d822050d84132a")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); + } + + @Test + 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 + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,080,000", 1, - Arrays.asList("e66b7321e2ac91742ad3ef91040daafd")); + Arrays.asList("c599eedbeb422713b8a28529e805e4ae")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); + } + @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 + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("37e891bf1ac40caec9ea228f39c27e44")); + Arrays.asList("37d908a682ac269f8f19dec939ff5b01")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); - } - - - } From 3e6c16d961dc411c2be01fa55f7cb42b991f0137 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 10 Oct 2011 11:04:38 -0400 Subject: [PATCH 230/363] CombineVariants preserves allele order --- .../CombineVariantsIntegrationTest.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java index 3b1a973695..e30187a7ca 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java @@ -78,26 +78,26 @@ public void combinePLs(String file1, String file2, String md5) { executeTest("combine PLs 1:" + new File(file1).getName() + " 2:" + new File(file2).getName(), spec); } - @Test public void test1SNP() { test1InOut("pilot2.snps.vcf4.genotypes.vcf", "c608b9fc1e36dba6cebb4f259883f9f0"); } - @Test public void test2SNP() { test1InOut("pilot2.snps.vcf4.genotypes.vcf", "20caad94411d6ab48153b214de916df8", " -setKey foo"); } - @Test public void test3SNP() { test1InOut("pilot2.snps.vcf4.genotypes.vcf", "004f3065cb1bc2ce2f9afd695caf0b48", " -setKey null"); } + @Test public void test1SNP() { test1InOut("pilot2.snps.vcf4.genotypes.vcf", "ea0a660cd04101ce7b534aba0310721d"); } + @Test public void test2SNP() { test1InOut("pilot2.snps.vcf4.genotypes.vcf", "cb0350e7a9d2483993482b69f5432b64", " -setKey foo"); } + @Test public void test3SNP() { test1InOut("pilot2.snps.vcf4.genotypes.vcf", "0571c48cc59cf244779caae52d562e79", " -setKey null"); } @Test public void testOfficialCEUPilotCalls() { test1InOut("CEU.trio.2010_03.genotypes.vcf.gz", "c9c901ff9ef2a982624b203a8086dff0"); } // official project VCF files in tabix format - @Test public void test1Indel1() { test1InOut("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "7593be578d4274d672fc22fced38012b"); } + @Test public void test1Indel1() { test1InOut("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "75901304abc1daa41b1906f881aa7bbc"); } @Test public void test1Indel2() { test1InOut("CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "1cd467863c4e948fadd970681552d57e"); } - @Test public void combineWithPLs() { combinePLs("combine.3.vcf", "combine.4.vcf", "0f873fed02aa99db5b140bcd6282c10a"); } + @Test public void combineWithPLs() { combinePLs("combine.3.vcf", "combine.4.vcf", "d08e933b6c81246e998d3ece50ddfdcc"); } - @Test public void combineTrioCalls() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", "", "1d5a021387a8a86554db45a29f66140f"); } // official project VCF files in tabix format - @Test public void combineTrioCallsMin() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", " -minimalVCF", "96941ee177b0614a9879af0ac3218963"); } // official project VCF files in tabix format - @Test public void combine2Indels() { combine2("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "f1c8720fde62687c2e861217670d8b3c"); } + @Test public void combineTrioCalls() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", "", "01967686e0e02dbccd2590b70f2d049b"); } // official project VCF files in tabix format + @Test public void combineTrioCallsMin() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "YRI.trio.2010_03.genotypes.vcf.gz", " -minimalVCF", "8c113199c4a93a4a408104b735d59044"); } // official project VCF files in tabix format + @Test public void combine2Indels() { combine2("CEU.dindel.vcf4.trio.2010_06.indel.genotypes.vcf", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "30e96a0cb614cd5bc056e1f7ec6d10bd"); } @Test public void combineSNPsAndIndels() { combine2("CEU.trio.2010_03.genotypes.vcf.gz", "CEU.dindel.vcf4.low_coverage.2010_06.indel.genotypes.vcf", "", "e144b6283765494bfe8189ac59965083"); } - @Test public void uniqueSNPs() { combine2("pilot2.snps.vcf4.genotypes.vcf", "yri.trio.gatk_glftrio.intersection.annotated.filtered.chr1.vcf", "", "89f55abea8f59e39d1effb908440548c"); } + @Test public void uniqueSNPs() { combine2("pilot2.snps.vcf4.genotypes.vcf", "yri.trio.gatk_glftrio.intersection.annotated.filtered.chr1.vcf", "", "78a49597f1abf1c738e67d50c8fbed2b"); } - @Test public void omniHM3Union() { combineSites(" -filteredRecordsMergeType KEEP_IF_ANY_UNFILTERED", "c6adeda751cb2a08690dd9202356629f"); } - @Test public void omniHM3Intersect() { combineSites(" -filteredRecordsMergeType KEEP_IF_ALL_UNFILTERED", "3a08fd5ee18993dfc8882156ccf5d2e9"); } + @Test public void omniHM3Union() { combineSites(" -filteredRecordsMergeType KEEP_IF_ANY_UNFILTERED", "9253d61ddb52c429adf0e153cef494ca"); } + @Test public void omniHM3Intersect() { combineSites(" -filteredRecordsMergeType KEEP_IF_ALL_UNFILTERED", "5012dfe65cf7e7d8f014e97e4a996aea"); } @Test public void threeWayWithRefs() { WalkerTestSpec spec = new WalkerTestSpec( @@ -127,10 +127,10 @@ public void combineComplexSites(String args, String md5) { executeTest("combineComplexSites 1:" + new File(file1).getName() + " 2:" + new File(file2).getName() + " args = " + args, spec); } - @Test public void complexTestFull() { combineComplexSites("", "b5a53ee92bdaacd2bb3327e9004ae058"); } - @Test public void complexTestMinimal() { combineComplexSites(" -minimalVCF", "df96cb3beb2dbb5e02f80abec7d3571e"); } - @Test public void complexTestSitesOnly() { combineComplexSites(" -sites_only", "f704caeaaaed6711943014b847fe381a"); } - @Test public void complexTestSitesOnlyMinimal() { combineComplexSites(" -sites_only -minimalVCF", "f704caeaaaed6711943014b847fe381a"); } + @Test public void complexTestFull() { combineComplexSites("", "2842337e9943366f7a4d5f148f701b8c"); } + @Test public void complexTestMinimal() { combineComplexSites(" -minimalVCF", "39724318e6265d0318a3fe4609612785"); } + @Test public void complexTestSitesOnly() { combineComplexSites(" -sites_only", "fe9bb02ab8b3d0dd2ad6373ebdb6d915"); } + @Test public void complexTestSitesOnlyMinimal() { combineComplexSites(" -sites_only -minimalVCF", "fe9bb02ab8b3d0dd2ad6373ebdb6d915"); } @Test public void combineDBSNPDuplicateSites() { From e3ff4f42664b5baa7f979d26fac2d8d43f931532 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 10 Oct 2011 11:05:02 -0400 Subject: [PATCH 231/363] Failing MD5 because output now contains absolute path --- .../gatk/walkers/diffengine/DiffObjectsIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1f11b58866..77a76d3880 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 String toString() { @DataProvider(name = "data") public Object[][] createData() { - new TestParams(testDir + "diffTestMaster.vcf", testDir + "diffTestTest.vcf", "dc1ca75c6ecf32641967d61e167acfff"); - new TestParams(testDir + "exampleBAM.bam", testDir + "exampleBAM.simple.bam", "df0fcb568a3a49fc74830103b2e26f6c"); + new TestParams(testDir + "diffTestMaster.vcf", testDir + "diffTestTest.vcf", "996dd8f24f2db98305d0fd8f155fc7f9"); + new TestParams(testDir + "exampleBAM.bam", testDir + "exampleBAM.simple.bam", "bf4e25cbc748e2441afd891e5c2722d6"); return TestParams.getTests(TestParams.class); } From b1124b2b61e68c356bfdac4fd98081bc4aa5074c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 10 Oct 2011 13:22:59 -0400 Subject: [PATCH 232/363] Trivial Qscript to evaluate the impact of max deletion fraction in UG From fb72bcf732b7760779a9a338b0cf27318982803b Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 10 Oct 2011 15:10:57 -0400 Subject: [PATCH 233/363] DiffObjects no longer prints out the file name in the status so MD5 are stable --- .../sting/gatk/walkers/diffengine/DiffObjectsWalker.java | 4 ++-- .../gatk/walkers/diffengine/DiffObjectsIntegrationTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsWalker.java index 5889d19e59..04437fdd1d 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/diffengine/DiffObjectsWalker.java @@ -219,10 +219,10 @@ public Integer reduce(Integer counter, Integer sum) { @Override public void onTraversalDone(Integer sum) { - out.printf("Reading master file %s%n", masterFile); + //out.printf("Reading master file %s%n", masterFile); DiffElement master = diffEngine.createDiffableFromFile(masterFile, MAX_OBJECTS_TO_READ); out.printf(" Read %d objects%n", master.size()); - out.printf("Reading test file %s%n", testFile); + //out.printf("Reading test file %s%n", testFile); DiffElement test = diffEngine.createDiffableFromFile(testFile, MAX_OBJECTS_TO_READ); out.printf(" Read %d objects%n", test.size()); 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 77a76d3880..c8a25c97bf 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 String toString() { @DataProvider(name = "data") public Object[][] createData() { - new TestParams(testDir + "diffTestMaster.vcf", testDir + "diffTestTest.vcf", "996dd8f24f2db98305d0fd8f155fc7f9"); - new TestParams(testDir + "exampleBAM.bam", testDir + "exampleBAM.simple.bam", "bf4e25cbc748e2441afd891e5c2722d6"); + new TestParams(testDir + "diffTestMaster.vcf", testDir + "diffTestTest.vcf", "ed377322c615abc7dceb97025076078d"); + new TestParams(testDir + "exampleBAM.bam", testDir + "exampleBAM.simple.bam", "02e46f5d2ebb3d49570850595b3f792e"); return TestParams.getTests(TestParams.class); } From 77c983c5b50385da4742d9bb85bdf8f6bb021131 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 10 Oct 2011 15:17:54 -0400 Subject: [PATCH 234/363] No one claimed this walker and it doesn't have integration tests or GATKdocs so it doesn't belong in public. --- .../coverage/CoarseCoverageWalker.java | 118 ------------------ 1 file changed, 118 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CoarseCoverageWalker.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CoarseCoverageWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CoarseCoverageWalker.java deleted file mode 100644 index 405a44c29f..0000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/CoarseCoverageWalker.java +++ /dev/null @@ -1,118 +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.coverage; - -import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.commandline.Argument; -import org.broadinstitute.sting.commandline.Output; -import org.broadinstitute.sting.gatk.contexts.ReferenceContext; -import org.broadinstitute.sting.gatk.refdata.ReadMetaDataTracker; -import org.broadinstitute.sting.gatk.walkers.ReadWalker; - -import java.io.PrintStream; - -/** - * Computes the coverage per every bases on the reference, or on the subset of the reference - * specified by the intervals provided. Moving to the next contig on the reference will always restart the - * count anew, even if the count of bases in the last chunk on the previous contig did not reach specified . - */ -public class CoarseCoverageWalker extends ReadWalker { - @Output - public PrintStream out; - - @Argument(fullName="granularity", shortName="G", doc="Granularity", required=true) - public Integer N; - - @Argument(fullName="dontZeroMissingContigs", shortName="Z", doc="If provided, we won't emit 0 counts for all sites on contigs skipped", required=true) - public boolean dontZeroMissingContigs; - - private int chunkStart = 1; // start of the current chunk we are counting reads for - private int contig = 0; // current contig we are on - private int count = 0; // number of reads overlapping with the current chunk - private static String zeroString = "0"; - - @Override - public void initialize() { - chunkStart = 1; - contig = 0; - count = 0; - } - - @Override - public Integer map(ReferenceContext ref, SAMRecord read, ReadMetaDataTracker metaDataTracker) { - - if ( read.getReadUnmappedFlag() || - read.getDuplicateReadFlag() || - read.getNotPrimaryAlignmentFlag() || - read.getMappingQuality() == 0 ) return 0; - - if ( read.getReferenceIndex() != contig ) { - // we jumped onto another contig - out.printf("%d%n", count); // print old count - count = 0; - - // if we skipped one or more contigs completely, make sure we print 0 counts over all of them: - for ( contig++ ; contig < read.getReferenceIndex() ; contig++) { - if ( ! dontZeroMissingContigs ) { - int contigSize = read.getHeader().getSequence(contig).getSequenceLength(); - for ( int k = 1 ; k < contigSize ; k+=N ) out.println(zeroString); - } - } - // by now we scrolled to the right contig - - chunkStart = 1; // reset chunk start - } - - // if our read is past the boundary of the current chunk, print old count(s) - // (for the current chunk and all chunks we may have skipped altogether) and reinitialize: - while ( chunkStart+N < read.getAlignmentStart() ) { - out.printf("%d%n", count); // print old count - count = 0; - chunkStart += N; - } - count++; - return 1; - } - - @Override - public Integer reduce(Integer value, Integer sum) { - return value+sum; - } - - @Override - public Integer reduceInit() { - return 0; - } - - @Override - public void onTraversalDone(Integer result) { - out.printf("%d%n", count); // print count from the last chunk - super.onTraversalDone(result); - - } - - -} From cffc959e582092117e340e7adfd1dec5b21ebefa Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 10 Oct 2011 23:55:57 -0400 Subject: [PATCH 235/363] Moving to archive instead, since no one owns it From 66b5646f958caa2ea9e72df85f2bce4ca315e08a Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 11 Oct 2011 13:56:00 -0400 Subject: [PATCH 236/363] Adding hidden options to the DPP controlling the default platform parameter to Count Covariates and the number of scatter gather jobs to generate are now available under hidden parameters --- .../qscripts/DataProcessingPipeline.scala | 19 +++++++++++++++++-- 1 file changed, 17 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 f97ce4884c..cb6bab9017 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala @@ -12,6 +12,7 @@ import net.sf.samtools.SAMFileHeader.SortOrder import org.broadinstitute.sting.queue.util.QScriptUtils import org.broadinstitute.sting.queue.function.ListWriterFunction +import org.broadinstitute.sting.commandline.Hidden class DataProcessingPipeline extends QScript { qscript => @@ -71,12 +72,24 @@ class DataProcessingPipeline extends QScript { var noValidation: Boolean = false + /**************************************************************************** + * Hidden Parameters + ****************************************************************************/ + @Hidden + @Input(doc="How many ways to scatter/gather", fullName="scatter_gather", shortName="sg", required=false) + var nContigs: Int = -1 + + @Hidden + @Input(doc="Define the default platform for Count Covariates -- useful for techdev purposes only.", fullName="default_platform", shortName="dp", required=false) + var defaultPlatform: String = _ + + /**************************************************************************** * Global Variables ****************************************************************************/ val queueLogDir: String = ".qlog/" // Gracefully hide Queue's output - var nContigs: Int = 0 // Use the number of contigs for scatter gathering jobs + var cleanModelEnum: ConsensusDeterminationModel = ConsensusDeterminationModel.USE_READS @@ -210,7 +223,8 @@ class DataProcessingPipeline extends QScript { // keep a record of the number of contigs in the first bam file in the list val bams = QScriptUtils.createListFromFile(input) - nContigs = QScriptUtils.getNumberOfContigs(bams(0)) + if (nContigs < 0) + nContigs = QScriptUtils.getNumberOfContigs(bams(0)) val realignedBAMs = if (useBWApe || useBWAse) {performAlignment(bams)} else {revertBams(bams)} @@ -325,6 +339,7 @@ class DataProcessingPipeline extends QScript { this.covariate ++= List("ReadGroupCovariate", "QualityScoreCovariate", "CycleCovariate", "DinucCovariate") this.input_file :+= inBam this.recal_file = outRecalFile + if (!defaultPlatform.isEmpty) this.default_platform = defaultPlatform if (!qscript.intervalString.isEmpty()) this.intervalsString ++= List(qscript.intervalString) else if (qscript.intervals != null) this.intervals :+= qscript.intervals this.scatterCount = nContigs From a2733a451f065b2c41fea282e63b9f05401d87cd Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Tue, 11 Oct 2011 19:31:45 -0400 Subject: [PATCH 237/363] Added NotCalled feature to GAV Added "not called" and "no status" to the truth table. Very useful. --- .../validation/GenotypeAndValidateWalker.java | 62 ++++++++++++++----- 1 file changed, 45 insertions(+), 17 deletions(-) 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 f416e94a09..c2f6e2099a 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.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.MutableVariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; @@ -266,8 +265,13 @@ public class GenotypeAndValidateWalker extends RodWalker 0 && context.getBasePileup().getBases().length < minDepth)) { counter.nUncovered = 1L; + if (vcComp.getAttribute("GV").equals("T")) + counter.nAltNotCalled = 1L; + else if (vcComp.getAttribute("GV").equals("F")) + counter.nRefNotCalled = 1L; + else + counter.nNoStatusNotCalled = 1L; + return counter; } @@ -382,7 +398,7 @@ public CountedData map( RefMetaDataTracker tracker, ReferenceContext ref, Alignm // If truth is a confident REF call if (call.isVariant()) { if (vcComp.isVariant()) - counter.nAltCalledAlt = 1L; // todo -- may wanna check if the alts called are the same? + counter.nAltCalledAlt = 1L; else { counter.nAltCalledRef = 1L; if ( printInterestingSites ) @@ -407,30 +423,41 @@ public CountedData map( RefMetaDataTracker tracker, ReferenceContext ref, Alignm } } else { - if (!vcComp.hasAttribute("GV")) - throw new UserException.BadInput("Variant has no GV annotation in the INFO field. " + vcComp.getChr() + ":" + vcComp.getStart()); - - +// if (!vcComp.hasAttribute("GV")) +// throw new UserException.BadInput("Variant has no GV annotation in the INFO field. " + vcComp.getChr() + ":" + vcComp.getStart()); if (call.isCalledAlt(callConf)) { if (vcComp.getAttribute("GV").equals("T")) counter.nAltCalledAlt = 1L; - else { + else if (vcComp.getAttribute("GV").equals("F")) { counter.nRefCalledAlt = 1L; if ( printInterestingSites ) System.out.println("Truth=REF Call=ALT at " + call.getChr() + ":" + call.getStart()); } + else + counter.nNoStatusCalledAlt = 1L; } else if (call.isCalledRef(callConf)) { if (vcComp.getAttribute("GV").equals("T")) { counter.nAltCalledRef = 1L; if ( printInterestingSites ) System.out.println("Truth=ALT Call=REF at " + call.getChr() + ":" + call.getStart()); - } else + } + else if (vcComp.getAttribute("GV").equals("F")) counter.nRefCalledRef = 1L; + + else + counter.nNoStatusCalledRef = 1L; } else { counter.nNotConfidentCalls = 1L; + if (vcComp.getAttribute("GV").equals("T")) + counter.nAltNotCalled = 1L; + else if (vcComp.getAttribute("GV").equals("F")) + counter.nRefNotCalled = 1L; + else + counter.nNoStatusNotCalled = 1L; + if ( printInterestingSites ) System.out.println("Truth is not confident at " + call.getChr() + ":" + call.getStart()); writeVariant = false; @@ -475,20 +502,21 @@ public void onTraversalDone( CountedData reduceSum ) { double sensitivity = 100 * ((double) reduceSum.nAltCalledAlt /( reduceSum.nAltCalledAlt + reduceSum.nAltCalledRef)); double specificity = (reduceSum.nRefCalledRef + reduceSum.nRefCalledAlt > 0) ? 100 * ((double) reduceSum.nRefCalledRef /( reduceSum.nRefCalledRef + reduceSum.nRefCalledAlt)) : 100; logger.info(String.format("Resulting Truth Table Output\n\n" + - "---------------------------------------------------\n" + - "\t\t|\tALT\t|\tREF\t\n" + - "---------------------------------------------------\n" + - "called alt\t|\t%d\t|\t%d\n" + - "called ref\t|\t%d\t|\t%d\n" + - "---------------------------------------------------\n" + + "------------------------------------------------------------------\n" + + "\t\t|\tALT\t|\tREF\t|\tNo Status\n" + + "------------------------------------------------------------------\n" + + "called alt\t|\t%d\t|\t%d\t|\t%d\n" + + "called ref\t|\t%d\t|\t%d\t|\t%d\n" + + "not called\t|\t%d\t|\t%d\t|\t%d\n" + + "------------------------------------------------------------------\n" + "positive predictive value: %f%%\n" + "negative predictive value: %f%%\n" + - "---------------------------------------------------\n" + + "------------------------------------------------------------------\n" + "sensitivity: %f%%\n" + "specificity: %f%%\n" + - "---------------------------------------------------\n" + + "------------------------------------------------------------------\n" + "not confident: %d\n" + "not covered: %d\n" + - "---------------------------------------------------\n", reduceSum.nAltCalledAlt, reduceSum.nRefCalledAlt, reduceSum.nAltCalledRef, reduceSum.nRefCalledRef, ppv, npv, sensitivity, specificity, reduceSum.nNotConfidentCalls, reduceSum.nUncovered)); + "------------------------------------------------------------------\n", reduceSum.nAltCalledAlt, reduceSum.nRefCalledAlt, reduceSum.nNoStatusCalledAlt, reduceSum.nAltCalledRef, reduceSum.nRefCalledRef, reduceSum.nNoStatusCalledRef, reduceSum.nAltNotCalled, reduceSum.nRefNotCalled, reduceSum.nNoStatusNotCalled, ppv, npv, sensitivity, specificity, reduceSum.nNotConfidentCalls, reduceSum.nUncovered)); } } From e53a952aeb10afca4bf863cb5cc9bb9e78d3ce14 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Wed, 12 Oct 2011 01:57:02 -0400 Subject: [PATCH 238/363] Added ION Torrent support to CountCovariates. --- .../sting/gatk/walkers/recalibration/CycleCovariate.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 5d07922a78..e117454f92 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 @@ -164,6 +164,7 @@ else if( read.getReadGroup().getPlatform().contains( "454" ) ) { // Some bams ha private static List LS454_NAMES = Arrays.asList("454"); private static List COMPLETE_GENOMICS_NAMES = Arrays.asList("COMPLETE"); private static List PACBIO_NAMES = Arrays.asList("PACBIO"); + private static List ION_TORRENT_NAMES = Arrays.asList("IONTORRENT"); private static boolean isPlatform(SAMRecord read, List names) { String pl = read.getReadGroup().getPlatform().toUpperCase(); @@ -224,10 +225,10 @@ public void getValues(SAMRecord read, Comparable[] comparable) { } //----------------------------- - // 454 + // 454 and Ion Torrent //----------------------------- - else if ( isPlatform(read, LS454_NAMES) ) { // Some bams have "LS454" and others have just "454" + else if ( isPlatform(read, LS454_NAMES) || isPlatform(read, ION_TORRENT_NAMES)) { // Some bams have "LS454" and others have just "454" final int readLength = read.getReadLength(); final byte[] bases = read.getReadBases(); From 9aecd504735fc687493cb5ee4a3c7d5a1d253705 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 12 Oct 2011 15:44:54 -0400 Subject: [PATCH 239/363] Adding ability to exclude annotations from the VA and UG lists. As described in the docs, this argument trumps all others (including -all) so that we can get around the SnpEff issue brought up by Menachem. Added integration test for it. --- .../walkers/annotator/VariantAnnotator.java | 14 +++++++-- .../annotator/VariantAnnotatorEngine.java | 29 ++++++++++++++++--- .../walkers/genotyper/UnifiedGenotyper.java | 9 +++++- .../VariantAnnotatorIntegrationTest.java | 26 +++++++++++------ 4 files changed, 62 insertions(+), 16 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 3be87da80e..7223260188 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 @@ -132,6 +132,13 @@ public class VariantAnnotator extends RodWalker implements Ann @Argument(fullName="annotation", shortName="A", doc="One or more specific annotations to apply to variant calls", required=false) protected List annotationsToUse = new ArrayList(); + /** + * Note that this argument has higher priority than the -A or -G arguments, + * so annotations will be excluded even if they are explicitly included with the other options. + */ + @Argument(fullName="excludeAnnotation", shortName="XA", doc="One or more specific annotations to exclude", required=false) + protected List annotationsToExclude = new ArrayList(); + /** * See the -list argument to view available groups. */ @@ -148,6 +155,9 @@ public class VariantAnnotator extends RodWalker implements Ann @Argument(fullName="expression", shortName="E", doc="One or more specific expressions to apply to variant calls; see documentation for more details", required=false) protected List expressionsToUse = new ArrayList(); + /** + * Note that the -XL argument can be used along with this one to exclude annotations. + */ @Argument(fullName="useAllAnnotations", shortName="all", doc="Use all possible annotations (not for the faint of heart)", required=false) protected Boolean USE_ALL_ANNOTATIONS = false; @@ -209,9 +219,9 @@ public void initialize() { } if ( USE_ALL_ANNOTATIONS ) - engine = new VariantAnnotatorEngine(this, getToolkit()); + engine = new VariantAnnotatorEngine(annotationsToExclude, this, getToolkit()); else - engine = new VariantAnnotatorEngine(annotationGroupsToUse, annotationsToUse, this, getToolkit()); + engine = new VariantAnnotatorEngine(annotationGroupsToUse, annotationsToUse, annotationsToExclude, this, getToolkit()); engine.initializeExpressions(expressionsToUse); // setup the header fields 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 e5effe6d8f..e4bc0d5d5c 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 @@ -73,19 +73,20 @@ public VAExpression(String fullEpression, List> bindi } // use this constructor if you want all possible annotations - public VariantAnnotatorEngine(AnnotatorCompatibleWalker walker, GenomeAnalysisEngine toolkit) { + public VariantAnnotatorEngine(List annotationsToExclude, AnnotatorCompatibleWalker walker, GenomeAnalysisEngine toolkit) { this.walker = walker; this.toolkit = toolkit; requestedInfoAnnotations = AnnotationInterfaceManager.createAllInfoFieldAnnotations(); requestedGenotypeAnnotations = AnnotationInterfaceManager.createAllGenotypeAnnotations(); + excludeAnnotations(annotationsToExclude); initializeDBs(); } // use this constructor if you want to select specific annotations (and/or interfaces) - public VariantAnnotatorEngine(List annotationGroupsToUse, List annotationsToUse, AnnotatorCompatibleWalker walker, GenomeAnalysisEngine toolkit) { + public VariantAnnotatorEngine(List annotationGroupsToUse, List annotationsToUse, List annotationsToExclude, AnnotatorCompatibleWalker walker, GenomeAnalysisEngine toolkit) { this.walker = walker; this.toolkit = toolkit; - initializeAnnotations(annotationGroupsToUse, annotationsToUse); + initializeAnnotations(annotationGroupsToUse, annotationsToUse, annotationsToExclude); initializeDBs(); } @@ -96,10 +97,30 @@ public void initializeExpressions(List expressionsToUse) { requestedExpressions.add(new VAExpression(expression, walker.getResourceRodBindings())); } - private void initializeAnnotations(List annotationGroupsToUse, List annotationsToUse) { + private void initializeAnnotations(List annotationGroupsToUse, List annotationsToUse, List annotationsToExclude) { AnnotationInterfaceManager.validateAnnotations(annotationGroupsToUse, annotationsToUse); requestedInfoAnnotations = AnnotationInterfaceManager.createInfoFieldAnnotations(annotationGroupsToUse, annotationsToUse); requestedGenotypeAnnotations = AnnotationInterfaceManager.createGenotypeAnnotations(annotationGroupsToUse, annotationsToUse); + excludeAnnotations(annotationsToExclude); + } + + private void excludeAnnotations(List annotationsToExclude) { + if ( annotationsToExclude.size() == 0 ) + return; + + List tempRequestedInfoAnnotations = new ArrayList(requestedInfoAnnotations.size()); + for ( InfoFieldAnnotation annotation : requestedInfoAnnotations ) { + if ( !annotationsToExclude.contains(annotation.getClass().getSimpleName()) ) + tempRequestedInfoAnnotations.add(annotation); + } + requestedInfoAnnotations = tempRequestedInfoAnnotations; + + List tempRequestedGenotypeAnnotations = new ArrayList(requestedGenotypeAnnotations.size()); + for ( GenotypeAnnotation annotation : requestedGenotypeAnnotations ) { + if ( !annotationsToExclude.contains(annotation.getClass().getSimpleName()) ) + tempRequestedGenotypeAnnotations.add(annotation); + } + requestedGenotypeAnnotations = tempRequestedGenotypeAnnotations; } private void initializeDBs() { 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 9fdf65015d..72dc217e14 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 @@ -149,6 +149,13 @@ public class UnifiedGenotyper extends LocusWalker annotationsToUse = new ArrayList(); + /** + * Which annotations to exclude from output in the VCF file. Note that this argument has higher priority than the -A or -G arguments, + * so annotations will be excluded even if they are explicitly included with the other options. + */ + @Argument(fullName="excludeAnnotation", shortName="XA", doc="One or more specific annotations to exclude", required=false) + protected List annotationsToExclude = new ArrayList(); + /** * Which groups of annotations to add to the output VCF file. See the VariantAnnotator -list argument to view available groups. */ @@ -210,7 +217,7 @@ public void initialize() { if ( verboseWriter != null ) verboseWriter.println("AFINFO\tLOC\tREF\tALT\tMAF\tF\tAFprior\tAFposterior\tNormalizedPosterior"); - annotationEngine = new VariantAnnotatorEngine(Arrays.asList(annotationClassesToUse), annotationsToUse, this, getToolkit()); + annotationEngine = new VariantAnnotatorEngine(Arrays.asList(annotationClassesToUse), annotationsToUse, annotationsToExclude, this, getToolkit()); UG_engine = new UnifiedGenotyperEngine(getToolkit(), UAC, logger, verboseWriter, annotationEngine, samples); // initialize the header 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 04bff8d416..2fed668c5f 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 @@ -31,7 +31,7 @@ public void testHasAnnotsNotAsking2() { @Test 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, + 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")); executeTest("test file has annotations, asking for annotations, #1", spec); } @@ -39,7 +39,7 @@ public void testHasAnnotsAsking1() { @Test 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, + 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")); executeTest("test file has annotations, asking for annotations, #2", spec); } @@ -63,7 +63,7 @@ public void testNoAnnotsNotAsking2() { @Test 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, + 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")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -71,15 +71,23 @@ public void testNoAnnotsAsking1() { @Test 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, + 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")); executeTest("test file doesn't have annotations, asking for annotations, #2", spec); } + @Test + 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")); + executeTest("test exclude annotations", spec); + } + @Test public void testOverwritingHeader() { WalkerTestSpec spec = new WalkerTestSpec( - baseTestString() + " -G \"Standard\" --variant:VCF " + validationDataLocation + "vcfexample4.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,001,292", 1, + baseTestString() + " -G Standard --variant:VCF " + validationDataLocation + "vcfexample4.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,001,292", 1, Arrays.asList("78d2c19f8107d865970dbaf3e12edd92")); executeTest("test overwriting header", spec); } @@ -87,7 +95,7 @@ public void testOverwritingHeader() { @Test public void testNoReads() { WalkerTestSpec spec = new WalkerTestSpec( - baseTestString() + " -G \"Standard\" --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -BTI variant", 1, + baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -BTI variant", 1, Arrays.asList("16e3a1403fc376320d7c69492cad9345")); executeTest("not passing it any reads", spec); } @@ -103,7 +111,7 @@ public void testDBTagWithDbsnp() { @Test public void testDBTagWithHapMap() { WalkerTestSpec spec = new WalkerTestSpec( - baseTestString() + " --comp:H3 " + validationDataLocation + "fakeHM3.vcf -G \"Standard\" --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -BTI variant", 1, + baseTestString() + " --comp:H3 " + validationDataLocation + "fakeHM3.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -BTI variant", 1, Arrays.asList("1bc01c5b3bd0b7aef75230310c3ce688")); executeTest("getting DB tag with HM3", spec); } @@ -111,7 +119,7 @@ public void testDBTagWithHapMap() { @Test public void testUsingExpression() { WalkerTestSpec spec = new WalkerTestSpec( - baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G \"Standard\" --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.AF -BTI variant", 1, + baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.AF -BTI variant", 1, Arrays.asList("e9c0d832dc6b4ed06c955060f830c140")); executeTest("using expression", spec); } @@ -121,7 +129,7 @@ public void testTabixAnnotations() { final String MD5 = "13269d5a2e16f06fd755cc0fb9271acf"; for ( String file : Arrays.asList("CEU.exon.2010_03.sites.vcf", "CEU.exon.2010_03.sites.vcf.gz")) { WalkerTestSpec spec = new WalkerTestSpec( - baseTestString() + " -A HomopolymerRun --variant:VCF " + validationDataLocation + "/" + file + " -BTI variant -NO_HEADER", 1, + baseTestString() + " -A HomopolymerRun --variant:VCF " + validationDataLocation + file + " -BTI variant -NO_HEADER", 1, Arrays.asList(MD5)); executeTest("Testing lookup vcf tabix vs. vcf tribble", spec); } From 22b432c4046afac7630b81815262dab8c8b7db4d Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 13 Oct 2011 13:19:54 -0400 Subject: [PATCH 240/363] Tool to expand intervals Quick tool to expand an interval file (creted from a VCF) by a given window. From 0939d16a8de200b8f80b047b4b0cb0e67ce5af1b Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 13 Oct 2011 13:22:05 -0400 Subject: [PATCH 241/363] String not empty bug Apparently var X: String = _ is not the same as var X: String = "". :( --- .../sting/queue/qscripts/DataProcessingPipeline.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cb6bab9017..968ca9c261 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala @@ -81,7 +81,7 @@ class DataProcessingPipeline extends QScript { @Hidden @Input(doc="Define the default platform for Count Covariates -- useful for techdev purposes only.", fullName="default_platform", shortName="dp", required=false) - var defaultPlatform: String = _ + var defaultPlatform: String = "" /**************************************************************************** From e12ffb654730e3553449302b395661026c117ad9 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 13 Oct 2011 13:27:00 -0400 Subject: [PATCH 242/363] Updating docs for GCContentByInterval This walker does not take any BAMs. It only walks over the reference. --- .../gatk/walkers/coverage/GCContentByIntervalWalker.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/GCContentByIntervalWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/GCContentByIntervalWalker.java index 5c2a967b9c..17b17764b6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/GCContentByIntervalWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/coverage/GCContentByIntervalWalker.java @@ -42,12 +42,12 @@ * *

Input

*

- * One or more BAM files. + * A reference file *

* *

Output

*

- * GC content calculations per interval. + * GC content calculations per interval. *

* *

Examples

@@ -56,7 +56,6 @@ * -R ref.fasta \ * -T GCContentByInterval \ * -o output.txt \ - * -I input.bam \ * -L input.intervals * * From 6b02354d8467d8435d4077195ac0b10a4a1ef3b7 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Mon, 17 Oct 2011 14:34:52 -0400 Subject: [PATCH 243/363] Adding a new getter in VariantsToTable to extract the indel event length. --- .../gatk/walkers/variantutils/VariantsToTable.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 81d0c36acf..7b0b81d2ad 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 @@ -27,6 +27,7 @@ import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; @@ -294,6 +295,14 @@ public String get(VariantContext vc) { return x.toString(); } }); + getters.put("EVENTLENGTH", new Getter() { public String get(VariantContext vc) { + int maxLength = 0; + for ( final Allele a : vc.getAlternateAlleles() ) { + final int length = a.length() - vc.getReference().length(); + if( Math.abs(length) > Math.abs(maxLength) ) { maxLength = length; } + } + return Integer.toString(maxLength); + }}); getters.put("QUAL", new Getter() { public String get(VariantContext vc) { return Double.toString(vc.getPhredScaledQual()); } }); getters.put("TRANSITION", new Getter() { public String get(VariantContext vc) { if ( vc.isSNP() && vc.isBiallelic() ) @@ -304,7 +313,6 @@ public String get(VariantContext vc) { getters.put("FILTER", new Getter() { public String get(VariantContext vc) { return vc.isNotFiltered() ? "PASS" : Utils.join(",", vc.getFilters()); } }); - 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()); } }); From 92e1cbfb6ced2ede8a6c08ce9ca37e6b13167c3a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 17 Oct 2011 15:52:41 -0400 Subject: [PATCH 244/363] Updating docs and adding option to use a very large file for GATK vs. Tribble comparison From 1e6794c53985f02b27d0f7418c0d1784b28ad754 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Mon, 17 Oct 2011 15:56:02 -0400 Subject: [PATCH 245/363] fixing typo in VariantsToTable docs --- .../sting/gatk/walkers/variantutils/VariantsToTable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7b0b81d2ad..bf16b2dfb8 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 @@ -134,7 +134,7 @@ public class VariantsToTable extends RodWalker { /** * By default, this tool throws a UserException when it encounters a field without a value in some record. This - * is generally useful when you mistype -F CHROM, so that you get a friendly warning about CHRMO not being + * is generally useful when you mistype -F CHROM, so that you get a friendly warning about CHROM not being * found before the tool runs through 40M 1000G records. However, in some cases you genuinely want to allow such * fields (e.g., AC not being calculated for filtered records, if included). When provided, this argument * will cause VariantsToTable to write out NA values for missing fields instead of throwing an error. From e5b793f4cecc51a47c90708cd74627c87d115ee6 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 18 Oct 2011 10:55:46 -0400 Subject: [PATCH 246/363] A VCFToPED writer for bridging VCF files to Haploview From 1a92ee3593417941a31b32e60cb34796d9bd5d95 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 18 Oct 2011 10:57:02 -0400 Subject: [PATCH 247/363] No longer adds a binding of ID -> . when the ID field is dot in the VCF -- Really we should make ID a primary key in VariantContext. Putting it into the attributes is just annoying now --- .../sting/utils/codecs/vcf/AbstractVCFCodec.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 c86b91b793..6b101ca74c 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 @@ -381,7 +381,8 @@ private Map parseInfo(String infoField, String id) { } } - attributes.put(VariantContext.ID_KEY, id); + if ( ! id.equals(VCFConstants.EMPTY_ID_FIELD) ) + attributes.put(VariantContext.ID_KEY, id); return attributes; } From f77f2eeb7d1abf58766f430974f0c7c395740f0e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 18 Oct 2011 13:04:43 -0400 Subject: [PATCH 248/363] Fix for new ID structure --- .../sting/gatk/walkers/variantutils/VariantsToTable.java | 1 + 1 file changed, 1 insertion(+) 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 bf16b2dfb8..5dbd3f5fd1 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 @@ -313,6 +313,7 @@ public String get(VariantContext vc) { 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("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()); } }); From e5fc82854609f90df8be3704e6f3cd5f482b5a91 Mon Sep 17 00:00:00 2001 From: Menachem Fromer Date: Tue, 18 Oct 2011 14:47:39 -0400 Subject: [PATCH 249/363] With Khalid's implicit approval, I have removed this line that overrides the memory limit of the VCF-gathering function, so that the inherited limit remains --- .../sting/queue/extensions/gatk/VcfGatherFunction.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/VcfGatherFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/VcfGatherFunction.scala index d700221479..70046c9138 100644 --- a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/VcfGatherFunction.scala +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/VcfGatherFunction.scala @@ -36,8 +36,6 @@ class VcfGatherFunction extends CombineVariants with GatherFunction { private lazy val originalGATK = this.originalFunction.asInstanceOf[CommandLineGATK] override def freezeFieldValues { - this.memoryLimit = Some(1) - this.jarFile = this.originalGATK.jarFile this.reference_sequence = this.originalGATK.reference_sequence this.intervals = this.originalGATK.intervals From d79b57d6f44f045c0114d46397a7ebc1376d87d7 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Wed, 19 Oct 2011 08:57:38 -0400 Subject: [PATCH 250/363] Fixing cases where the Object equals function wasn't being properly overridden in the haplotype caller classes. From df3e4e1abd41c47ac95a0197b41761255d6fa8c4 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 11:22:35 -0400 Subject: [PATCH 251/363] First working code to use SamRecordFactory to produce objects of our own design in SAMFileReader --- .../gatk/datasources/reads/SAMDataSource.java | 4 + .../sting/utils/sam/GATKSamRecordFactory.java | 74 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java 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 74d39ecb0d..96d8d7e1a3 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 @@ -43,6 +43,7 @@ import org.broadinstitute.sting.utils.baq.BAQSamIterator; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.sam.GATKSamRecordFactory; import java.io.File; import java.lang.reflect.InvocationTargetException; @@ -57,6 +58,8 @@ * Converts shards to SAM iterators over the specified region */ public class SAMDataSource { + final private static GATKSamRecordFactory factory = new GATKSamRecordFactory(); + /** Backing support for reads. */ protected final ReadProperties readProperties; @@ -756,6 +759,7 @@ private class SAMReaders implements Iterable { public SAMReaders(Collection readerIDs, SAMFileReader.ValidationStringency validationStringency) { for(SAMReaderID readerID: readerIDs) { SAMFileReader reader = new SAMFileReader(readerID.samFile); + reader.setSAMRecordFactory(factory); reader.enableFileSource(true); reader.enableIndexMemoryMapping(false); if(!enableLowMemorySharding) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java new file mode 100644 index 0000000000..7c98a68e6c --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java @@ -0,0 +1,74 @@ +/* + * 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.sam; + +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMRecord; +import net.sf.samtools.SAMRecordFactory; +import net.sf.samtools.BAMRecord; +import org.broadinstitute.sting.utils.exceptions.UserException; + +/** + * Factory interface which allows plugging in of different classes for generating instances of + * SAMRecord and BAMRecord when reading from SAM/BAM files. + * + * @author Tim Fennell + */ +public class GATKSamRecordFactory implements SAMRecordFactory { + + /** Create a new SAMRecord to be filled in */ + public SAMRecord createSAMRecord(SAMFileHeader header) { + throw new UserException.BadInput("The GATK now longer supports input SAM files"); + } + + /** Create a new BAM Record. */ + public BAMRecord createBAMRecord(final SAMFileHeader header, + final int referenceSequenceIndex, + final int alignmentStart, + final short readNameLength, + final short mappingQuality, + final int indexingBin, + final int cigarLen, + final int flags, + final int readLen, + final int mateReferenceSequenceIndex, + final int mateAlignmentStart, + final int insertSize, + final byte[] variableLengthBlock) { + return new BAMRecord(header, + referenceSequenceIndex, + alignmentStart, + readNameLength, + mappingQuality, + indexingBin, + cigarLen, + flags, + readLen, + mateReferenceSequenceIndex, + mateAlignmentStart, + insertSize, + variableLengthBlock); + } +} From 6cadaa84c977d0cecedb3871c354ecf152526fce Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 19 Oct 2011 11:48:23 -0400 Subject: [PATCH 252/363] Just use validate() from super class since it does the same thing --- .../utils/variantcontext/MutableGenotype.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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 0cd684cb6d..14419a2a0d 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/MutableGenotype.java @@ -40,19 +40,7 @@ public Genotype unmodifiableGenotype() { */ public void setAlleles(List alleles) { this.alleles = new ArrayList(alleles); - - // todo -- add validation checking here - - if ( alleles == null ) throw new IllegalArgumentException("BUG: alleles cannot be null in setAlleles"); - if ( alleles.size() == 0) throw new IllegalArgumentException("BUG: alleles cannot be of size 0 in setAlleles"); - - int nNoCalls = 0; - for ( Allele allele : alleles ) { nNoCalls += allele.isNoCall() ? 1 : 0; } - if ( nNoCalls > 0 && nNoCalls != alleles.size() ) - throw new IllegalArgumentException("BUG: alleles include some No Calls and some Calls, an illegal state " + this); - - for ( Allele allele : alleles ) - if ( allele == null ) throw new IllegalArgumentException("BUG: Cannot add a null allele to a genotype"); + validate(); } public void setPhase(boolean isPhased) { From 48c4a8cb335e9df5b336bc869a803a2f0b9e98ba Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 19 Oct 2011 11:49:16 -0400 Subject: [PATCH 253/363] Make error messages clearer (even I was confused) --- .../sting/utils/variantcontext/VariantContext.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 eac6d70b59..14a680af49 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -1357,10 +1357,10 @@ private static List makeAlleles(Collection alleles) { throw new IllegalArgumentException("Duplicate allele added to VariantContext: " + a); } - // deal with the case where the first allele isn't the reference + // 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 a single reference allele: " + alleles); + throw new IllegalArgumentException("Alleles for a VariantContext must contain at most one reference allele: " + alleles); alleleList.add(0, a); sawRef = true; } @@ -1372,7 +1372,7 @@ private static List makeAlleles(Collection alleles) { 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 a single reference allele: " + alleles); + throw new IllegalArgumentException("Alleles for a VariantContext must contain at least one reference allele: " + alleles); return alleleList; } From 5a6468c11ea1f3e72e4079f2073d66c0bff00e99 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 19 Oct 2011 11:52:05 -0400 Subject: [PATCH 254/363] Allowing ./X genotypes and adding a unit test to ensure that this case is covered from now on (especially given that we may want to revert in the future). Reverting this change is really easy and entails uncommenting a few lines of code. But for now, despite Mark's objections, this case is allowed in the VCF spec and we are wrong not to allow it. --- .../sting/utils/variantcontext/Genotype.java | 10 ++++++---- .../variantcontext/VariantContextUnitTest.java | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) 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 7ab3f81f08..5639ef30eb 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -197,14 +197,16 @@ public void validate() { if ( alleles == null ) return; if ( alleles.size() == 0) throw new IllegalArgumentException("BUG: alleles cannot be of size 0"); - int nNoCalls = 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; + // nNoCalls += allele.isNoCall() ? 1 : 0; } - if ( nNoCalls > 0 && nNoCalls != alleles.size() ) - throw new IllegalArgumentException("BUG: alleles include some No Calls and some Calls, an illegal state " + this); + + // 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() { 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 9026f09d65..a5060e54e5 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -252,6 +252,23 @@ public void testCreatingInsertionVariantContext() { Assert.assertEquals(vc.getSampleNames().size(), 0); } + @Test + 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)); + + Assert.assertTrue(vc.isSNP()); + Assert.assertEquals(vc.getNAlleles(), 2); + Assert.assertTrue(vc.hasGenotypes()); + 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(Aref), 0); + Assert.assertEquals(vc.getChromosomeCount(C), 1); + } + @Test (expectedExceptions = IllegalArgumentException.class) public void testBadConstructorArgs1() { new VariantContext("test", insLoc, insLocStart, insLocStop, Arrays.asList(delRef, ATCref)); From 7928b287fcb76d80bcc1e6889f0474f782d2fe08 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 13:15:27 -0400 Subject: [PATCH 255/363] GATKSamRecord now produced by SAMFileReaders by default -- Removed all of the unnecessary caching operations in GATKSAMRecord -- GATKSAMRecord renamed to GATKSamRecord for consistency --- .../gatk/datasources/reads/SAMDataSource.java | 4 +- .../iterators/ReadFormattingIterator.java | 26 +- ...elGenotypeLikelihoodsCalculationModel.java | 6 +- .../recalibration/CountCovariatesWalker.java | 6 +- .../recalibration/RecalDataManager.java | 8 +- .../TableRecalibrationWalker.java | 4 +- .../sting/utils/sam/GATKSAMRecord.java | 368 +----------------- .../sting/utils/sam/GATKSamRecordFactory.java | 26 +- .../sting/utils/sam/ReadUtils.java | 18 +- 9 files changed, 81 insertions(+), 385 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 96d8d7e1a3..8452aadfd9 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 @@ -647,7 +647,9 @@ protected StingSAMIterator applyDecoratingIterators(ReadMetrics readMetrics, BAQ.QualityMode qmode, IndexedFastaSequenceFile refReader, byte defaultBaseQualities) { - wrappedIterator = new ReadFormattingIterator(wrappedIterator, useOriginalBaseQualities, defaultBaseQualities); + if ( useOriginalBaseQualities || defaultBaseQualities >= 0 ) + // only wrap if we are replacing the original qualitiies or using a default base quality + wrappedIterator = new ReadFormattingIterator(wrappedIterator, useOriginalBaseQualities, defaultBaseQualities); // NOTE: this (and other filtering) should be done before on-the-fly sorting // as there is no reason to sort something that we will end of throwing away diff --git a/public/java/src/org/broadinstitute/sting/gatk/iterators/ReadFormattingIterator.java b/public/java/src/org/broadinstitute/sting/gatk/iterators/ReadFormattingIterator.java index 2f30d12a80..9a89d2086f 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/iterators/ReadFormattingIterator.java +++ b/public/java/src/org/broadinstitute/sting/gatk/iterators/ReadFormattingIterator.java @@ -2,7 +2,6 @@ import net.sf.samtools.SAMRecord; import org.apache.log4j.Logger; -import org.broadinstitute.sting.utils.sam.GATKSAMRecord; /** * An iterator which does post-processing of a read, including potentially wrapping @@ -78,7 +77,30 @@ public boolean hasNext() { * no next exists. */ public SAMRecord next() { - return new GATKSAMRecord(wrappedIterator.next(), useOriginalBaseQualities, defaultBaseQualities); + SAMRecord rec = wrappedIterator.next(); + + // if we are using default quals, check if we need them, and add if necessary. + // 1. we need if reads are lacking or have incomplete quality scores + // 2. we add if defaultBaseQualities has a positive value + if (defaultBaseQualities >= 0) { + byte reads [] = rec.getReadBases(); + byte quals [] = rec.getBaseQualities(); + if (quals == null || quals.length < reads.length) { + byte new_quals [] = new byte [reads.length]; + for (int i=0; i computeConsensusAlleles(ReferenceContext ref, for ( ExtendedEventPileupElement p : indelPileup.toExtendedIterable() ) { //SAMRecord read = p.getRead(); - GATKSAMRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); + GATKSamRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); if (read == null) continue; if(ReadUtils.is454Read(read)) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java index 1bdb70bdda..424d4cdd61 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java @@ -41,7 +41,7 @@ import org.broadinstitute.sting.utils.exceptions.DynamicClassResolutionException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.pileup.PileupElement; -import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.GATKSamRecord; import java.io.PrintStream; import java.util.ArrayList; @@ -352,7 +352,7 @@ public CountedData map( RefMetaDataTracker tracker, ReferenceContext ref, Alignm if( tracker.getValues(knownSites).size() == 0 ) { // If something here is in one of the knownSites tracks then skip over it, otherwise proceed // For each read at this locus for( final PileupElement p : context.getBasePileup() ) { - final GATKSAMRecord gatkRead = (GATKSAMRecord) p.getRead(); + final GATKSamRecord gatkRead = (GATKSamRecord) p.getRead(); int offset = p.getOffset(); if( gatkRead.containsTemporaryAttribute( SKIP_RECORD_ATTRIBUTE ) ) { @@ -445,7 +445,7 @@ private static void updateMismatchCounts(CountedData counter, final AlignmentCon * @param offset The offset in the read for this locus * @param refBase The reference base at this locus */ - private void updateDataFromRead(CountedData counter, final GATKSAMRecord gatkRead, final int offset, final byte refBase) { + private void updateDataFromRead(CountedData counter, final GATKSamRecord gatkRead, final int offset, final byte refBase) { final Object[][] covars = (Comparable[][]) gatkRead.getTemporaryAttribute(COVARS_ATTRIBUTE); final Object[] key = covars[offset]; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java index 2daa8c0254..bd949aa811 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java @@ -35,7 +35,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.sam.AlignmentUtils; -import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.GATKSamRecord; import java.util.ArrayList; import java.util.List; @@ -243,7 +243,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu // There is no readGroup so defaulting to these values readGroup = new SAMReadGroupRecord( RAC.DEFAULT_READ_GROUP ); readGroup.setPlatform( RAC.DEFAULT_PLATFORM ); - ((GATKSAMRecord)read).setReadGroup( readGroup ); + ((GATKSamRecord)read).setReadGroup( readGroup ); } else { throw new UserException.MalformedBAM(read, "The input .bam file contains reads with no read group. First observed at read with name = " + read.getReadName() ); } @@ -253,7 +253,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu final String oldPlatform = readGroup.getPlatform(); readGroup = new SAMReadGroupRecord( RAC.FORCE_READ_GROUP ); readGroup.setPlatform( oldPlatform ); - ((GATKSAMRecord)read).setReadGroup( readGroup ); + ((GATKSamRecord)read).setReadGroup( readGroup ); } if( RAC.FORCE_PLATFORM != null && (readGroup.getPlatform() == null || !readGroup.getPlatform().equals(RAC.FORCE_PLATFORM))) { @@ -572,7 +572,7 @@ public static boolean isInconsistentColorSpace( final SAMRecord read, final int * value for the ith position in the read and the jth covariate in * reqeustedCovariates list. */ - public static Comparable[][] computeCovariates(final GATKSAMRecord gatkRead, final List requestedCovariates) { + public static Comparable[][] computeCovariates(final GATKSamRecord gatkRead, final List requestedCovariates) { //compute all covariates for this read final List requestedCovariatesRef = requestedCovariates; final int numRequestedCovariates = requestedCovariatesRef.size(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java index e04f5bc4b4..6f8e309fe6 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java @@ -39,7 +39,7 @@ import org.broadinstitute.sting.utils.collections.NestedHashMap; import org.broadinstitute.sting.utils.exceptions.DynamicClassResolutionException; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.GATKSamRecord; import org.broadinstitute.sting.utils.text.TextFormattingUtils; import org.broadinstitute.sting.utils.text.XReadLines; @@ -398,7 +398,7 @@ public SAMRecord map( ReferenceContext refBases, SAMRecord read, ReadMetaDataTra //compute all covariate values for this read final Comparable[][] covariateValues_offset_x_covar = - RecalDataManager.computeCovariates((GATKSAMRecord) read, requestedCovariates); + RecalDataManager.computeCovariates((GATKSamRecord) read, requestedCovariates); // For each base in the read for( int offset = 0; offset < read.getReadLength(); offset++ ) { 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 c55a462f15..56e6e74be1 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -1,12 +1,8 @@ package org.broadinstitute.sting.utils.sam; import net.sf.samtools.*; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import org.broadinstitute.sting.utils.exceptions.UserException; -import java.lang.reflect.Method; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -26,70 +22,35 @@ * done. Hopefully SAMRecord will become an interface and * this will eventually be fixed. */ -public class GATKSAMRecord extends SAMRecord { - - // the underlying SAMRecord which we are wrapping - private final SAMRecord mRecord; - +public class GATKSamRecord extends BAMRecord { // the SAMRecord data we're caching private String mReadString = null; private SAMReadGroupRecord mReadGroup = null; - private boolean mNegativeStrandFlag; - private boolean mUnmappedFlag; - private Boolean mSecondOfPairFlag = null; // because some values can be null, we don't want to duplicate effort private boolean retrievedReadGroup = false; - /** A private cache for the reduced read quality. Null indicates the value hasn't be fetched yet or isn't available */ - private boolean lookedUpReducedReadQuality = false; - private Integer reducedReadQuality; - // These temporary attributes were added here to make life easier for // certain algorithms by providing a way to label or attach arbitrary data to // individual GATKSAMRecords. // These attributes exist in memory only, and are never written to disk. private Map temporaryAttributes; - public GATKSAMRecord(SAMRecord record, boolean useOriginalBaseQualities, byte defaultBaseQualities) { - super(null); // it doesn't matter - this isn't used - if ( record == null ) - throw new IllegalArgumentException("The SAMRecord argument cannot be null"); - mRecord = record; - - mNegativeStrandFlag = mRecord.getReadNegativeStrandFlag(); - mUnmappedFlag = mRecord.getReadUnmappedFlag(); - - // because attribute methods are declared to be final (and we can't overload them), - // we need to actually set all of the attributes here - List attributes = record.getAttributes(); - for ( SAMTagAndValue attribute : attributes ) - setAttribute(attribute.tag, attribute.value); - - // if we are using default quals, check if we need them, and add if necessary. - // 1. we need if reads are lacking or have incomplete quality scores - // 2. we add if defaultBaseQualities has a positive value - if (defaultBaseQualities >= 0) { - byte reads [] = record.getReadBases(); - byte quals [] = record.getBaseQualities(); - if (quals == null || quals.length < reads.length) { - byte new_quals [] = new byte [reads.length]; - for (int i=0; i getAttributes() { return mRecord.getAttributes(); } - - public SAMFileHeader getHeader() { return mRecord.getHeader(); } - - public void setHeader(SAMFileHeader samFileHeader) { mRecord.setHeader(samFileHeader); } - - public byte[] getVariableBinaryRepresentation() { return mRecord.getVariableBinaryRepresentation(); } - - public int getAttributesBinarySize() { return mRecord.getAttributesBinarySize(); } - - public String format() { return mRecord.format(); } - - public List getAlignmentBlocks() { return mRecord.getAlignmentBlocks(); } - - public List validateCigar(long l) { return mRecord.validateCigar(l); } - @Override public boolean equals(Object o) { if (this == o) return true; // note -- this forbids a GATKSAMRecord being equal to its underlying SAMRecord - if (!(o instanceof GATKSAMRecord)) return false; + if (!(o instanceof GATKSamRecord)) return false; // note that we do not consider the GATKSAMRecord internal state at all - return mRecord.equals(((GATKSAMRecord)o).mRecord); - } - - public int hashCode() { return mRecord.hashCode(); } - - public List isValid() { return mRecord.isValid(); } - - public Object clone() throws CloneNotSupportedException { return mRecord.clone(); } - - public String toString() { return mRecord.toString(); } - - public SAMFileSource getFileSource() { return mRecord.getFileSource(); } - - /** - * Sets a marker providing the source reader for this file and the position in the file from which the read originated. - * @param fileSource source of the given file. - */ - @Override - protected void setFileSource(final SAMFileSource fileSource) { - try { - Method method = SAMRecord.class.getDeclaredMethod("setFileSource",SAMFileSource.class); - method.setAccessible(true); - method.invoke(mRecord,fileSource); - } - catch(Exception ex) { - throw new ReviewedStingException("Unable to invoke setFileSource method",ex); - } + return super.equals(o); } } diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java index 7c98a68e6c..9076f06ae9 100644 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java @@ -57,18 +57,18 @@ public BAMRecord createBAMRecord(final SAMFileHeader header, final int mateAlignmentStart, final int insertSize, final byte[] variableLengthBlock) { - return new BAMRecord(header, - referenceSequenceIndex, - alignmentStart, - readNameLength, - mappingQuality, - indexingBin, - cigarLen, - flags, - readLen, - mateReferenceSequenceIndex, - mateAlignmentStart, - insertSize, - variableLengthBlock); + return new GATKSamRecord(header, + referenceSequenceIndex, + alignmentStart, + readNameLength, + mappingQuality, + indexingBin, + cigarLen, + flags, + readLen, + mateReferenceSequenceIndex, + mateAlignmentStart, + insertSize, + variableLengthBlock); } } 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 d1e9a236f5..f91d772448 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -274,10 +274,10 @@ private static Pair getAdaptorBoundaries(SAMRecord rec, int ad * @param adaptorLength length of adaptor sequence * @return a new read with adaptor sequence hard-clipped out or null if read is fully clipped */ - public static GATKSAMRecord hardClipAdaptorSequence(final SAMRecord rec, int adaptorLength) { + public static GATKSamRecord hardClipAdaptorSequence(final SAMRecord rec, int adaptorLength) { Pair adaptorBoundaries = getAdaptorBoundaries(rec, adaptorLength); - GATKSAMRecord result = (GATKSAMRecord)rec; + GATKSamRecord result = (GATKSamRecord)rec; if ( adaptorBoundaries != null ) { if ( rec.getReadNegativeStrandFlag() && adaptorBoundaries.second >= rec.getAlignmentStart() && adaptorBoundaries.first < rec.getAlignmentEnd() ) @@ -290,7 +290,7 @@ else if ( !rec.getReadNegativeStrandFlag() && adaptorBoundaries.first <= rec.get } // return true if the read needs to be completely clipped - private static GATKSAMRecord hardClipStartOfRead(SAMRecord oldRec, int stopPosition) { + private static GATKSamRecord hardClipStartOfRead(SAMRecord 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 @@ -298,9 +298,9 @@ private static GATKSAMRecord hardClipStartOfRead(SAMRecord oldRec, int stopPosit return null; } - GATKSAMRecord rec; + GATKSamRecord rec; try { - rec = (GATKSAMRecord)oldRec.clone(); + rec = (GATKSamRecord)oldRec.clone(); } catch (Exception e) { return null; } @@ -370,7 +370,7 @@ private static GATKSAMRecord hardClipStartOfRead(SAMRecord oldRec, int stopPosit return rec; } - private static GATKSAMRecord hardClipEndOfRead(SAMRecord oldRec, int startPosition) { + private static GATKSamRecord hardClipEndOfRead(SAMRecord 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 @@ -378,9 +378,9 @@ private static GATKSAMRecord hardClipEndOfRead(SAMRecord oldRec, int startPositi return null; } - GATKSAMRecord rec; + GATKSamRecord rec; try { - rec = (GATKSAMRecord)oldRec.clone(); + rec = (GATKSamRecord)oldRec.clone(); } catch (Exception e) { return null; } @@ -598,7 +598,7 @@ public static SAMRecord replaceSoftClipsWithMatches(SAMRecord read) { * @param rec 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 SAMRecord rec) { + public static GATKSamRecord hardClipAdaptorSequence(final SAMRecord rec) { return hardClipAdaptorSequence(rec, DEFAULT_ADAPTOR_SIZE); } From d8d73fe4f2ffef079edd495066ae499de14a919a Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 19 Oct 2011 15:11:13 -0400 Subject: [PATCH 256/363] Treat ./X genotypes as MIXED so that isHet, isHom, etc. still return the expected and correct values. Added docs to these accessors with contracts explicitly mentioned. Fixed case where NPE could be thrown. --- .../sting/utils/variantcontext/Genotype.java | 61 +++++++++++++++---- .../utils/variantcontext/VariantContext.java | 11 +++- .../VariantContextUnitTest.java | 6 ++ 3 files changed, 65 insertions(+), 13 deletions(-) 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 5639ef30eb..e2e44e2b9b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/Genotype.java @@ -108,14 +108,19 @@ public Allele getAllele(int i) { /** * @return the ploidy of this genotype */ - public int getPloidy() { return alleles.size(); } + 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 + UNAVAILABLE, + MIXED // no-call and call in the same genotype } public Type getType() { @@ -129,36 +134,68 @@ protected Type determineType() { if ( alleles == null ) return Type.UNAVAILABLE; - Allele firstAllele = alleles.get(0); + boolean sawNoCall = false, sawMultipleAlleles = false; + Allele observedAllele = null; - if ( firstAllele.isNoCall() ) { - return Type.NO_CALL; + for ( Allele allele : alleles ) { + if ( allele.isNoCall() ) + sawNoCall = true; + else if ( observedAllele == null ) + observedAllele = allele; + else if ( !allele.equals(observedAllele) ) + sawMultipleAlleles = true; } - for (Allele a : alleles) { - if ( ! firstAllele.equals(a) ) - return Type.HET; + if ( sawNoCall ) { + if ( observedAllele == null ) + return Type.NO_CALL; + return Type.MIXED; } - return firstAllele.isReference() ? Type.HOM_REF : Type.HOM_VAR; + + 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) + * @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) + * @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) + * @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; } // 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 14a680af49..f52a7087b0 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContext.java @@ -998,7 +998,7 @@ else if ( g.isHet() ) else if ( g.isHomVar() ) genotypeCounts[Genotype.Type.HOM_VAR.ordinal()]++; else - throw new IllegalStateException("Genotype of unknown type: " + g); + genotypeCounts[Genotype.Type.MIXED.ordinal()]++; } } } @@ -1042,6 +1042,15 @@ 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 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 a5060e54e5..a4d78b6377 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/VariantContextUnitTest.java @@ -267,6 +267,12 @@ public void testCreatingPartiallyCalledGenotype() { Assert.assertEquals(vc.getChromosomeCount(), 2); // we know that there are 2 chromosomes, even though one isn't called Assert.assertEquals(vc.getChromosomeCount(Aref), 0); Assert.assertEquals(vc.getChromosomeCount(C), 1); + Assert.assertFalse(vc.getGenotype("foo").isHet()); + Assert.assertFalse(vc.getGenotype("foo").isHom()); + Assert.assertFalse(vc.getGenotype("foo").isNoCall()); + Assert.assertFalse(vc.getGenotype("foo").isHom()); + Assert.assertTrue(vc.getGenotype("foo").isMixed()); + Assert.assertEquals(vc.getGenotype("foo").getType(), Genotype.Type.MIXED); } @Test (expectedExceptions = IllegalArgumentException.class) From 1b38aa1a7e67deeb4f426b16cf7eaa8d6016e938 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 15:46:44 -0400 Subject: [PATCH 257/363] Cleaning up reduced read code accessors --- .../sting/utils/sam/ReadUtils.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 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 f91d772448..9fa8fc544f 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -55,6 +55,7 @@ private ReadUtils() { } public static final String REDUCED_READ_CONSENSUS_COUNTS_TAG = "CC"; public final static byte[] getReducedReadQualityTagValue(final SAMRecord read) { + // TODO -- warning of performance problem. Should be cached in GATKSamRecord return read.getByteArrayAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); } @@ -70,21 +71,6 @@ public final static byte getReducedCount(final SAMRecord read, final int i) { return getReducedReadQualityTagValue(read)[i]; } - public final static SAMRecord reducedReadWithReducedQuals(final SAMRecord read) { - if ( ! isReducedRead(read) ) throw new IllegalArgumentException("read must be a reduced read"); - return read; -// try { -// SAMRecord newRead = (SAMRecord)read.clone(); -// byte reducedQual = (byte)(int)getReducedReadQualityTagValue(read); -// byte[] newQuals = new byte[read.getBaseQualities().length]; -// Arrays.fill(newQuals, reducedQual); -// newRead.setBaseQualities(newQuals); -// return newRead; -// } catch ( CloneNotSupportedException e ) { -// throw new ReviewedStingException("SAMRecord no longer supports clone", e); -// } - } - // ---------------------------------------------------------------------------------------------------- // // General utilities From 52345f0aec828aca04af8e84259d68ad21ca69c9 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 15:47:36 -0400 Subject: [PATCH 258/363] Meaningful documentation string --- .../sting/utils/sam/GATKSamRecordFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java index 9076f06ae9..adde2e293f 100644 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java @@ -31,10 +31,10 @@ import org.broadinstitute.sting.utils.exceptions.UserException; /** - * Factory interface which allows plugging in of different classes for generating instances of - * SAMRecord and BAMRecord when reading from SAM/BAM files. + * Factory interface implementation used to create GATKSamRecords + * from SAMFileReaders with SAM-JDK * - * @author Tim Fennell + * @author Mark DePristo */ public class GATKSamRecordFactory implements SAMRecordFactory { From cd8a6d62bba320d276455d93b73290a61d6a203c Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Wed, 19 Oct 2011 17:42:37 -0400 Subject: [PATCH 259/363] You know how the wiki has a big section on commiting local changes to BRANCHES of the repository you clone it from? Yeah. It sucks if you don't do that. This commit contains: - IntronLossGenotyper is brought into its current incarnation - A couple of simple new filters (ReadName is super useful for debugging, MateUnmapped is useful for selecting out reads that may have a relevant unaligned mate) - RFA now matches my current local repository. It's in flux since I'm transitioning to the new traversal type. + the triggering read stash pilot required me to change the scope of some of the variables in the ReadClipping code, private -> protected. Those are all the changes there. - MendelianViolation restored to its former glory (and an annotator module that uses the likelihood calculation has been added) + use this rather than a hard GQ threshold if you're doing MV analyses. - Some miscellaneous QScripts --- .../sting/gatk/filters/ReadNameFilter.java | 23 ++++++++ .../walkers/annotator/MVLikelihoodRatio.java | 58 +++++++++++++++++++ .../sting/utils/MendelianViolation.java | 42 ++++++++++++++ 3 files changed, 123 insertions(+) create mode 100755 public/java/src/org/broadinstitute/sting/gatk/filters/ReadNameFilter.java create mode 100755 public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MVLikelihoodRatio.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/filters/ReadNameFilter.java b/public/java/src/org/broadinstitute/sting/gatk/filters/ReadNameFilter.java new file mode 100755 index 0000000000..a56af56d1b --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/filters/ReadNameFilter.java @@ -0,0 +1,23 @@ +package org.broadinstitute.sting.gatk.filters; + +import net.sf.samtools.Cigar; +import net.sf.samtools.CigarElement; +import net.sf.samtools.CigarOperator; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.commandline.Argument; + +/** + * Created by IntelliJ IDEA. + * User: chartl + * Date: 9/19/11 + * Time: 4:09 PM + * To change this template use File | Settings | File Templates. + */ +public class ReadNameFilter extends ReadFilter { + @Argument(fullName = "readName", shortName = "rn", doc="Filter out all reads except those with this read name", required=true) + private String readName; + + public boolean filterOut(final SAMRecord rec) { + return ! rec.getReadName().equals(readName); + } +} 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 new file mode 100755 index 0000000000..e0a2329f80 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/MVLikelihoodRatio.java @@ -0,0 +1,58 @@ +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.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; + +/** + * Created by IntelliJ IDEA. + * User: chartl + * Date: 9/14/11 + * Time: 12:24 PM + * To change this template use File | Settings | File Templates. + */ +public class MVLikelihoodRatio extends InfoFieldAnnotation implements ExperimentalAnnotation { + + private MendelianViolation mendelianViolation = null; + + public Map annotate(RefMetaDataTracker tracker, AnnotatorCompatibleWalker walker, ReferenceContext ref, Map stratifiedContexts, VariantContext vc) { + if ( mendelianViolation == null ) { + if ( walker instanceof VariantAnnotator ) { + mendelianViolation = new MendelianViolation(((VariantAnnotator)walker).familyStr, ((VariantAnnotator)walker).minGenotypeQualityP ); + } + else { + throw new UserException("Mendelian violation annotation can only be used from the Variant Annotator"); + } + } + + Map toRet = new HashMap(1); + boolean hasAppropriateGenotypes = vc.hasGenotype(mendelianViolation.getSampleChild()) && + vc.hasGenotype(mendelianViolation.getSampleDad()) && + vc.hasGenotype(mendelianViolation.getSampleMom()); + if ( hasAppropriateGenotypes ) + toRet.put("MVLR",mendelianViolation.violationLikelihoodRatio(vc)); + + return toRet; + } + + // return the descriptions used for the VCF INFO meta field + 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]")); } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java index e62a7e512f..cf45dab79c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java +++ b/public/java/src/org/broadinstitute/sting/utils/MendelianViolation.java @@ -26,6 +26,9 @@ public class MendelianViolation { double minGenotypeQuality; + 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() { @@ -134,4 +137,43 @@ public boolean isViolation() { return false; return true; } + + /** + * @return the likelihood ratio for a mendelian violation + */ + public double violationLikelihoodRatio(VariantContext vc) { + double[] logLikAssignments = new double[27]; + // the matrix to set up is + // MOM DAD CHILD + // |- AA + // AA AA | AB + // |- BB + // |- AA + // 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(); + int offset = 0; + for ( int oMom = 0; oMom < 3; oMom++ ) { + for ( int oDad = 0; oDad < 3; oDad++ ) { + for ( int oChild = 0; oChild < 3; oChild ++ ) { + logLikAssignments[offset++] = momGL[oMom] + dadGL[oDad] + childGL[oChild]; + } + } + } + double[] mvLiks = new double[12]; + double[] nonMVLiks = new double[15]; + for ( int i = 0; i < 12; i ++ ) { + mvLiks[i] = logLikAssignments[mvOffsets[i]]; + } + + for ( int i = 0; i < 15; i++) { + nonMVLiks[i] = logLikAssignments[nonMVOffsets[i]]; + } + + return MathUtils.log10sumLog10(mvLiks) - MathUtils.log10sumLog10(nonMVLiks); + } + } From bba69701b505dec882ff8eee978be0451ec5b335 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 17:49:17 -0400 Subject: [PATCH 260/363] Now creates GATKSamRecords now SamRecords --- .../org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2dcdd5ce6c..b6b6713606 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java @@ -146,7 +146,7 @@ public static SAMRecord createArtificialRead( SAMFileHeader header, String name, if( (refIndex == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart != SAMRecord.NO_ALIGNMENT_START) || (refIndex != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart == SAMRecord.NO_ALIGNMENT_START) ) throw new ReviewedStingException("Invalid alignment start for artificial read, start = " + alignmentStart); - SAMRecord record = new SAMRecord(header); + SAMRecord record = new GATKSamRecord(header); record.setReadName(name); record.setReferenceIndex(refIndex); record.setAlignmentStart(alignmentStart); From 3227143a1c930db8a243f1be264840a7e76ea7fc Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 17:50:27 -0400 Subject: [PATCH 261/363] Systematic test code for FragmentPileup -- Creates all combinatinos of overlapping and non-overlapping read pair pileups in all orientations and first/second pairings to validate fragment detection. --- .../utils/pileup/FragmentPileupUnitTest.java | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java new file mode 100644 index 0000000000..7782595280 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.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.utils.pileup; + +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMReadGroupRecord; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.collections.Pair; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.testng.Assert; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.*; + +/** + * Test routines for read-backed pileup. + */ +public class FragmentPileupUnitTest extends BaseTest { + private static SAMFileHeader header; + + private class FragmentPileupTest extends TestDataProvider { + List states = new ArrayList(); + + private FragmentPileupTest(String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { + super(FragmentPileupTest.class, String.format("%s-leftIsFirst:%b-leftIsNegative:%b", name, leftIsFirst, leftIsNegative)); + + for ( int pos = leftStart; pos < rightStart + readLen; pos++) { + SAMRecord left = ArtificialSAMUtils.createArtificialRead(header, "readpair", 0, leftStart, readLen); + SAMRecord right = ArtificialSAMUtils.createArtificialRead(header, "readpair", 0, rightStart, readLen); + + left.setProperPairFlag(true); + right.setProperPairFlag(true); + + left.setFirstOfPairFlag(leftIsFirst); + right.setFirstOfPairFlag(! leftIsFirst); + + left.setReadNegativeStrandFlag(leftIsNegative); + left.setMateNegativeStrandFlag(!leftIsNegative); + right.setReadNegativeStrandFlag(!leftIsNegative); + right.setMateNegativeStrandFlag(leftIsNegative); + + left.setMateAlignmentStart(right.getAlignmentStart()); + right.setMateAlignmentStart(left.getAlignmentStart()); + + left.setMateReferenceIndex(0); + right.setMateReferenceIndex(0); + + int isize = rightStart + readLen - leftStart; + left.setInferredInsertSize(isize); + right.setInferredInsertSize(-isize); + + boolean posCoveredByLeft = pos >= left.getAlignmentStart() && pos <= left.getAlignmentEnd(); + boolean posCoveredByRight = pos >= right.getAlignmentStart() && pos <= right.getAlignmentEnd(); + + if ( posCoveredByLeft || posCoveredByRight ) { + List reads = new ArrayList(); + List offsets = new ArrayList(); + + if ( posCoveredByLeft ) { + reads.add(left); + offsets.add(pos - left.getAlignmentStart()); + } + + if ( posCoveredByRight ) { + reads.add(right); + offsets.add(pos - right.getAlignmentStart()); + } + + boolean shouldBeFragment = posCoveredByLeft && posCoveredByRight; + ReadBackedPileup pileup = new ReadBackedPileupImpl(null, reads, offsets); + TestState testState = new TestState(shouldBeFragment, pileup); + states.add(testState); + } + } + } + } + + private class TestState { + boolean shouldBeFragment; + ReadBackedPileup pileup; + + private TestState(final boolean shouldBeFragment, final ReadBackedPileup pileup) { + this.shouldBeFragment = shouldBeFragment; + this.pileup = pileup; + } + } + + @DataProvider(name = "fragmentPileupTest") + public Object[][] createTests() { + for ( boolean leftIsFirst : Arrays.asList(true, false) ) { + for ( boolean leftIsNegative : Arrays.asList(true, false) ) { + // Overlapping pair + // ----> [first] + // <--- [second] + new FragmentPileupTest("overlapping-pair", 10, 1, 5, leftIsFirst, leftIsNegative); + + // Non-overlapping pair + // ----> + // <---- + new FragmentPileupTest("nonoverlapping-pair", 10, 1, 15, leftIsFirst, leftIsNegative); + } + } + + return FragmentPileupTest.getTests(FragmentPileupTest.class); + } + + @Test(enabled = true, dataProvider = "fragmentPileupTest") + public void testMe(FragmentPileupTest test) { + for ( TestState testState : test.states ) { + ReadBackedPileup rbp = testState.pileup; + FragmentPileup fp = new FragmentPileup(rbp); + Assert.assertEquals(fp.getTwoReadPileup().size(), testState.shouldBeFragment ? 1 : 0); + Assert.assertEquals(fp.getOneReadPileup().size(), testState.shouldBeFragment ? 0 : 1); + } + } + + + @BeforeTest + public void setup() { + header = ArtificialSAMUtils.createArtificialSamHeader(1,1,1000); + } +} From 999a8998ae4d007847f9c2fdd99b4ef3217cc9fe Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Wed, 19 Oct 2011 17:51:48 -0400 Subject: [PATCH 262/363] Constructor for GATKSamRecord with header only, for unit testing --- .../sting/utils/sam/GATKSAMRecord.java | 13 +++++++++++++ 1 file changed, 13 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 56e6e74be1..7956ac3889 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -36,6 +36,16 @@ public class GATKSamRecord extends BAMRecord { // These attributes exist in memory only, and are never written to disk. private Map temporaryAttributes; + /** + * HACK TO CREATE GATKSAMRECORD WITH ONLY A HEADER FOR TESTING PURPOSES ONLY + * @param header + */ + public GATKSamRecord(final SAMFileHeader header) { + super(header, SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX, SAMRecord.NO_ALIGNMENT_START, + (short)0, (short)255, 0, 1, 0, 1, 0, 0, 0, null); + } + + public GATKSamRecord(final SAMFileHeader header, final int referenceSequenceIndex, final int alignmentStart, @@ -57,17 +67,20 @@ public GATKSamRecord(final SAMFileHeader header, // *** The following methods are overloaded to cache the appropriate data ***// /////////////////////////////////////////////////////////////////////////////// + @Override public String getReadString() { if ( mReadString == null ) mReadString = super.getReadString(); return mReadString; } + @Override public void setReadString(String s) { super.setReadString(s); mReadString = s; } + @Override public SAMReadGroupRecord getReadGroup() { if ( !retrievedReadGroup ) { SAMReadGroupRecord tempReadGroup = super.getReadGroup(); From 6f72b3de6a39582bc022d3914d202b683d568957 Mon Sep 17 00:00:00 2001 From: Christopher Hartl Date: Wed, 19 Oct 2011 18:46:31 -0400 Subject: [PATCH 263/363] Forgot to add this in too (oops) From ed402588cc37c0bc1b979916f4e623a7671065f4 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 20 Oct 2011 16:19:13 -0400 Subject: [PATCH 264/363] Adding the "gold standard NA12878" target --- .../qscripts/MethodsDevelopmentCallingPipeline.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 cc4790f051..88c1bd2c88 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala @@ -98,13 +98,17 @@ class MethodsDevelopmentCallingPipeline extends QScript { // BUGBUG: We no longer support b36/hg18 because several of the necessary files aren't available aligned to those references val targetDataSets: Map[String, Target] = Map( + "NA12878_gold" -> new Target("NA12878.goldStandard", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, + new File("/humgen/gsa-hpprojects/dev/carneiro/NA12878/data/goldStandard.list"), + new File("/humgen/gsa-hpprojects/dev/carneiro/NA12878/analysis/snps/NA12878.HiSeq19.filtered.vcf"), // ** There is no gold standard for the gold standard ** + "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.noChrY.hg19.intervals", 2.14, 99.0, lowPass, !exome, 391), "NA12878_wgs_b37" -> new Target("NA12878.HiSeq.WGS.b37", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.HiSeq.WGS.bwa.cleaned.recal.hg19.bam"), - new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.HiSeq19.filtered.vcf"), + new File("/humgen/gsa-hpprojects/dev/carneiro/NA12878/analysis/snps/NA12878.HiSeq19.filtered.vcf"), "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.noChrY.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), "NA12878_wgs_decoy" -> new Target("NA12878.HiSeq.WGS.b37_decoy", b37_decoy, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/CEUTrio.HiSeq.WGS.b37_decoy.NA12878.clean.dedup.recal.bam"), - new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.HiSeq19.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** + new File("/humgen/gsa-hpprojects/dev/carneiro/NA12878/analysis/snps/NA12878.HiSeq19.filtered.vcf"), // ** THIS GOLD STANDARD NEEDS TO BE CORRECTED ** "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.noChrY.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), "NA12878_wgs_hg18" -> new Target("NA12878.HiSeq.WGS.hg18", hg18, dbSNP_hg18_129, hapmap_hg18, "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/1000GenomesProcessingPaper/wgs.v13/HiSeq.WGS.cleaned.indels.10.mask", @@ -142,7 +146,7 @@ class MethodsDevelopmentCallingPipeline extends QScript { "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg19.intervals", 2.3, 99.0, !lowPass, !exome, 3), "GA2hg19" -> new Target("NA12878.GA2.hg19", hg19, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/gsa-hpprojects/NA12878Collection/bams/NA12878.GA2.WGS.bwa.cleaned.hg19.bam"), - new File("/humgen/gsa-hpprojects/dev/carneiro/hiseq19/analysis/snps/NA12878.GA2.hg19.filtered.vcf"), + new File("/humgen/gsa-hpprojects/dev/carneiro/NA12878/analysis/snps/NA12878.GA2.hg19.filtered.vcf"), "/humgen/1kg/processing/pipeline_test_bams/whole_genome_chunked.hg19.intervals", 2.14, 99.0, !lowPass, !exome, 1), "FIN" -> new Target("FIN", b37, dbSNP_b37, hapmap_b37, indelMask_b37, new File("/humgen/1kg/processing/pipeline_test_bams/FIN.79sample.Nov2010.chr20.bam"), From c9d8b22092b04e1ec6d05d1a3f98a109de94851b Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 20 Oct 2011 18:36:28 -0400 Subject: [PATCH 265/363] Added BWASW support to the pipeline Data Processing Pipeline can now use BWASW for realigning the reads. Useful for Ion Torrent data. --- .../qscripts/DataProcessingPipeline.scala | 40 ++++++++-- .../extensions/picard/PicardBamFunction.scala | 2 +- .../queue/extensions/picard/SamToFastq.scala | 76 +++++++++++++++++++ 3 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 public/scala/src/org/broadinstitute/sting/queue/extensions/picard/SamToFastq.scala 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 7262de8633..5e5fd23658 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/DataProcessingPipeline.scala @@ -65,6 +65,9 @@ class DataProcessingPipeline extends QScript { @Input(doc="Decompose input BAM file and fully realign it using BWA and assume Pair Ended reads", fullName="use_bwa_pair_ended", shortName="bwape", required=false) var useBWApe: Boolean = false + @Input(doc="Decompose input BAM file and fully realign it using BWA SW", fullName="use_bwa_sw", shortName="bwasw", required=false) + var useBWAsw: Boolean = false + @Input(doc="Number of threads BWA should use", fullName="bwa_threads", shortName="bt", required=false) var bwaThreads: Int = 1 @@ -162,22 +165,28 @@ class DataProcessingPipeline extends QScript { var index = 1 for (bam <- bams) { // first revert the BAM file to the original qualities - val revertedBAM = revertBAM(bam, true) - val readSortedBam = swapExt(revertedBAM, ".bam", "." + index + ".sorted.bam" ) val saiFile1 = swapExt(bam, ".bam", "." + index + ".1.sai") val saiFile2 = swapExt(bam, ".bam", "." + index + ".2.sai") val realignedSamFile = swapExt(bam, ".bam", "." + index + ".realigned.sam") val realignedBamFile = swapExt(bam, ".bam", "." + index + ".realigned.bam") val rgRealignedBamFile = swapExt(bam, ".bam", "." + index + ".realigned.rg.bam") + if (useBWAse) { + val revertedBAM = revertBAM(bam, true) add(bwa_aln_se(revertedBAM, saiFile1), bwa_sam_se(revertedBAM, saiFile1, realignedSamFile)) } - else { - add(sortSam(revertedBAM, readSortedBam, SortOrder.queryname), - bwa_aln_pe(readSortedBam, saiFile1, 1), - bwa_aln_pe(readSortedBam, saiFile2, 2), - bwa_sam_pe(readSortedBam, saiFile1, saiFile2, realignedSamFile)) + else if (useBWApe) { + val revertedBAM = revertBAM(bam, true) + add(bwa_aln_pe(revertedBAM, saiFile1, 1), + bwa_aln_pe(revertedBAM, saiFile2, 2), + bwa_sam_pe(revertedBAM, saiFile1, saiFile2, realignedSamFile)) + } + else if (useBWAsw) { + val revertedBAM = revertBAM(bam, false) + val fastQ = swapExt(revertedBAM, ".bam", ".fq") + add(convertToFastQ(revertedBAM, fastQ), + bwa_sw(fastQ, realignedSamFile)) } add(sortSam(realignedSamFile, realignedBamFile, SortOrder.coordinate)) addReadGroups(realignedBamFile, rgRealignedBamFile, new SAMFileReader(bam)) @@ -226,7 +235,7 @@ class DataProcessingPipeline extends QScript { if (nContigs < 0) nContigs = QScriptUtils.getNumberOfContigs(bams(0)) - val realignedBAMs = if (useBWApe || useBWAse) {performAlignment(bams)} else {revertBams(bams, false)} + val realignedBAMs = if (useBWApe || useBWAse || useBWAsw) {performAlignment(bams)} else {revertBams(bams, false)} // generate a BAM file per sample joining all per lane files if necessary val sampleBAMFiles: Map[String, List[File]] = createSampleFiles(bams, realignedBAMs) @@ -427,9 +436,16 @@ class DataProcessingPipeline extends QScript { this.output = outBam this.input :+= inBam this.removeAlignmentInformation = removeAlignmentInfo; + this.sortOrder = if (removeAlignmentInfo) {SortOrder.queryname} else {SortOrder.coordinate} this.analysisName = queueLogDir + outBam + "revert" this.jobName = queueLogDir + outBam + ".revert" + } + case class convertToFastQ (inBam: File, outFQ: File) extends SamToFastq with ExternalCommonArgs { + this.input :+= inBam + this.fastq = outFQ + this.analysisName = queueLogDir + outFQ + "convert_to_fastq" + this.jobName = queueLogDir + outFQ + ".convert_to_fastq" } case class bwa_aln_se (inBam: File, outSai: File) extends CommandLineFunction with ExternalCommonArgs { @@ -469,6 +485,14 @@ class DataProcessingPipeline extends QScript { this.jobName = queueLogDir + outBam + ".bwa_sam_pe" } + case class bwa_sw (inFastQ: File, outBam: File) extends CommandLineFunction with ExternalCommonArgs { + @Input(doc="fastq file to be aligned") var fq = inFastQ + @Output(doc="output bam file") var bam = outBam + def commandLine = bwaPath + " bwasw -t " + bwaThreads + " " + reference + " " + fq + " > " + bam + this.analysisName = queueLogDir + outBam + ".bwasw" + this.jobName = queueLogDir + outBam + ".bwasw" + } + case class writeList(inBams: List[File], outBamList: File) extends ListWriterFunction { this.inputFiles = inBams this.listFile = outBamList 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 2654e4a3d2..427c09f827 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 @@ -50,8 +50,8 @@ trait PicardBamFunction extends JavaCommandLineFunction { abstract override def commandLine = super.commandLine + Array( repeat(" INPUT=", inputBams), - " OUTPUT=" + outputBam, " TMP_DIR=" + jobTempDir, + optional(" OUTPUT=", outputBam), optional(" COMPRESSION_LEVEL=", compressionLevel), optional(" VALIDATION_STRINGENCY=", validationStringency), optional(" SO=", sortOrder), 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 new file mode 100644 index 0000000000..2f9f84c63d --- /dev/null +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/picard/SamToFastq.scala @@ -0,0 +1,76 @@ +package org.broadinstitute.sting.queue.extensions.picard + +import org.broadinstitute.sting.commandline._ + +import java.io.File + +/* + * Created by IntelliJ IDEA. + * User: carneiro + * Date: 6/22/11 + * Time: 10:35 AM + */ +class SamToFastq extends org.broadinstitute.sting.queue.function.JavaCommandLineFunction with PicardBamFunction { + analysisName = "SamToFastq" + javaMainClass = "net.sf.picard.sam.SamToFastq" + + @Input(shortName = "input", fullName = "input_bam_files", required = true, doc = "Input SAM/BAM file to extract reads from.") + var input: List[File] = Nil + + @Output(shortName = "fastq", fullName = "output_fastq_file", required = true, doc = "Output fastq file (single-end fastq or, if paired, first end of the pair fastq).") + var fastq: File = _ + + @Output(shortName = "se", fullName = "second_end_fastq", required = false, doc = "Output fastq file (if paired, second end of the pair fastq).") + var secondEndFastQ: File = _ + + @Argument(shortName = "opg", fullName = "output_per_readgroup", required = false, doc = "Output a fastq file per read group (two fastq files per read group if the group is paired).") + var outputPerReadGroup: Boolean = false + + @Argument(shortName = "od", fullName = "output_dir", required = false, doc = "Directory in which to output the fastq file(s). Used only when OUTPUT_PER_RG is true.") + var outputDir: File = _ + + @Argument(shortName = "rr", fullName = "re_reverse", required = false, doc = "Re-reverse bases and qualities of reads with negative strand flag set before writing them to fastq.") + var reReverse: Boolean = true + + @Argument(shortName = "nonpf", fullName = "include_non_pf_reads", required = false, doc = "Include non-PF reads from the SAM file into the output FASTQ files.") + var includeNonPFReads: Boolean = false + + @Argument(shortName = "cat", fullName = "clipping_attribute", required = false, doc = "The attribute that stores the position at which the SAM record should be clipped.") + var clippingAttribute: String = null + + @Argument(shortName = "cac", fullName = "clipping_action", required = false, doc = "The action that should be taken with clipped reads: 'X' means the reads and qualities should be trimmed at the clipped position; 'N' means the bases should be changed to Ns in the clipped region; and any integer means that the base qualities should be set to that value in the clipped region.") + var clippingAction: String = null + + @Argument(shortName = "r1t", fullName = "read_one_trim", required = false, doc = "The number of bases to trim from the beginning of read 1.") + var readOneTrim: Int = -1 + + @Argument(shortName = "r1mbtw", fullName = "read_one_max_bases_to_write", required = false, doc = "The maximum number of bases to write from read 1 after trimming. If there are fewer than this many bases left after trimming, all will be written. If this value is null then all bases left after trimming will be written.") + var readOneMaxBasesToWrite: Int = -1 + + @Argument(shortName = "r2t", fullName = "read_two_trim", required = false, doc = "The number of bases to trim from the beginning of read 2.") + var readTwoTrim: Int = -1 + + @Argument(shortName = "r2mbtw", fullName = "read_two_max_bases_to_write", required = false, doc = "The maximum number of bases to write from read 2 after trimming. If there are fewer than this many bases left after trimming, all will be written. If this value is null then all bases left after trimming will be written.") + var readTwoMaxBasesToWrite: Int = -1 + + @Argument(shortName = "inpa", fullName = "include_non_primary_alignments", required = false, doc = "If true, include non-primary alignments in the output. Support of non-primary alignments in SamToFastq is not comprehensive, so there may be exceptions if this is set to true and there are paired reads with non-primary alignments.") + var includeNonPrimaryAlignments: Boolean = false + + override def inputBams = input + override def outputBam = 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)) +} \ No newline at end of file From 9f867d77ca746f3063f8c626826bf899dc632e85 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Thu, 20 Oct 2011 18:44:09 -0400 Subject: [PATCH 266/363] no sort order subtle bug fixed. --- .../sting/queue/extensions/picard/SamToFastq.scala | 1 + 1 file changed, 1 insertion(+) 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 2f9f84c63d..3a4217e605 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 @@ -58,6 +58,7 @@ class SamToFastq extends org.broadinstitute.sting.queue.function.JavaCommandLine override def inputBams = input override def outputBam = null + this.sortOrder = null override def commandLine = super.commandLine + " FASTQ=" + fastq + From 94e1898d8f48185593d5717395888ce08abe13f7 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 21 Oct 2011 09:37:45 -0400 Subject: [PATCH 267/363] A canonical set of NGS platforms as enums with convenient manipulation methods --- .../sting/utils/NGSPlatform.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java diff --git a/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java b/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java new file mode 100644 index 0000000000..3ab9e16552 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java @@ -0,0 +1,108 @@ +/* + * 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; + +import net.sf.samtools.SAMReadGroupRecord; +import net.sf.samtools.SAMRecord; + +/** + * A canonical, master list of the standard NGS platforms. These values + * can be obtained (efficiently) from a GATKSamRecord object with the + * getNGSPlatform method. + * + * @author Mark DePristo + * @since 2011 + */ +public enum NGSPlatform { + ILLUMINA("ILLUMINA", "SLX", "SOLEXA"), + SOLID("SOLID"), + LS454("454"), + COMPLETE_GENOMICS("COMPLETE"), + PACBIO("PACBIO"), + ION_TORRENT("IONTORRENT"), + UNKNOWN("UNKNOWN"); + + /** + * Array of the prefix names in a BAM file for each of the platforms. + */ + private final String[] BAM_PL_NAMES; + + NGSPlatform(final String... BAM_PL_NAMES) { + for ( int i = 0; i < BAM_PL_NAMES.length; i++ ) + BAM_PL_NAMES[i] = BAM_PL_NAMES[i].toUpperCase(); + this.BAM_PL_NAMES = BAM_PL_NAMES; + } + + /** + * Returns a representative PL string for this platform + * @return + */ + public final String getDefaultPlatform() { + return BAM_PL_NAMES[0]; + } + + /** + * Convenience constructor -- calculates the NGSPlatfrom from a SAMRecord. + * Note you should not use this function if you have a GATKSamRecord -- use the + * accessor method instead. + * + * @param read + * @return an NGSPlatform object matching the PL field of the header, of UNKNOWN if there was no match + */ + public static final NGSPlatform fromRead(SAMRecord read) { + return fromReadGroup(read.getReadGroup()); + } + + /** + * Returns the NGSPlatform corresponding to the PL tag in the read group + * @param rg + * @return an NGSPlatform object matching the PL field of the header, of UNKNOWN if there was no match + */ + public static final NGSPlatform fromReadGroup(SAMReadGroupRecord rg) { + return fromReadGroupPL(rg.getPlatform()); + } + + /** + * Returns the NGSPlatform corresponding to the PL tag in the read group + * @param plFromRG -- the PL field (or equivalent) in a ReadGroup object + * @return an NGSPlatform object matching the PL field of the header, of UNKNOWN if there was no match + */ + public static final NGSPlatform fromReadGroupPL(final String plFromRG) { + if ( plFromRG == null ) return UNKNOWN; + + // todo -- algorithm could be implemented more efficiently, as the list of all + // todo -- names is known upfront, so a decision tree could be used to identify + // todo -- a prefix common to PL + final String pl = plFromRG.toUpperCase(); + for ( final NGSPlatform ngsPlatform : NGSPlatform.values() ) { + for ( final String bamPLName : ngsPlatform.BAM_PL_NAMES ) { + if ( pl.contains(bamPLName) ) + return ngsPlatform; + } + } + + return UNKNOWN; + } +} From ed74ebcfa11ad90c85694dd6666c5d96af807393 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 21 Oct 2011 09:38:41 -0400 Subject: [PATCH 268/363] GATKSamRecords with efficiency NGSPlatform method --- .../utils/sam/GATKSAMReadGroupRecord.java | 23 +++++++++++++++++++ .../sting/utils/sam/GATKSAMRecord.java | 16 +++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMReadGroupRecord.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMReadGroupRecord.java index c7ffcab0c0..ff7d12f098 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMReadGroupRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMReadGroupRecord.java @@ -1,6 +1,7 @@ package org.broadinstitute.sting.utils.sam; import net.sf.samtools.SAMReadGroupRecord; +import org.broadinstitute.sting.utils.NGSPlatform; /** * @author ebanks @@ -15,16 +16,28 @@ public class GATKSAMReadGroupRecord extends SAMReadGroupRecord { // the SAMReadGroupRecord data we're caching private String mSample = null; private String mPlatform = null; + private NGSPlatform mNGSPlatform = null; // because some values can be null, we don't want to duplicate effort private boolean retrievedSample = false; private boolean retrievedPlatform = false; + private boolean retrievedNGSPlatform = false; + public GATKSAMReadGroupRecord(final String id) { + super(id); + } public GATKSAMReadGroupRecord(SAMReadGroupRecord record) { super(record.getReadGroupId(), record); } + public GATKSAMReadGroupRecord(SAMReadGroupRecord record, NGSPlatform pl) { + super(record.getReadGroupId(), record); + setPlatform(pl.getDefaultPlatform()); + mNGSPlatform = pl; + retrievedPlatform = retrievedNGSPlatform = true; + } + /////////////////////////////////////////////////////////////////////////////// // *** The following methods are overloaded to cache the appropriate data ***// /////////////////////////////////////////////////////////////////////////////// @@ -55,5 +68,15 @@ public void setPlatform(String s) { super.setPlatform(s); mPlatform = s; retrievedPlatform = true; + retrievedNGSPlatform = false; // recalculate the NGSPlatform + } + + public NGSPlatform getNGSPlatform() { + if ( ! retrievedNGSPlatform ) { + mNGSPlatform = NGSPlatform.fromReadGroupPL(getPlatform()); + retrievedNGSPlatform = true; + } + + return mNGSPlatform; } } \ No newline at end of file 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 7956ac3889..037545a78e 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -1,6 +1,7 @@ package org.broadinstitute.sting.utils.sam; import net.sf.samtools.*; +import org.broadinstitute.sting.utils.NGSPlatform; import java.util.HashMap; import java.util.Map; @@ -25,7 +26,7 @@ public class GATKSamRecord extends BAMRecord { // the SAMRecord data we're caching private String mReadString = null; - private SAMReadGroupRecord mReadGroup = null; + private GATKSAMReadGroupRecord mReadGroup = null; // because some values can be null, we don't want to duplicate effort private boolean retrievedReadGroup = false; @@ -81,17 +82,22 @@ public void setReadString(String s) { } @Override - public SAMReadGroupRecord getReadGroup() { + public GATKSAMReadGroupRecord getReadGroup() { if ( !retrievedReadGroup ) { SAMReadGroupRecord tempReadGroup = super.getReadGroup(); - mReadGroup = (tempReadGroup == null ? tempReadGroup : new GATKSAMReadGroupRecord(tempReadGroup)); + mReadGroup = (tempReadGroup == null ? null : new GATKSAMReadGroupRecord(tempReadGroup)); retrievedReadGroup = true; } return mReadGroup; } - public void setReadGroup(SAMReadGroupRecord record) { - mReadGroup = record; + public NGSPlatform getNGSPlatform() { + return getReadGroup().getNGSPlatform(); + } + + public void setReadGroup( final GATKSAMReadGroupRecord readGroup ) { + mReadGroup = readGroup; + retrievedReadGroup = true; } /** From be797a8a1f4eb72fe5843864decc45d2e7d852e8 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 21 Oct 2011 09:39:21 -0400 Subject: [PATCH 269/363] Recalibrator now uses the much more efficient NGSPlatform in the cycle covariates system --- ivy.xml | 2 +- .../walkers/recalibration/CycleCovariate.java | 130 ++---------------- .../recalibration/RecalDataManager.java | 8 +- 3 files changed, 14 insertions(+), 126 deletions(-) diff --git a/ivy.xml b/ivy.xml index 96c1de844d..0737b676db 100644 --- a/ivy.xml +++ b/ivy.xml @@ -10,7 +10,7 @@ - + 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 e117454f92..857f741556 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 @@ -2,9 +2,12 @@ import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.utils.BaseUtils; +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; /* @@ -46,6 +49,9 @@ */ public class CycleCovariate implements StandardCovariate { + private final static EnumSet DISCRETE_CYCLE_PLATFORMS = EnumSet.of(NGSPlatform.ILLUMINA, NGSPlatform.SOLID, NGSPlatform.PACBIO, NGSPlatform.COMPLETE_GENOMICS); + private final static EnumSet FLOW_CYCLE_PLATFORMS = EnumSet.of(NGSPlatform.LS454, NGSPlatform.ION_TORRENT); + // Initialize any member variables using the command-line arguments passed to the walkers public void initialize( final RecalibrationArgumentCollection RAC ) { if( RAC.DEFAULT_PLATFORM != null ) { @@ -58,122 +64,6 @@ public void initialize( final RecalibrationArgumentCollection RAC ) { } } - /* - // Used to pick out the covariate's value from attributes of the read - public final Comparable getValue( final SAMRecord read, final int offset ) { - - int cycle = 1; - - //----------------------------- - // ILLUMINA and SOLID - //----------------------------- - - if( read.getReadGroup().getPlatform().equalsIgnoreCase( "ILLUMINA" ) || read.getReadGroup().getPlatform().equalsIgnoreCase( "SLX" ) || // Some bams have "illumina" and others have "SLX" - read.getReadGroup().getPlatform().equalsIgnoreCase( "SOLID" ) || read.getReadGroup().getPlatform().equalsIgnoreCase( "ABI_SOLID" )) { // Some bams have "solid" and others have "ABI_SOLID" - cycle = offset + 1; - if( read.getReadNegativeStrandFlag() ) { - cycle = read.getReadLength() - offset; - } - } - - //----------------------------- - // 454 - //----------------------------- - - else if( read.getReadGroup().getPlatform().contains( "454" ) ) { // Some bams have "LS454" and others have just "454" - final byte[] bases = read.getReadBases(); - - // BUGBUG: Consider looking at degradation of base quality scores in homopolymer runs to detect when the cycle incremented even though the nucleotide didn't change - // For example, AAAAAAA was probably read in two flow cycles but here we count it as one - if( !read.getReadNegativeStrandFlag() ) { // Forward direction - int iii = 0; - while( iii <= offset ) - { - while( iii <= offset && bases[iii] == (byte)'T' ) { iii++; } - while( iii <= offset && bases[iii] == (byte)'A' ) { iii++; } - while( iii <= offset && bases[iii] == (byte)'C' ) { iii++; } - while( iii <= offset && bases[iii] == (byte)'G' ) { iii++; } - if( iii <= offset ) { cycle++; } - if( iii <= offset && !BaseUtils.isRegularBase(bases[iii]) ) { iii++; } - - } - } else { // Negative direction - int iii = bases.length-1; - while( iii >= offset ) - { - while( iii >= offset && bases[iii] == (byte)'T' ) { iii--; } - while( iii >= offset && bases[iii] == (byte)'A' ) { iii--; } - while( iii >= offset && bases[iii] == (byte)'C' ) { iii--; } - while( iii >= offset && bases[iii] == (byte)'G' ) { iii--; } - if( iii >= offset ) { cycle++; } - if( iii >= offset && !BaseUtils.isRegularBase(bases[iii]) ) { iii--; } - } - } - } - - //----------------------------- - // SOLID (unused), only to be used in conjunction with PrimerRoundCovariate - //----------------------------- - - //else if( read.getReadGroup().getPlatform().equalsIgnoreCase( "SOLID" ) ) { - // // The ligation cycle according to http://www3.appliedbiosystems.com/cms/groups/mcb_marketing/documents/generaldocuments/cms_057511.pdf - // int pos = offset + 1; - // if( read.getReadNegativeStrandFlag() ) { - // pos = read.getReadLength() - offset; - // } - // cycle = pos / 5; // integer division - //} - - //----------------------------- - // UNRECOGNIZED PLATFORM - //----------------------------- - - else { // Platform is unrecognized so revert to the default platform but warn the user first - if( defaultPlatform != null) { // The user set a default platform - if( !warnedUserBadPlatform ) { - Utils.warnUser( "Platform string (" + read.getReadGroup().getPlatform() + ") unrecognized in CycleCovariate. " + - "Defaulting to platform = " + defaultPlatform + "." ); - } - warnedUserBadPlatform = true; - - read.getReadGroup().setPlatform( defaultPlatform ); - return getValue( read, offset ); // A recursive call - } else { // The user did not set a default platform - throw new StingException( "Platform string (" + read.getReadGroup().getPlatform() + ") unrecognized in CycleCovariate. " + - "No default platform specified. Users must set the default platform using the --default_platform argument." ); - } - } - - // Differentiate between first and second of pair. - // The sequencing machine cycle keeps incrementing for the second read in a pair. So it is possible for a read group - // to have an error affecting quality at a particular cycle on the first of pair which carries over to the second of pair. - // Therefore the cycle covariate must differentiate between first and second of pair reads. - // This effect can not be corrected by pulling out the first of pair and second of pair flags into a separate covariate because - // the current sequential model would consider the effects independently instead of jointly. - if( read.getReadPairedFlag() && read.getSecondOfPairFlag() ) { - cycle *= -1; - } - - return cycle; - } - */ - - // todo -- this should be put into a common place in the code base - private static List ILLUMINA_NAMES = Arrays.asList("ILLUMINA", "SLX", "SOLEXA"); - private static List SOLID_NAMES = Arrays.asList("SOLID"); - private static List LS454_NAMES = Arrays.asList("454"); - private static List COMPLETE_GENOMICS_NAMES = Arrays.asList("COMPLETE"); - private static List PACBIO_NAMES = Arrays.asList("PACBIO"); - private static List ION_TORRENT_NAMES = Arrays.asList("IONTORRENT"); - - private static boolean isPlatform(SAMRecord read, List names) { - String pl = read.getReadGroup().getPlatform().toUpperCase(); - for ( String name : names ) - if ( pl.contains( name ) ) - return true; - return false; - } - // Used to pick out the covariate's value from attributes of the read public void getValues(SAMRecord read, Comparable[] comparable) { @@ -181,7 +71,8 @@ public void getValues(SAMRecord read, Comparable[] comparable) { // Illumina, Solid, PacBio, and Complete Genomics //----------------------------- - if( isPlatform(read, ILLUMINA_NAMES) || isPlatform(read, SOLID_NAMES) || isPlatform(read, PACBIO_NAMES) || isPlatform(read, COMPLETE_GENOMICS_NAMES) ) { + final NGSPlatform ngsPlatform = ((GATKSamRecord)read).getNGSPlatform(); + if( DISCRETE_CYCLE_PLATFORMS.contains(ngsPlatform) ) { final int init; final int increment; if( !read.getReadNegativeStrandFlag() ) { @@ -227,8 +118,7 @@ public void getValues(SAMRecord read, Comparable[] comparable) { //----------------------------- // 454 and Ion Torrent //----------------------------- - - else if ( isPlatform(read, LS454_NAMES) || isPlatform(read, ION_TORRENT_NAMES)) { // Some bams have "LS454" and others have just "454" + else if( FLOW_CYCLE_PLATFORMS.contains(ngsPlatform) ) { final int readLength = read.getReadLength(); final byte[] bases = read.getReadBases(); @@ -273,8 +163,6 @@ else if ( isPlatform(read, LS454_NAMES) || isPlatform(read, ION_TORRENT_NAMES)) else { throw new IllegalStateException("This method hasn't been implemented yet for " + read.getReadGroup().getPlatform()); } - - } // Used to get the covariate's value from input csv file in TableRecalibrationWalker diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java index bd949aa811..a8a829801c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java @@ -35,6 +35,7 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.sam.AlignmentUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMReadGroupRecord; import org.broadinstitute.sting.utils.sam.GATKSamRecord; import java.util.ArrayList; @@ -228,8 +229,7 @@ public final NestedHashMap getCollapsedTable( final int covariate ) { * @param RAC The list of shared command line arguments */ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgumentCollection RAC ) { - - SAMReadGroupRecord readGroup = read.getReadGroup(); + GATKSAMReadGroupRecord readGroup = ((GATKSamRecord)read).getReadGroup(); // If there are no read groups we have to default to something, and that something could be specified by the user using command line arguments if( readGroup == null ) { @@ -241,7 +241,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu warnUserNullReadGroup = true; } // There is no readGroup so defaulting to these values - readGroup = new SAMReadGroupRecord( RAC.DEFAULT_READ_GROUP ); + readGroup = new GATKSAMReadGroupRecord( RAC.DEFAULT_READ_GROUP ); readGroup.setPlatform( RAC.DEFAULT_PLATFORM ); ((GATKSamRecord)read).setReadGroup( readGroup ); } else { @@ -251,7 +251,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu if( RAC.FORCE_READ_GROUP != null && !readGroup.getReadGroupId().equals(RAC.FORCE_READ_GROUP) ) { // Collapse all the read groups into a single common String provided by the user final String oldPlatform = readGroup.getPlatform(); - readGroup = new SAMReadGroupRecord( RAC.FORCE_READ_GROUP ); + readGroup = new GATKSAMReadGroupRecord( RAC.FORCE_READ_GROUP ); readGroup.setPlatform( oldPlatform ); ((GATKSamRecord)read).setReadGroup( readGroup ); } From 2403e9606201070f6c38039d6b5d2fcff1ea5e7c Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 21 Oct 2011 09:59:24 -0400 Subject: [PATCH 270/363] Renamed GATKSamRecord -> GATKSAMRecord for consistency. Better docs. --- ...elGenotypeLikelihoodsCalculationModel.java | 4 +- .../recalibration/CountCovariatesWalker.java | 6 +-- .../walkers/recalibration/CycleCovariate.java | 4 +- .../recalibration/RecalDataManager.java | 10 ++-- .../TableRecalibrationWalker.java | 4 +- .../sting/utils/NGSPlatform.java | 4 +- .../sting/utils/sam/ArtificialSAMUtils.java | 2 +- .../sting/utils/sam/GATKSAMRecord.java | 53 ++++++++++++++----- .../sting/utils/sam/GATKSamRecordFactory.java | 2 +- .../sting/utils/sam/ReadUtils.java | 20 +++---- 10 files changed, 67 insertions(+), 42 deletions(-) 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 1eafcfb10a..aea63b61da 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 @@ -39,7 +39,7 @@ import org.broadinstitute.sting.utils.pileup.PileupElement; 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.GATKSAMRecord; import org.broadinstitute.sting.utils.sam.ReadUtils; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.VariantContext; @@ -125,7 +125,7 @@ private ArrayList computeConsensusAlleles(ReferenceContext ref, for ( ExtendedEventPileupElement p : indelPileup.toExtendedIterable() ) { //SAMRecord read = p.getRead(); - GATKSamRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); + GATKSAMRecord read = ReadUtils.hardClipAdaptorSequence(p.getRead()); if (read == null) continue; if(ReadUtils.is454Read(read)) { diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java index 424d4cdd61..1bdb70bdda 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CountCovariatesWalker.java @@ -41,7 +41,7 @@ import org.broadinstitute.sting.utils.exceptions.DynamicClassResolutionException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.pileup.PileupElement; -import org.broadinstitute.sting.utils.sam.GATKSamRecord; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import java.io.PrintStream; import java.util.ArrayList; @@ -352,7 +352,7 @@ public CountedData map( RefMetaDataTracker tracker, ReferenceContext ref, Alignm if( tracker.getValues(knownSites).size() == 0 ) { // If something here is in one of the knownSites tracks then skip over it, otherwise proceed // For each read at this locus for( final PileupElement p : context.getBasePileup() ) { - final GATKSamRecord gatkRead = (GATKSamRecord) p.getRead(); + final GATKSAMRecord gatkRead = (GATKSAMRecord) p.getRead(); int offset = p.getOffset(); if( gatkRead.containsTemporaryAttribute( SKIP_RECORD_ATTRIBUTE ) ) { @@ -445,7 +445,7 @@ private static void updateMismatchCounts(CountedData counter, final AlignmentCon * @param offset The offset in the read for this locus * @param refBase The reference base at this locus */ - private void updateDataFromRead(CountedData counter, final GATKSamRecord gatkRead, final int offset, final byte refBase) { + private void updateDataFromRead(CountedData counter, final GATKSAMRecord gatkRead, final int offset, final byte refBase) { final Object[][] covars = (Comparable[][]) gatkRead.getTemporaryAttribute(COVARS_ATTRIBUTE); final Object[] key = covars[offset]; 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 857f741556..e10334a777 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 @@ -4,7 +4,7 @@ import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.NGSPlatform; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.sam.GATKSamRecord; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import java.util.Arrays; import java.util.EnumSet; @@ -71,7 +71,7 @@ public void getValues(SAMRecord read, Comparable[] comparable) { // Illumina, Solid, PacBio, and Complete Genomics //----------------------------- - final NGSPlatform ngsPlatform = ((GATKSamRecord)read).getNGSPlatform(); + final NGSPlatform ngsPlatform = ((GATKSAMRecord)read).getNGSPlatform(); if( DISCRETE_CYCLE_PLATFORMS.contains(ngsPlatform) ) { final int init; final int increment; diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java index a8a829801c..a0c928afa0 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java @@ -36,7 +36,7 @@ import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.sam.GATKSAMReadGroupRecord; -import org.broadinstitute.sting.utils.sam.GATKSamRecord; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import java.util.ArrayList; import java.util.List; @@ -229,7 +229,7 @@ public final NestedHashMap getCollapsedTable( final int covariate ) { * @param RAC The list of shared command line arguments */ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgumentCollection RAC ) { - GATKSAMReadGroupRecord readGroup = ((GATKSamRecord)read).getReadGroup(); + GATKSAMReadGroupRecord readGroup = ((GATKSAMRecord)read).getReadGroup(); // If there are no read groups we have to default to something, and that something could be specified by the user using command line arguments if( readGroup == null ) { @@ -243,7 +243,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu // There is no readGroup so defaulting to these values readGroup = new GATKSAMReadGroupRecord( RAC.DEFAULT_READ_GROUP ); readGroup.setPlatform( RAC.DEFAULT_PLATFORM ); - ((GATKSamRecord)read).setReadGroup( readGroup ); + ((GATKSAMRecord)read).setReadGroup( readGroup ); } else { throw new UserException.MalformedBAM(read, "The input .bam file contains reads with no read group. First observed at read with name = " + read.getReadName() ); } @@ -253,7 +253,7 @@ public static void parseSAMRecord( final SAMRecord read, final RecalibrationArgu final String oldPlatform = readGroup.getPlatform(); readGroup = new GATKSAMReadGroupRecord( RAC.FORCE_READ_GROUP ); readGroup.setPlatform( oldPlatform ); - ((GATKSamRecord)read).setReadGroup( readGroup ); + ((GATKSAMRecord)read).setReadGroup( readGroup ); } if( RAC.FORCE_PLATFORM != null && (readGroup.getPlatform() == null || !readGroup.getPlatform().equals(RAC.FORCE_PLATFORM))) { @@ -572,7 +572,7 @@ public static boolean isInconsistentColorSpace( final SAMRecord read, final int * value for the ith position in the read and the jth covariate in * reqeustedCovariates list. */ - public static Comparable[][] computeCovariates(final GATKSamRecord gatkRead, final List requestedCovariates) { + public static Comparable[][] computeCovariates(final GATKSAMRecord gatkRead, final List requestedCovariates) { //compute all covariates for this read final List requestedCovariatesRef = requestedCovariates; final int numRequestedCovariates = requestedCovariatesRef.size(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java index 6f8e309fe6..e04f5bc4b4 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java @@ -39,7 +39,7 @@ import org.broadinstitute.sting.utils.collections.NestedHashMap; import org.broadinstitute.sting.utils.exceptions.DynamicClassResolutionException; import org.broadinstitute.sting.utils.exceptions.UserException; -import org.broadinstitute.sting.utils.sam.GATKSamRecord; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.text.TextFormattingUtils; import org.broadinstitute.sting.utils.text.XReadLines; @@ -398,7 +398,7 @@ public SAMRecord map( ReferenceContext refBases, SAMRecord read, ReadMetaDataTra //compute all covariate values for this read final Comparable[][] covariateValues_offset_x_covar = - RecalDataManager.computeCovariates((GATKSamRecord) read, requestedCovariates); + RecalDataManager.computeCovariates((GATKSAMRecord) read, requestedCovariates); // For each base in the read for( int offset = 0; offset < read.getReadLength(); offset++ ) { diff --git a/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java b/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java index 3ab9e16552..4f01f2b7aa 100644 --- a/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java +++ b/public/java/src/org/broadinstitute/sting/utils/NGSPlatform.java @@ -29,7 +29,7 @@ /** * A canonical, master list of the standard NGS platforms. These values - * can be obtained (efficiently) from a GATKSamRecord object with the + * can be obtained (efficiently) from a GATKSAMRecord object with the * getNGSPlatform method. * * @author Mark DePristo @@ -65,7 +65,7 @@ public final String getDefaultPlatform() { /** * Convenience constructor -- calculates the NGSPlatfrom from a SAMRecord. - * Note you should not use this function if you have a GATKSamRecord -- use the + * Note you should not use this function if you have a GATKSAMRecord -- use the * accessor method instead. * * @param read 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 b6b6713606..85011f2e04 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java @@ -146,7 +146,7 @@ public static SAMRecord createArtificialRead( SAMFileHeader header, String name, if( (refIndex == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart != SAMRecord.NO_ALIGNMENT_START) || (refIndex != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart == SAMRecord.NO_ALIGNMENT_START) ) throw new ReviewedStingException("Invalid alignment start for artificial read, start = " + alignmentStart); - SAMRecord record = new GATKSamRecord(header); + SAMRecord record = new GATKSAMRecord(header); record.setReadName(name); record.setReferenceIndex(refIndex); record.setAlignmentStart(alignmentStart); 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 037545a78e..c363f20721 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -1,3 +1,27 @@ +/* + * 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.sam; import net.sf.samtools.*; @@ -7,23 +31,17 @@ import java.util.Map; /** - * @author ebanks + * @author ebanks, depristo * GATKSAMRecord * - * this class extends the samtools SAMRecord class and caches important + * this class extends the samtools BAMRecord class (and SAMRecord) and caches important * (and oft-accessed) data that's not already cached by the SAMRecord class * * IMPORTANT NOTE: Because ReadGroups are not set through the SAMRecord, * if they are ever modified externally then one must also invoke the * setReadGroup() method here to ensure that the cache is kept up-to-date. - * - * 13 Oct 2010 - mhanna - this class is fundamentally flawed: it uses a decorator - * pattern to wrap a heavyweight object, which can lead - * to heinous side effects if the wrapping is not carefully - * done. Hopefully SAMRecord will become an interface and - * this will eventually be fixed. */ -public class GATKSamRecord extends BAMRecord { +public class GATKSAMRecord extends BAMRecord { // the SAMRecord data we're caching private String mReadString = null; private GATKSAMReadGroupRecord mReadGroup = null; @@ -41,13 +59,12 @@ public class GATKSamRecord extends BAMRecord { * HACK TO CREATE GATKSAMRECORD WITH ONLY A HEADER FOR TESTING PURPOSES ONLY * @param header */ - public GATKSamRecord(final SAMFileHeader header) { + public GATKSAMRecord(final SAMFileHeader header) { super(header, SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX, SAMRecord.NO_ALIGNMENT_START, (short)0, (short)255, 0, 1, 0, 1, 0, 0, 0, null); } - - public GATKSamRecord(final SAMFileHeader header, + public GATKSAMRecord(final SAMFileHeader header, final int referenceSequenceIndex, final int alignmentStart, final short readNameLength, @@ -91,6 +108,10 @@ public GATKSAMReadGroupRecord getReadGroup() { return mReadGroup; } + /** + * Efficient caching accessor that returns the GATK NGSPlatform of this read + * @return + */ public NGSPlatform getNGSPlatform() { return getReadGroup().getNGSPlatform(); } @@ -153,12 +174,16 @@ public Object getTemporaryAttribute(Object key) { return null; } + @Override + public int hashCode() { + return super.hashCode(); + } + @Override public boolean equals(Object o) { if (this == o) return true; - // note -- this forbids a GATKSAMRecord being equal to its underlying SAMRecord - if (!(o instanceof GATKSamRecord)) return false; + if (!(o instanceof GATKSAMRecord)) return false; // note that we do not consider the GATKSAMRecord internal state at all return super.equals(o); diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java index adde2e293f..d96c874eaf 100644 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSamRecordFactory.java @@ -57,7 +57,7 @@ public BAMRecord createBAMRecord(final SAMFileHeader header, final int mateAlignmentStart, final int insertSize, final byte[] variableLengthBlock) { - return new GATKSamRecord(header, + return new GATKSAMRecord(header, referenceSequenceIndex, alignmentStart, readNameLength, 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 9fa8fc544f..16abbe4985 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -55,7 +55,7 @@ private ReadUtils() { } public static final String REDUCED_READ_CONSENSUS_COUNTS_TAG = "CC"; public final static byte[] getReducedReadQualityTagValue(final SAMRecord read) { - // TODO -- warning of performance problem. Should be cached in GATKSamRecord + // TODO -- warning of performance problem. Should be cached in GATKSAMRecord return read.getByteArrayAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); } @@ -260,10 +260,10 @@ private static Pair getAdaptorBoundaries(SAMRecord rec, int ad * @param adaptorLength length of adaptor sequence * @return a new read with adaptor sequence hard-clipped out or null if read is fully clipped */ - public static GATKSamRecord hardClipAdaptorSequence(final SAMRecord rec, int adaptorLength) { + public static GATKSAMRecord hardClipAdaptorSequence(final SAMRecord rec, int adaptorLength) { Pair adaptorBoundaries = getAdaptorBoundaries(rec, adaptorLength); - GATKSamRecord result = (GATKSamRecord)rec; + GATKSAMRecord result = (GATKSAMRecord)rec; if ( adaptorBoundaries != null ) { if ( rec.getReadNegativeStrandFlag() && adaptorBoundaries.second >= rec.getAlignmentStart() && adaptorBoundaries.first < rec.getAlignmentEnd() ) @@ -276,7 +276,7 @@ else if ( !rec.getReadNegativeStrandFlag() && adaptorBoundaries.first <= rec.get } // return true if the read needs to be completely clipped - private static GATKSamRecord hardClipStartOfRead(SAMRecord oldRec, int stopPosition) { + private static GATKSAMRecord hardClipStartOfRead(SAMRecord 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 @@ -284,9 +284,9 @@ private static GATKSamRecord hardClipStartOfRead(SAMRecord oldRec, int stopPosit return null; } - GATKSamRecord rec; + GATKSAMRecord rec; try { - rec = (GATKSamRecord)oldRec.clone(); + rec = (GATKSAMRecord)oldRec.clone(); } catch (Exception e) { return null; } @@ -356,7 +356,7 @@ private static GATKSamRecord hardClipStartOfRead(SAMRecord oldRec, int stopPosit return rec; } - private static GATKSamRecord hardClipEndOfRead(SAMRecord oldRec, int startPosition) { + private static GATKSAMRecord hardClipEndOfRead(SAMRecord 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 @@ -364,9 +364,9 @@ private static GATKSamRecord hardClipEndOfRead(SAMRecord oldRec, int startPositi return null; } - GATKSamRecord rec; + GATKSAMRecord rec; try { - rec = (GATKSamRecord)oldRec.clone(); + rec = (GATKSAMRecord)oldRec.clone(); } catch (Exception e) { return null; } @@ -584,7 +584,7 @@ public static SAMRecord replaceSoftClipsWithMatches(SAMRecord read) { * @param rec 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 SAMRecord rec) { + public static GATKSAMRecord hardClipAdaptorSequence(final SAMRecord rec) { return hardClipAdaptorSequence(rec, DEFAULT_ADAPTOR_SIZE); } From 1b01e24e239c2ae39aed3e0b7453c87028a1040f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 21 Oct 2011 13:26:30 -0400 Subject: [PATCH 271/363] Moving around comments From b863390cb1a4c89d8a77cdc6a20c76aa5f810ac8 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Fri, 21 Oct 2011 13:28:05 -0400 Subject: [PATCH 272/363] Moving reduced read functionality into GATKSAMRecord -- More functions take / produce GATKSAMRecords instead of SAMRecord --- .../indels/PairHMMIndelErrorModel.java | 2 +- .../sting/utils/pileup/PileupElement.java | 7 +-- .../sting/utils/sam/ArtificialSAMUtils.java | 11 +++-- .../sting/utils/sam/GATKSAMRecord.java | 48 ++++++++++++++++++- .../sting/utils/sam/ReadUtils.java | 18 ------- .../sting/utils/ReadUtilsUnitTest.java | 12 ++--- .../utils/ReservoirDownsamplerUnitTest.java | 5 +- 7 files changed, 67 insertions(+), 36 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 9edf5b5d44..3a824cdfe9 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 @@ -393,7 +393,7 @@ public synchronized double[] computeReadHaplotypeLikelihoods(ReadBackedPileup pi for (PileupElement p: pileup) { // > 1 when the read is a consensus read representing multiple independent observations - final boolean isReduced = ReadUtils.isReducedRead(p.getRead()); + final boolean isReduced = p.isReducedRead(); readCounts[readIdx] = isReduced ? p.getReducedCount() : 1; // check if we've already computed likelihoods for this pileup element (i.e. for this read at this location) 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 f6ed792a57..e47b6ada76 100755 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java @@ -4,6 +4,7 @@ import com.google.java.contract.Requires; import net.sf.samtools.SAMRecord; import org.broadinstitute.sting.utils.BaseUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.sam.ReadUtils; /** @@ -82,16 +83,16 @@ protected byte getQual(final int offset) { // -------------------------------------------------------------------------- public boolean isReducedRead() { - return ReadUtils.isReducedRead(getRead()); + return ((GATKSAMRecord)read).isReducedRead(); } public int getReducedCount() { if ( ! isReducedRead() ) throw new IllegalArgumentException("Cannot get reduced count for non-reduced read " + getRead().getReadName()); - return ReadUtils.getReducedCount(getRead(), offset); + return ((GATKSAMRecord)read).getReducedCount(offset); } public byte getReducedQual() { if ( ! isReducedRead() ) throw new IllegalArgumentException("Cannot get reduced qual for non-reduced read " + getRead().getReadName()); - return ReadUtils.getReducedQual(getRead(), offset); + return getQual(); } } \ No newline at end of file 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 85011f2e04..4eba323831 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java @@ -134,6 +134,7 @@ public static SAMFileHeader createEnumeratedReadGroups( SAMFileHeader header, Li /** * Create an artificial read based on the parameters. The cigar string will be *M, where * is the length of the read * + * * @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 @@ -142,11 +143,11 @@ public static SAMFileHeader createEnumeratedReadGroups( SAMFileHeader header, Li * * @return the artificial read */ - public static SAMRecord createArtificialRead( SAMFileHeader header, String name, int refIndex, int alignmentStart, int length ) { + public static GATKSAMRecord createArtificialRead(SAMFileHeader header, String name, int refIndex, int alignmentStart, int length) { if( (refIndex == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart != SAMRecord.NO_ALIGNMENT_START) || (refIndex != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart == SAMRecord.NO_ALIGNMENT_START) ) throw new ReviewedStingException("Invalid alignment start for artificial read, start = " + alignmentStart); - SAMRecord record = new GATKSAMRecord(header); + GATKSAMRecord record = new GATKSAMRecord(header); record.setReadName(name); record.setReferenceIndex(refIndex); record.setAlignmentStart(alignmentStart); @@ -166,6 +167,7 @@ public static SAMRecord createArtificialRead( SAMFileHeader header, String name, if (refIndex == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { record.setReadUnmappedFlag(true); } + return record; } @@ -181,16 +183,17 @@ public static SAMRecord createArtificialRead( SAMFileHeader header, String name, * * @return the artificial read */ - public static SAMRecord createArtificialRead( SAMFileHeader header, String name, int refIndex, int alignmentStart, byte[] bases, byte[] qual ) { + public static GATKSAMRecord createArtificialRead( SAMFileHeader header, String name, int refIndex, int alignmentStart, byte[] bases, byte[] qual ) { if (bases.length != qual.length) { throw new ReviewedStingException("Passed in read string is different length then the quality array"); } - SAMRecord rec = createArtificialRead(header, name, refIndex, alignmentStart, bases.length); + GATKSAMRecord rec = createArtificialRead(header, name, refIndex, alignmentStart, bases.length); rec.setReadBases(bases); rec.setBaseQualities(qual); if (refIndex == -1) { rec.setReadUnmappedFlag(true); } + return rec; } 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 c363f20721..d50f2ff03c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -45,9 +45,11 @@ public class GATKSAMRecord extends BAMRecord { // the SAMRecord data we're caching private String mReadString = null; private GATKSAMReadGroupRecord mReadGroup = null; + private byte[] reducedReadCounts = null; // because some values can be null, we don't want to duplicate effort private boolean retrievedReadGroup = false; + private boolean retrievedReduceReadCounts = false; // These temporary attributes were added here to make life easier for // certain algorithms by providing a way to label or attach arbitrary data to @@ -60,8 +62,27 @@ public class GATKSAMRecord extends BAMRecord { * @param header */ public GATKSAMRecord(final SAMFileHeader header) { - super(header, SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX, SAMRecord.NO_ALIGNMENT_START, - (short)0, (short)255, 0, 1, 0, 1, 0, 0, 0, null); + this(new SAMRecord(header)); + } + + /** + * HACK TO CREATE GATKSAMRECORD BASED ONLY A SAMRECORD FOR TESTING PURPOSES ONLY + * @param read + */ + public GATKSAMRecord(final SAMRecord read) { + super(read.getHeader(), read.getMateReferenceIndex(), + read.getAlignmentStart(), + read.getReadName() != null ? (short)read.getReadNameLength() : 0, + (short)read.getMappingQuality(), + 0, + read.getCigarLength(), + read.getFlags(), + read.getReadLength(), + read.getMateReferenceIndex(), + read.getMateAlignmentStart(), + read.getInferredInsertSize(), + new byte[]{}); + super.clearAttributes(); } public GATKSAMRecord(final SAMFileHeader header, @@ -121,6 +142,29 @@ public void setReadGroup( final GATKSAMReadGroupRecord readGroup ) { retrievedReadGroup = true; } + // + // + // Reduced read functions + // + // + + public byte[] getReducedReadCounts() { + if ( ! retrievedReduceReadCounts ) { + reducedReadCounts = getByteArrayAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); + retrievedReduceReadCounts = true; + } + + return reducedReadCounts; + } + + public boolean isReducedRead() { + return getReducedReadCounts() != null; + } + + public final byte getReducedCount(final int i) { + return getReducedReadCounts()[i]; + } + /** * Checks whether an attribute has been set for the given key. * 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 16abbe4985..f8e4927ed8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -52,24 +52,6 @@ private ReadUtils() { } // ---------------------------------------------------------------------------------------------------- public static final String REDUCED_READ_QUALITY_TAG = "RQ"; - public static final String REDUCED_READ_CONSENSUS_COUNTS_TAG = "CC"; - - public final static byte[] getReducedReadQualityTagValue(final SAMRecord read) { - // TODO -- warning of performance problem. Should be cached in GATKSAMRecord - return read.getByteArrayAttribute(ReadUtils.REDUCED_READ_QUALITY_TAG); - } - - public final static boolean isReducedRead(final SAMRecord read) { - return getReducedReadQualityTagValue(read) != null; - } - - public final static byte getReducedQual(final SAMRecord read, final int i) { - return read.getBaseQualities()[i]; - } - - public final static byte getReducedCount(final SAMRecord read, final int i) { - return getReducedReadQualityTagValue(read)[i]; - } // ---------------------------------------------------------------------------------------------------- // diff --git a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java index cc00074399..59a6ecb8d4 100755 --- a/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReadUtilsUnitTest.java @@ -5,6 +5,7 @@ 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; @@ -12,7 +13,7 @@ public class ReadUtilsUnitTest extends BaseTest { - SAMRecord read, reducedRead; + 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}; @@ -47,13 +48,12 @@ private void testReadBasesAndQuals(SAMRecord read, int expectedStart, int expect @Test public void testReducedReads() { - Assert.assertFalse(ReadUtils.isReducedRead(read), "isReducedRead is false for normal read"); - Assert.assertEquals(ReadUtils.getReducedReadQualityTagValue(read), null, "No reduced read tag in normal read"); + Assert.assertFalse(read.isReducedRead(), "isReducedRead is false for normal read"); + Assert.assertEquals(read.getReducedReadCounts(), null, "No reduced read tag in normal read"); - Assert.assertTrue(ReadUtils.isReducedRead(reducedRead), "isReducedRead is true for reduced read"); + Assert.assertTrue(reducedRead.isReducedRead(), "isReducedRead is true for reduced read"); for ( int i = 0; i < reducedRead.getReadLength(); i++) { - Assert.assertEquals(ReadUtils.getReducedQual(reducedRead, i), read.getBaseQualities()[i], "Reduced read quality not set to the expected value at " + i); - Assert.assertEquals(ReadUtils.getReducedCount(reducedRead, i), REDUCED_READ_COUNTS[i], "Reduced read count not set to the expected value at " + i); + Assert.assertEquals(reducedRead.getReducedCount(i), REDUCED_READ_COUNTS[i], "Reduced read count not set to the expected value at " + i); } } diff --git a/public/java/test/org/broadinstitute/sting/utils/ReservoirDownsamplerUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/ReservoirDownsamplerUnitTest.java index 76dd5d3412..0f19e2f902 100644 --- a/public/java/test/org/broadinstitute/sting/utils/ReservoirDownsamplerUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/ReservoirDownsamplerUnitTest.java @@ -1,5 +1,6 @@ package org.broadinstitute.sting.utils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; import org.testng.annotations.Test; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; @@ -28,7 +29,7 @@ public void testEmptyIterator() { @Test public void testOneElementWithPoolSizeOne() { - List reads = Collections.singletonList(ArtificialSAMUtils.createArtificialRead(header,"read1",0,1,76)); + List reads = Collections.singletonList(ArtificialSAMUtils.createArtificialRead(header,"read1",0,1,76)); ReservoirDownsampler downsampler = new ReservoirDownsampler(1); downsampler.addAll(reads); @@ -40,7 +41,7 @@ public void testOneElementWithPoolSizeOne() { @Test public void testOneElementWithPoolSizeGreaterThanOne() { - List reads = Collections.singletonList(ArtificialSAMUtils.createArtificialRead(header,"read1",0,1,76)); + List reads = Collections.singletonList(ArtificialSAMUtils.createArtificialRead(header,"read1",0,1,76)); ReservoirDownsampler downsampler = new ReservoirDownsampler(5); downsampler.addAll(reads); From f4b409fa0df0ffc601cbf9efc718ab58c19b9b5c Mon Sep 17 00:00:00 2001 From: Guillermo del Angel Date: Fri, 21 Oct 2011 14:07:20 -0400 Subject: [PATCH 273/363] CombineVariants bug fix: when merging records with disparate alleles we were leaving AC,AF fields intact. This had as a consequence that we could end up with a record with 3 alt alleles but only 2 values in AC,AF fields. Now, if alleles in combined vc are different from original, and if AC,AF fields can't be recomputed from genotypes, we remove attributes from vc map since they'll be invalid anyway. Integration test md5 changed since there were several badly merged records in result --- .../sting/utils/variantcontext/VariantContextUtils.java | 4 +++- .../walkers/variantutils/CombineVariantsIntegrationTest.java | 4 ++-- 2 files 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 74ab7074aa..43f91041f2 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/VariantContextUtils.java @@ -607,7 +607,7 @@ public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, } // 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) + // 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; @@ -615,6 +615,8 @@ public static VariantContext simpleMerge(final GenomeLocParser genomeLocParser, 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; } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java index e30187a7ca..5a4d6e6a1b 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/variantutils/CombineVariantsIntegrationTest.java @@ -96,8 +96,8 @@ public void combinePLs(String file1, String file2, String md5) { @Test public void uniqueSNPs() { combine2("pilot2.snps.vcf4.genotypes.vcf", "yri.trio.gatk_glftrio.intersection.annotated.filtered.chr1.vcf", "", "78a49597f1abf1c738e67d50c8fbed2b"); } - @Test public void omniHM3Union() { combineSites(" -filteredRecordsMergeType KEEP_IF_ANY_UNFILTERED", "9253d61ddb52c429adf0e153cef494ca"); } - @Test public void omniHM3Intersect() { combineSites(" -filteredRecordsMergeType KEEP_IF_ALL_UNFILTERED", "5012dfe65cf7e7d8f014e97e4a996aea"); } + @Test public void omniHM3Union() { combineSites(" -filteredRecordsMergeType KEEP_IF_ANY_UNFILTERED", "4c63bfa5f73793aaca42e130ec49f238"); } + @Test public void omniHM3Intersect() { combineSites(" -filteredRecordsMergeType KEEP_IF_ALL_UNFILTERED", "86e326acbd8d2af8a6040eb146d92fc6"); } @Test public void threeWayWithRefs() { WalkerTestSpec spec = new WalkerTestSpec( From 102dafdcbc84ccd1cddd671639ddddb79896186e Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 21 Oct 2011 17:40:43 -0400 Subject: [PATCH 274/363] Validation of GATKSamRecord in read filters Moved the validation of the GATKSamRecord to the MalformedReadFilter with the intent to make the read filter the ultimate validation location for sam records. This way we can opt to filter out malformed reads if we know what we are doing or blow up otherwise. --- .../gatk/filters/MalformedReadFilter.java | 26 +++++++++++++++++-- .../sting/utils/sam/GATKSAMRecord.java | 4 --- 2 files changed, 24 insertions(+), 6 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 74deace9a9..11bbf9e4c5 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java +++ b/public/java/src/org/broadinstitute/sting/gatk/filters/MalformedReadFilter.java @@ -27,7 +27,9 @@ import net.sf.samtools.SAMFileHeader; import net.sf.samtools.SAMRecord; import net.sf.samtools.SAMSequenceRecord; +import org.broadinstitute.sting.commandline.Argument; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; +import org.broadinstitute.sting.utils.exceptions.UserException; /** * Filter out malformed reads. @@ -37,14 +39,25 @@ */ public class MalformedReadFilter extends ReadFilter { private SAMFileHeader header; - + + @Argument(fullName = "filter_mismatching_base_and_quals", shortName = "filterMBQ", doc = "if a read has mismatching number of bases and base qualities, filter out the read instead of blowing up.", required = false) + boolean filterMismatchingBaseAndQuals = false; + @Override public void initialize(GenomeAnalysisEngine engine) { this.header = engine.getSAMFileHeader(); } public boolean filterOut(SAMRecord read) { - return !checkInvalidAlignmentStart(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) || !checkCigarDisagreesWithAlignment(read); @@ -108,4 +121,13 @@ private static boolean checkCigarDisagreesWithAlignment(SAMRecord read) { return false; return true; } + + /** + * Check if the read has the same number of bases and base qualities + * @param read the read to validate + * @return true if they have the same number. False otherwise. + */ + private static boolean checkMismatchingBasesAndQuals(SAMRecord read) { + return (read.getReadLength() == read.getBaseQualities().length); + } } 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 c55a462f15..e7c235cf72 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -86,10 +86,6 @@ public GATKSAMRecord(SAMRecord record, boolean useOriginalBaseQualities, byte de if ( originalQuals != null ) mRecord.setBaseQualities(originalQuals); } - - // sanity check that the lengths of the base and quality strings are equal - if ( getBaseQualities().length != getReadLength() ) - throw new UserException.MalformedBAM(this, String.format("Error: the number of base qualities does not match the number of bases in %s.", mRecord.getReadName())); } /////////////////////////////////////////////////////////////////////////////// From 86305a5dcfc058a8a31bb27aaefe2576c993a3aa Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 21 Oct 2011 17:41:52 -0400 Subject: [PATCH 275/363] Adjusting the memory limits of the MDCP Indel caller needs more than 3G for large datasets. --- .../queue/qscripts/MethodsDevelopmentCallingPipeline.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 88c1bd2c88..da02c8ac5e 100755 --- a/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala +++ b/public/scala/qscript/org/broadinstitute/sting/queue/qscripts/MethodsDevelopmentCallingPipeline.scala @@ -205,7 +205,6 @@ class MethodsDevelopmentCallingPipeline extends QScript { // 1.) Unified Genotyper Base class GenotyperBase (t: Target) extends UnifiedGenotyper with UNIVERSAL_GATK_ARGS { - this.memoryLimit = 3 this.reference_sequence = t.reference this.intervalsString ++= List(t.intervals) this.scatterCount = 140 @@ -232,6 +231,7 @@ class MethodsDevelopmentCallingPipeline extends QScript { // 1b.) Call Indels with UG class indelCall (t: Target) extends GenotyperBase(t) { + this.memoryLimit = 6 this.out = t.rawIndelVCF this.glm = org.broadinstitute.sting.gatk.walkers.genotyper.GenotypeLikelihoodsCalculationModel.Model.INDEL this.baq = org.broadinstitute.sting.utils.baq.BAQ.CalculationMode.OFF @@ -259,7 +259,6 @@ class MethodsDevelopmentCallingPipeline extends QScript { // 3.) Variant Quality Score Recalibration - Generate Recalibration table class VQSR(t: Target, goldStandard: Boolean) extends VariantRecalibrator with UNIVERSAL_GATK_ARGS { - this.memoryLimit = 4 this.nt = 2 this.reference_sequence = t.reference this.intervalsString ++= List(t.intervals) From c3cb07fc1d3d4e4109be8cce0aec1567d918365b Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Sat, 22 Oct 2011 13:35:52 -0400 Subject: [PATCH 276/363] Relocating the ReduceBAM script. From 42bf9adede112282e8e8e45bf987ab01305b0a7e Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Sat, 22 Oct 2011 21:36:37 -0400 Subject: [PATCH 277/363] Initial version of "fast" FragmentPileup code -- Uses mayOverlapRoutine in ReadUtils -- Attempts to be smart when doing overlap calculation, to avoid unnecessary allocations -- PileupElement now comparable (sorts on offset than on start) -- Caliper microbenchmark to assess performance --- .../sting/utils/pileup/FragmentPileup.java | 124 ++++++++++++++++-- .../sting/utils/pileup/PileupElement.java | 16 ++- .../sting/utils/sam/ReadUtils.java | 48 +++++++ .../UnifiedGenotyperIntegrationTest.java | 12 +- .../utils/pileup/FragmentPileupBenchmark.java | 108 +++++++++++++++ .../utils/pileup/FragmentPileupUnitTest.java | 56 ++++---- 6 files changed, 321 insertions(+), 43 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java index f7d237401c..f9a94989bd 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java @@ -1,9 +1,10 @@ package org.broadinstitute.sting.utils.pileup; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.ReadUtils; + +import java.util.*; /** * An easy to access fragment-based pileup, which contains two separate pileups. The first @@ -16,28 +17,46 @@ * TODO -- technically we could generalize this code to support a pseudo-duplicate marking * TODO -- algorithm that could collect all duplicates into a single super pileup element * + * Oct 21: note that the order of the oneReadPileup and twoReadPileups are not + * defined. The algorithms that produce these lists are in fact producing + * lists of Pileup elements *NOT* sorted by alignment start position of the underlying + * reads. + * * User: depristo * Date: 3/26/11 * Time: 10:09 PM */ public class FragmentPileup { - final Collection oneReadPile; - final Collection twoReadPile = new ArrayList(); + Collection oneReadPile = null; + Collection twoReadPile = null; /** * Create a new Fragment-based pileup from the standard read-based pileup * @param pileup */ public FragmentPileup(ReadBackedPileup pileup) { - Map nameMap = new HashMap(); + //oldSlowCalculation(pileup); + fastNewCalculation(pileup); + } + + protected FragmentPileup(ReadBackedPileup pileup, boolean useOldAlgorithm) { + if ( useOldAlgorithm ) + oldSlowCalculation(pileup); + else + fastNewCalculation(pileup); + } + + private final void oldSlowCalculation(final ReadBackedPileup pileup) { + final Map nameMap = new HashMap(); // build an initial map, grabbing all of the multi-read fragments - for ( PileupElement p : pileup ) { - String readName = p.getRead().getReadName(); + for ( final PileupElement p : pileup ) { + final String readName = p.getRead().getReadName(); - PileupElement pe1 = nameMap.get(readName); + final PileupElement pe1 = nameMap.get(readName); if ( pe1 != null ) { // assumes we have at most 2 reads per fragment + if ( twoReadPile == null ) twoReadPile = new ArrayList(); twoReadPile.add(new TwoReadPileupElement(pe1, p)); nameMap.remove(readName); } else { @@ -45,17 +64,96 @@ public FragmentPileup(ReadBackedPileup pileup) { } } - // now set the one Read pile to the values in the nameMap with only a single read oneReadPile = nameMap.values(); } + /** + * @param pileup + */ + private final void fastNewCalculation(final ReadBackedPileup pileup) { + Map nameMap = null; // lazy initialization + + for ( final PileupElement p : pileup ) { + final SAMRecord read = p.getRead(); + + switch (ReadUtils.readMightOverlapMate(read) ) { + // we know for certain this read doesn't have an overlapping mate + case NO: { + addToOnePile(p); + break; + } + + // we know that we overlap our mate, so put the read in the nameMap in + // case our mate shows up + case LEFT_YES: { + nameMap = addToNameMap(nameMap, p); + break; + } + + // read starts at the same position, so we are looking at either the first or + // the second read. In the first, add it to the map, in the second grab it + // from the map and create a fragment + case SAME_START: { + final PileupElement pe1 = getFromNameMap(nameMap, p); + if ( pe1 != null ) { + addToTwoPile(pe1, p); + nameMap.remove(p.getRead().getReadName()); + } else { + nameMap = addToNameMap(nameMap, p); + } + break; + } + + + // in this case we need to see if our mate is already present, and if so + // grab the read from the list + case RIGHT_MAYBE: { + final PileupElement pe1 = getFromNameMap(nameMap, p); + if ( pe1 != null ) { + addToTwoPile(pe1, p); + nameMap.remove(p.getRead().getReadName()); + } else { + addToOnePile(p); + } + break; + } + } + } + + if ( nameMap != null && ! nameMap.isEmpty() ) // could be slightly more optimally + for ( final PileupElement p : nameMap.values() ) + addToOnePile(p); + } + + private final Map addToNameMap(Map map, final PileupElement p) { + if ( map == null ) map = new HashMap(); + map.put(p.getRead().getReadName(), p); + return map; + } + + private final PileupElement getFromNameMap(Map map, final PileupElement p) { + return map == null ? null : map.get(p.getRead().getReadName()); + } + + + private final void addToOnePile(final PileupElement p) { + if ( oneReadPile == null ) oneReadPile = new ArrayList(); + oneReadPile.add(p); + } + + private final void addToTwoPile(final PileupElement p1, final PileupElement p2) { + // assumes we have at most 2 reads per fragment + if ( twoReadPile == null ) twoReadPile = new ArrayList(); + twoReadPile.add(new TwoReadPileupElement(p1, p2)); + } + /** * Gets the pileup elements containing two reads, in no particular order * * @return */ public Collection getTwoReadPileup() { - return twoReadPile; + return twoReadPile == null ? Collections.emptyList() : twoReadPile; } /** @@ -64,7 +162,7 @@ public Collection getTwoReadPileup() { * @return */ public Collection getOneReadPileup() { - return oneReadPile; + return oneReadPile == null ? Collections.emptyList() : oneReadPile; } /** 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 e47b6ada76..3d6b6f4b9d 100755 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/PileupElement.java @@ -13,7 +13,7 @@ * Date: Apr 14, 2009 * Time: 8:54:05 AM */ -public class PileupElement { +public class PileupElement implements Comparable { public static final byte DELETION_BASE = BaseUtils.D; public static final byte DELETION_QUAL = (byte) 16; public static final byte A_FOLLOWED_BY_INSERTION_BASE = (byte) 87; @@ -76,6 +76,20 @@ protected byte getQual(final int offset) { return isDeletion() ? DELETION_QUAL : read.getBaseQualities()[offset]; } + @Override + public int compareTo(final PileupElement pileupElement) { + if ( offset < pileupElement.offset ) + return -1; + else if ( offset > pileupElement.offset ) + return 1; + else if ( read.getAlignmentStart() < pileupElement.read.getAlignmentStart() ) + return -1; + else if ( read.getAlignmentStart() > pileupElement.read.getAlignmentStart() ) + return 1; + else + return 0; + } + // -------------------------------------------------------------------------- // // Reduced read accessors 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 f8e4927ed8..f093e6539c 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -214,6 +214,54 @@ public static OverlapType readPairBaseOverlapType(final SAMRecord rec, long base return state; } + /** + * s1 e1 + * |-----------------------> [record in hand] + * s2 + * <-----------------------| + * + * s1, e1, and s2 are all in the record. Assuming that s1 < s2 (we are the left most read), + * we can compute whether we overlap with our mate by seeing if s2 <= e1 or no. If e1 < + * s2 then we known that we cannot over. + * + * If we are looking at the right read + * + * s1 + * |-----------------------> + * s2 e2 + * <-----------------------| [record in hand] + * + * we know the position of s1 and s2, but we don't know e1, so we cannot tell if we + * overlap with our mate or not, so in this case we return MAYBE. + * + * Note that if rec has an unmapped mate or is unpaired we certainly know the answer + * + * @param rec + * @return + */ + public static ReadOverlapsMateType readMightOverlapMate(final SAMRecord rec) { + if ( ! rec.getReadPairedFlag() || rec.getMateUnmappedFlag() ) { + return ReadOverlapsMateType.NO; + } else { // read is actually paired + final int recStart = rec.getAlignmentStart(); + final int recEnd = rec.getAlignmentEnd(); + final int mateStart = rec.getMateAlignmentStart(); + + if ( recStart < mateStart ) { + // we are the left most read + return mateStart <= recEnd ? ReadOverlapsMateType.LEFT_YES: ReadOverlapsMateType.NO; + } else if ( recStart == mateStart ) { + // we are the left most read + return ReadOverlapsMateType.SAME_START; + } else { + // we are the right most read, so we cannot tell + return ReadOverlapsMateType.RIGHT_MAYBE; + } + } + } + + public enum ReadOverlapsMateType { LEFT_YES, NO, SAME_START, RIGHT_MAYBE } + private static Pair getAdaptorBoundaries(SAMRecord rec, int adaptorLength) { int isize = rec.getInferredInsertSize(); if ( isize == 0 ) 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 6b63464474..7d6cfc7ad6 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 @@ -224,7 +224,7 @@ public void testSimpleIndels() { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("0bece77ce6bc447438ef9b2921b2dc41")); + Arrays.asList("eeba568272f9b42d5450da75c7cc6d2d")); executeTest(String.format("test indel caller in SLX"), spec); } @@ -252,7 +252,7 @@ public void testMultiTechnologyIndels() { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("790b1a1d6ab79eee8c24812bb8ca6fae")); + Arrays.asList("19ff9bd3139480bdf79dcbf117cf2b24")); executeTest(String.format("test indel calling, multiple technologies"), spec); } @@ -262,7 +262,7 @@ public void testWithIndelAllelesPassedIn1() { 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("408d3aba4d094c067fc00a43992c2292")); + Arrays.asList("118918f2e9e56a3cfc5ccb2856d529c8")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -272,7 +272,7 @@ public void testWithIndelAllelesPassedIn2() { 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("5e4e09354410b76fc0d822050d84132a")); + Arrays.asList("a20799237accd52c1b8c2ac096309c8f")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -282,7 +282,7 @@ 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 + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,080,000", 1, - Arrays.asList("c599eedbeb422713b8a28529e805e4ae")); + Arrays.asList("18ef8181157b4ac3eb8492f538467f92")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -291,7 +291,7 @@ 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 + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("37d908a682ac269f8f19dec939ff5b01")); + Arrays.asList("ad884e511a751b05e64db5314314365a")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java new file mode 100644 index 0000000000..fe1ca305fa --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java @@ -0,0 +1,108 @@ +/* + * 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.pileup; + +import com.google.caliper.Param; +import com.google.caliper.SimpleBenchmark; +import com.google.caliper.runner.CaliperMain; +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; + +import java.util.*; + +/** + * Caliper microbenchmark of fragment pileup + */ +public class FragmentPileupBenchmark extends SimpleBenchmark { + final int N_PILEUPS_TO_GENERATE = 100; + List pileups = new ArrayList(N_PILEUPS_TO_GENERATE); + + @Param({"10", "100", "1000"}) // , "10000"}) + int pileupSize; // set automatically by framework + + @Param({"150", "400"}) + int insertSize; // set automatically by framework + + @Override protected void setUp() { + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); + GenomeLocParser genomeLocParser; + genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); + final int pos = 50; + GenomeLoc loc = genomeLocParser.createGenomeLoc("chr1", pos); + + final Random ran = new Random(); + final int readLen = 100; + final boolean leftIsFirst = true; + final boolean leftIsNegative = false; + final int insertSizeVariation = insertSize / 10; + + for ( int pileupN = 0; pileupN < N_PILEUPS_TO_GENERATE; pileupN++ ) { + List pileupElements = new ArrayList(); + for ( int i = 0; i < pileupSize / 2; i++ ) { + final String readName = "read" + i; + final int leftStart = new Random().nextInt(49) + 1; + final int fragmentSize = (int)(ran.nextGaussian() * insertSizeVariation + insertSize); + final int rightStart = leftStart + fragmentSize - readLen; + + if ( rightStart <= 0 ) continue; + + List pair = FragmentPileupUnitTest.createPair(header, readName, readLen, leftStart, rightStart, leftIsFirst, leftIsNegative); + SAMRecord left = pair.get(0); + SAMRecord right = pair.get(1); + + pileupElements.add(new PileupElement(left, pos - leftStart)); + + if ( pos >= right.getAlignmentStart() && pos <= right.getAlignmentEnd() ) { + pileupElements.add(new PileupElement(right, pos - rightStart)); + } + } + + Collections.sort(pileupElements); + pileups.add(new ReadBackedPileupImpl(loc, pileupElements)); + } + } + + public void timeNaiveNameMatch(int rep) { + int nFrags = 0; + for ( int i = 0; i < rep; i++ ) { + for ( ReadBackedPileup rbp : pileups ) + nFrags += new FragmentPileup(rbp, true).getTwoReadPileup().size(); + } + } + + public void timeFastNameMatch(int rep) { + int nFrags = 0; + for ( int i = 0; i < rep; i++ ) + for ( ReadBackedPileup rbp : pileups ) + nFrags += new FragmentPileup(rbp, false).getTwoReadPileup().size(); + } + + public static void main(String[] args) { + CaliperMain.main(FragmentPileupBenchmark.class, args); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java index 7782595280..be4ecc0c64 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java @@ -43,37 +43,48 @@ public class FragmentPileupUnitTest extends BaseTest { private static SAMFileHeader header; - private class FragmentPileupTest extends TestDataProvider { - List states = new ArrayList(); + public final static List createPair(SAMFileHeader header, String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { + SAMRecord left = ArtificialSAMUtils.createArtificialRead(header, name, 0, leftStart, readLen); + SAMRecord right = ArtificialSAMUtils.createArtificialRead(header, name, 0, rightStart, readLen); - private FragmentPileupTest(String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { - super(FragmentPileupTest.class, String.format("%s-leftIsFirst:%b-leftIsNegative:%b", name, leftIsFirst, leftIsNegative)); + left.setReadPairedFlag(true); + right.setReadPairedFlag(true); - for ( int pos = leftStart; pos < rightStart + readLen; pos++) { - SAMRecord left = ArtificialSAMUtils.createArtificialRead(header, "readpair", 0, leftStart, readLen); - SAMRecord right = ArtificialSAMUtils.createArtificialRead(header, "readpair", 0, rightStart, readLen); + left.setProperPairFlag(true); + right.setProperPairFlag(true); - left.setProperPairFlag(true); - right.setProperPairFlag(true); + left.setFirstOfPairFlag(leftIsFirst); + right.setFirstOfPairFlag(! leftIsFirst); - left.setFirstOfPairFlag(leftIsFirst); - right.setFirstOfPairFlag(! leftIsFirst); + left.setReadNegativeStrandFlag(leftIsNegative); + left.setMateNegativeStrandFlag(!leftIsNegative); + right.setReadNegativeStrandFlag(!leftIsNegative); + right.setMateNegativeStrandFlag(leftIsNegative); - left.setReadNegativeStrandFlag(leftIsNegative); - left.setMateNegativeStrandFlag(!leftIsNegative); - right.setReadNegativeStrandFlag(!leftIsNegative); - right.setMateNegativeStrandFlag(leftIsNegative); + left.setMateAlignmentStart(right.getAlignmentStart()); + right.setMateAlignmentStart(left.getAlignmentStart()); - left.setMateAlignmentStart(right.getAlignmentStart()); - right.setMateAlignmentStart(left.getAlignmentStart()); + left.setMateReferenceIndex(0); + right.setMateReferenceIndex(0); - left.setMateReferenceIndex(0); - right.setMateReferenceIndex(0); + int isize = rightStart + readLen - leftStart; + left.setInferredInsertSize(isize); + right.setInferredInsertSize(-isize); - int isize = rightStart + readLen - leftStart; - left.setInferredInsertSize(isize); - right.setInferredInsertSize(-isize); + return Arrays.asList(left, right); + } + private class FragmentPileupTest extends TestDataProvider { + List states = new ArrayList(); + + private FragmentPileupTest(String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { + super(FragmentPileupTest.class, String.format("%s-leftIsFirst:%b-leftIsNegative:%b", name, leftIsFirst, leftIsNegative)); + + List pair = createPair(header, "readpair", readLen, leftStart, rightStart, leftIsFirst, leftIsNegative); + SAMRecord left = pair.get(0); + SAMRecord right = pair.get(1); + + for ( int pos = leftStart; pos < rightStart + readLen; pos++) { boolean posCoveredByLeft = pos >= left.getAlignmentStart() && pos <= left.getAlignmentEnd(); boolean posCoveredByRight = pos >= right.getAlignmentStart() && pos <= right.getAlignmentEnd(); @@ -139,7 +150,6 @@ public void testMe(FragmentPileupTest test) { } } - @BeforeTest public void setup() { header = ArtificialSAMUtils.createArtificialSamHeader(1,1,1000); From f5d910b8a54b1dbceab506a7260ebaf1d9c4796b Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Sun, 23 Oct 2011 13:29:08 -0400 Subject: [PATCH 278/363] Haplotype caller now sends genotype likelihoods to the exact model to genotype the events found in the best haplotypes. --- .../AlleleFrequencyCalculationModel.java | 6 +- .../genotyper/ExactAFCalculationModel.java | 4 +- .../genotyper/GridSearchAFEstimation.java | 4 +- .../MultiallelicGenotypeLikelihoods.java | 7 +- .../genotyper/UnifiedGenotyperEngine.java | 79 ++++++++++++++++++- 5 files changed, 82 insertions(+), 18 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 1bb6970561..35a9fe31d3 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,16 +68,12 @@ protected AlleleFrequencyCalculationModel(UnifiedArgumentCollection UAC, int N, /** * Must be overridden by concrete subclasses - * @param tracker rod data - * @param ref reference context * @param GLs genotype likelihoods * @param Alleles Alleles corresponding to GLs * @param log10AlleleFrequencyPriors priors * @param log10AlleleFrequencyPosteriors array (pre-allocated) to store results */ - protected abstract void getLog10PNonRef(RefMetaDataTracker tracker, - ReferenceContext ref, - Map GLs, List Alleles, + protected abstract void getLog10PNonRef(Map 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 f87eae7814..1c2d82ab73 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 @@ -51,9 +51,7 @@ protected ExactAFCalculationModel(UnifiedArgumentCollection UAC, int N, Logger l super(UAC, N, logger, verboseWriter); } - public void getLog10PNonRef(RefMetaDataTracker tracker, - ReferenceContext ref, - Map GLs, List alleles, + public void getLog10PNonRef(Map GLs, List alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { final int numAlleles = alleles.size(); 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 f4195e5f08..27842a8bf7 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 @@ -52,9 +52,7 @@ protected GridSearchAFEstimation(UnifiedArgumentCollection UAC, int N, Logger lo AFMatrix = new AlleleFrequencyMatrix(N); } - protected void getLog10PNonRef(RefMetaDataTracker tracker, - ReferenceContext ref, - Map GLs, List alleles, + protected void getLog10PNonRef(Map GLs, List alleles, double[] log10AlleleFrequencyPriors, double[] log10AlleleFrequencyPosteriors) { initializeAFMatrix(GLs); 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 index 3652763dee..4f378b24a9 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/MultiallelicGenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/MultiallelicGenotypeLikelihoods.java @@ -4,6 +4,7 @@ import org.broadinstitute.sting.utils.variantcontext.Allele; import java.util.ArrayList; +import java.util.List; /** * Created by IntelliJ IDEA. @@ -15,11 +16,11 @@ public class MultiallelicGenotypeLikelihoods { private String sample; private double[] GLs; - private ArrayList alleleList; + private List alleleList; private int depth; public MultiallelicGenotypeLikelihoods(String sample, - ArrayList A, + List A, double[] log10Likelihoods, int depth) { /* Check for consistency between likelihood vector and number of alleles */ int numAlleles = A.size(); @@ -40,7 +41,7 @@ public double[] getLikelihoods() { return GLs; } - public ArrayList getAlleles() { + public List getAlleles() { return alleleList; } 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 b72b68f9ff..adb4231454 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 @@ -325,7 +325,7 @@ private VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, Refere // 'zero' out the AFs (so that we don't have to worry if not all samples have reads at this position) clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(tracker, refContext, vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); // find the most likely frequency int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()); @@ -383,7 +383,7 @@ private VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, Refere // the overall lod VariantContext vcOverall = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.COMPLETE, vc.getAlternateAllele(0), false, model); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(tracker, refContext, vcOverall.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), 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); //if ( DEBUG_SLOD ) System.out.println("overallLog10PofF=" + overallLog10PofF); @@ -391,7 +391,7 @@ private VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, Refere // the forward lod VariantContext vcForward = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.FORWARD, vc.getAlternateAllele(0), false, model); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(tracker, refContext, vcForward.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), 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); @@ -400,7 +400,7 @@ private VariantCallContext calculateGenotypes(RefMetaDataTracker tracker, Refere // the reverse lod VariantContext vcReverse = calculateLikelihoods(tracker, refContext, stratifiedContexts, AlignmentContextUtils.ReadOrientation.REVERSE, vc.getAlternateAllele(0), false, model); clearAFarray(log10AlleleFrequencyPosteriors.get()); - afcm.get().getLog10PNonRef(tracker, refContext, vcReverse.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), 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); @@ -447,6 +447,77 @@ else if (rawContext.hasBasePileup()) return new VariantCallContext(vcCall, confidentlyCalled(phredScaledConfidence, PofF)); } + 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 ) { + log10AlleleFrequencyPosteriors.set(new double[N+1]); + afcm.set(getAlleleFrequencyCalculationObject(N, logger, verboseWriter, UAC)); + } + + // 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(log10AlleleFrequencyPosteriors.get()); + afcm.get().getLog10PNonRef(vc.getGenotypes(), vc.getAlleles(), getAlleleFrequencyPriors(model), log10AlleleFrequencyPosteriors.get()); + + // find the most likely frequency + int bestAFguess = MathUtils.maxElementIndex(log10AlleleFrequencyPosteriors.get()); + + // calculate p(f>0) + double[] normalizedPosteriors = MathUtils.normalizeFromLog10(log10AlleleFrequencyPosteriors.get()); + 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]; + } 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 ) + break; + sum += log10AlleleFrequencyPosteriors.get()[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 + Map 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(); + + 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()); + } + VariantContext vcCall = new VariantContext("UG_call", loc.getContig(), loc.getStart(), endLoc, + myAlleles, genotypes, phredScaledConfidence/10.0, passesCallThreshold(phredScaledConfidence) ? null : filter, attributes, vc.getReferenceBaseForIndel()); + + 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 From 090382ce03c5f792462f93d6ec59525dd6c49b50 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Mon, 24 Oct 2011 08:21:05 -0400 Subject: [PATCH 279/363] Now calculates proper multi-allelic likelihoods and sends them to the exact model if appropriate. From 13702939ac31c1e6123dbf1b27c87406c6d7d488 Mon Sep 17 00:00:00 2001 From: Ryan Poplin Date: Mon, 24 Oct 2011 10:15:58 -0400 Subject: [PATCH 280/363] HaplotypeCaller now calculates multi-sample likelihoods and passes them to the exact model to make multi-sample calls. From 166174a551860fa18e8092f3dbe56e0ef28eb884 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 24 Oct 2011 14:04:53 -0400 Subject: [PATCH 281/363] Google caliper example execution script -- FragmentPileup with final performance testing --- .../sting/utils/pileup/FragmentPileup.java | 93 +++++++++++++++++-- .../utils/pileup/FragmentPileupBenchmark.java | 34 ++++--- 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java index f9a94989bd..e811db3559 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java @@ -30,6 +30,13 @@ public class FragmentPileup { Collection oneReadPile = null; Collection twoReadPile = null; + public enum FragmentMatchingAlgorithm { + ORIGINAL, + FAST_V1, + skipNonOverlapping, + skipNonOverlappingNotLazy + } + /** * Create a new Fragment-based pileup from the standard read-based pileup * @param pileup @@ -39,15 +46,17 @@ public FragmentPileup(ReadBackedPileup pileup) { fastNewCalculation(pileup); } - protected FragmentPileup(ReadBackedPileup pileup, boolean useOldAlgorithm) { - if ( useOldAlgorithm ) - oldSlowCalculation(pileup); - else - fastNewCalculation(pileup); + protected FragmentPileup(ReadBackedPileup pileup, FragmentMatchingAlgorithm algorithm) { + switch ( algorithm ) { + case ORIGINAL: oldSlowCalculation(pileup); break; + case FAST_V1: fastNewCalculation(pileup); break; + case skipNonOverlapping: skipNonOverlapping(pileup); break; + case skipNonOverlappingNotLazy: skipNonOverlappingNotLazy(pileup); break; + } } private final void oldSlowCalculation(final ReadBackedPileup pileup) { - final Map nameMap = new HashMap(); + final Map nameMap = new HashMap(pileup.size()); // build an initial map, grabbing all of the multi-read fragments for ( final PileupElement p : pileup ) { @@ -104,7 +113,6 @@ private final void fastNewCalculation(final ReadBackedPileup pileup) { break; } - // in this case we need to see if our mate is already present, and if so // grab the read from the list case RIGHT_MAYBE: { @@ -120,9 +128,74 @@ private final void fastNewCalculation(final ReadBackedPileup pileup) { } } - if ( nameMap != null && ! nameMap.isEmpty() ) // could be slightly more optimally - for ( final PileupElement p : nameMap.values() ) - addToOnePile(p); + if ( nameMap != null && ! nameMap.isEmpty() ) { + if ( oneReadPile == null ) + oneReadPile = nameMap.values(); + else + oneReadPile.addAll(nameMap.values()); + } + } + + /** + * @param pileup + */ + private final void skipNonOverlappingNotLazy(final ReadBackedPileup pileup) { + oneReadPile = new ArrayList(pileup.size()); + twoReadPile = new ArrayList(); + final Map nameMap = new HashMap(pileup.size()); + + // build an initial map, grabbing all of the multi-read fragments + for ( final PileupElement p : pileup ) { + // if we know that this read won't overlap its mate, or doesn't have one, jump out early + final SAMRecord read = p.getRead(); + final int mateStart = read.getMateAlignmentStart(); + if ( mateStart == 0 || mateStart > read.getAlignmentEnd() ) { + oneReadPile.add(p); + } else { + final String readName = p.getRead().getReadName(); + final PileupElement pe1 = nameMap.get(readName); + if ( pe1 != null ) { + // assumes we have at most 2 reads per fragment + twoReadPile.add(new TwoReadPileupElement(pe1, p)); + nameMap.remove(readName); + } else { + nameMap.put(readName, p); + } + } + } + + oneReadPile.addAll(nameMap.values()); + } + + private final void skipNonOverlapping(final ReadBackedPileup pileup) { + Map nameMap = null; + + // build an initial map, grabbing all of the multi-read fragments + for ( final PileupElement p : pileup ) { + // if we know that this read won't overlap its mate, or doesn't have one, jump out early + final SAMRecord read = p.getRead(); + final int mateStart = read.getMateAlignmentStart(); + if ( mateStart == 0 || mateStart > read.getAlignmentEnd() ) { + if ( oneReadPile == null ) oneReadPile = new ArrayList(pileup.size()); + oneReadPile.add(p); + } else { + final String readName = p.getRead().getReadName(); + final PileupElement pe1 = nameMap == null ? null : nameMap.get(readName); + if ( pe1 != null ) { + // assumes we have at most 2 reads per fragment + if ( twoReadPile == null ) twoReadPile = new ArrayList(); + twoReadPile.add(new TwoReadPileupElement(pe1, p)); + nameMap.remove(readName); + } else { + nameMap = addToNameMap(nameMap, p); + } + } + } + + if ( oneReadPile == null ) + oneReadPile = nameMap == null ? Collections.emptyList() : nameMap.values(); + else if ( nameMap != null ) + oneReadPile.addAll(nameMap.values()); } private final Map addToNameMap(Map map, final PileupElement p) { diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java index fe1ca305fa..6a5378d46e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java @@ -39,16 +39,17 @@ * Caliper microbenchmark of fragment pileup */ public class FragmentPileupBenchmark extends SimpleBenchmark { - final int N_PILEUPS_TO_GENERATE = 100; - List pileups = new ArrayList(N_PILEUPS_TO_GENERATE); + List pileups; - @Param({"10", "100", "1000"}) // , "10000"}) + @Param({"0", "4", "30", "150", "1000"}) int pileupSize; // set automatically by framework - @Param({"150", "400"}) + @Param({"200", "400"}) int insertSize; // set automatically by framework @Override protected void setUp() { + final int nPileupsToGenerate = 100; + pileups = new ArrayList(nPileupsToGenerate); SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); GenomeLocParser genomeLocParser; genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); @@ -61,7 +62,7 @@ public class FragmentPileupBenchmark extends SimpleBenchmark { final boolean leftIsNegative = false; final int insertSizeVariation = insertSize / 10; - for ( int pileupN = 0; pileupN < N_PILEUPS_TO_GENERATE; pileupN++ ) { + for ( int pileupN = 0; pileupN < nPileupsToGenerate; pileupN++ ) { List pileupElements = new ArrayList(); for ( int i = 0; i < pileupSize / 2; i++ ) { final String readName = "read" + i; @@ -87,19 +88,28 @@ public class FragmentPileupBenchmark extends SimpleBenchmark { } } - public void timeNaiveNameMatch(int rep) { + private void run(int rep, FragmentPileup.FragmentMatchingAlgorithm algorithm) { int nFrags = 0; for ( int i = 0; i < rep; i++ ) { for ( ReadBackedPileup rbp : pileups ) - nFrags += new FragmentPileup(rbp, true).getTwoReadPileup().size(); + nFrags += new FragmentPileup(rbp, algorithm).getTwoReadPileup().size(); } } - public void timeFastNameMatch(int rep) { - int nFrags = 0; - for ( int i = 0; i < rep; i++ ) - for ( ReadBackedPileup rbp : pileups ) - nFrags += new FragmentPileup(rbp, false).getTwoReadPileup().size(); + public void timeOriginal(int rep) { + run(rep, FragmentPileup.FragmentMatchingAlgorithm.ORIGINAL); + } + + public void timeFullOverlapPotential(int rep) { + run(rep, FragmentPileup.FragmentMatchingAlgorithm.FAST_V1); + } + + public void timeSkipNonOverlapping(int rep) { + run(rep, FragmentPileup.FragmentMatchingAlgorithm.skipNonOverlapping); + } + + public void timeSkipNonOverlappingNotLazy(int rep) { + run(rep, FragmentPileup.FragmentMatchingAlgorithm.skipNonOverlappingNotLazy); } public static void main(String[] args) { From 502592671d7bfdbb9cff0575e94ca3c0946d000d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 24 Oct 2011 14:40:05 -0400 Subject: [PATCH 282/363] Cleanup FragmentPileup before main repo commit -- removed intermiate functions. Now only original version and best optimized new version remain -- Moved general artificial read backed pileup creation code into ArtificialSamUtils --- .../sting/utils/pileup/FragmentPileup.java | 149 +++--------------- .../sting/utils/sam/ArtificialSAMUtils.java | 91 ++++++++++- .../sting/utils/sam/ReadUtils.java | 48 ------ .../utils/pileup/FragmentPileupBenchmark.java | 40 +---- .../utils/pileup/FragmentPileupUnitTest.java | 33 +--- 5 files changed, 109 insertions(+), 252 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java b/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java index e811db3559..4eda7c7cdf 100644 --- a/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java +++ b/public/java/src/org/broadinstitute/sting/utils/pileup/FragmentPileup.java @@ -14,9 +14,6 @@ * * Based on the original code by E. Banks * - * TODO -- technically we could generalize this code to support a pseudo-duplicate marking - * TODO -- algorithm that could collect all duplicates into a single super pileup element - * * Oct 21: note that the order of the oneReadPileup and twoReadPileups are not * defined. The algorithms that produce these lists are in fact producing * lists of Pileup elements *NOT* sorted by alignment start position of the underlying @@ -30,11 +27,9 @@ public class FragmentPileup { Collection oneReadPile = null; Collection twoReadPile = null; - public enum FragmentMatchingAlgorithm { + protected enum FragmentMatchingAlgorithm { ORIGINAL, - FAST_V1, skipNonOverlapping, - skipNonOverlappingNotLazy } /** @@ -42,16 +37,14 @@ public enum FragmentMatchingAlgorithm { * @param pileup */ public FragmentPileup(ReadBackedPileup pileup) { - //oldSlowCalculation(pileup); - fastNewCalculation(pileup); + skipNonOverlapping(pileup); } + /** For performance testing only */ protected FragmentPileup(ReadBackedPileup pileup, FragmentMatchingAlgorithm algorithm) { switch ( algorithm ) { case ORIGINAL: oldSlowCalculation(pileup); break; - case FAST_V1: fastNewCalculation(pileup); break; case skipNonOverlapping: skipNonOverlapping(pileup); break; - case skipNonOverlappingNotLazy: skipNonOverlappingNotLazy(pileup); break; } } @@ -76,148 +69,42 @@ private final void oldSlowCalculation(final ReadBackedPileup pileup) { oneReadPile = nameMap.values(); } - /** - * @param pileup - */ - private final void fastNewCalculation(final ReadBackedPileup pileup) { - Map nameMap = null; // lazy initialization - - for ( final PileupElement p : pileup ) { - final SAMRecord read = p.getRead(); - - switch (ReadUtils.readMightOverlapMate(read) ) { - // we know for certain this read doesn't have an overlapping mate - case NO: { - addToOnePile(p); - break; - } - - // we know that we overlap our mate, so put the read in the nameMap in - // case our mate shows up - case LEFT_YES: { - nameMap = addToNameMap(nameMap, p); - break; - } - - // read starts at the same position, so we are looking at either the first or - // the second read. In the first, add it to the map, in the second grab it - // from the map and create a fragment - case SAME_START: { - final PileupElement pe1 = getFromNameMap(nameMap, p); - if ( pe1 != null ) { - addToTwoPile(pe1, p); - nameMap.remove(p.getRead().getReadName()); - } else { - nameMap = addToNameMap(nameMap, p); - } - break; - } - - // in this case we need to see if our mate is already present, and if so - // grab the read from the list - case RIGHT_MAYBE: { - final PileupElement pe1 = getFromNameMap(nameMap, p); - if ( pe1 != null ) { - addToTwoPile(pe1, p); - nameMap.remove(p.getRead().getReadName()); - } else { - addToOnePile(p); - } - break; - } - } - } - - if ( nameMap != null && ! nameMap.isEmpty() ) { - if ( oneReadPile == null ) - oneReadPile = nameMap.values(); - else - oneReadPile.addAll(nameMap.values()); - } - } - - /** - * @param pileup - */ - private final void skipNonOverlappingNotLazy(final ReadBackedPileup pileup) { - oneReadPile = new ArrayList(pileup.size()); - twoReadPile = new ArrayList(); - final Map nameMap = new HashMap(pileup.size()); - - // build an initial map, grabbing all of the multi-read fragments - for ( final PileupElement p : pileup ) { - // if we know that this read won't overlap its mate, or doesn't have one, jump out early - final SAMRecord read = p.getRead(); - final int mateStart = read.getMateAlignmentStart(); - if ( mateStart == 0 || mateStart > read.getAlignmentEnd() ) { - oneReadPile.add(p); - } else { - final String readName = p.getRead().getReadName(); - final PileupElement pe1 = nameMap.get(readName); - if ( pe1 != null ) { - // assumes we have at most 2 reads per fragment - twoReadPile.add(new TwoReadPileupElement(pe1, p)); - nameMap.remove(readName); - } else { - nameMap.put(readName, p); - } - } - } - - oneReadPile.addAll(nameMap.values()); - } - private final void skipNonOverlapping(final ReadBackedPileup pileup) { Map nameMap = null; // build an initial map, grabbing all of the multi-read fragments for ( final PileupElement p : pileup ) { - // if we know that this read won't overlap its mate, or doesn't have one, jump out early final SAMRecord read = p.getRead(); final int mateStart = read.getMateAlignmentStart(); + if ( mateStart == 0 || mateStart > read.getAlignmentEnd() ) { - if ( oneReadPile == null ) oneReadPile = new ArrayList(pileup.size()); + // if we know that this read won't overlap its mate, or doesn't have one, jump out early + if ( oneReadPile == null ) oneReadPile = new ArrayList(pileup.size()); // lazy init oneReadPile.add(p); } else { + // the read might overlap it's mate, or is the rightmost read of a pair final String readName = p.getRead().getReadName(); final PileupElement pe1 = nameMap == null ? null : nameMap.get(readName); if ( pe1 != null ) { // assumes we have at most 2 reads per fragment - if ( twoReadPile == null ) twoReadPile = new ArrayList(); + if ( twoReadPile == null ) twoReadPile = new ArrayList(); // lazy init twoReadPile.add(new TwoReadPileupElement(pe1, p)); nameMap.remove(readName); } else { - nameMap = addToNameMap(nameMap, p); + if ( nameMap == null ) nameMap = new HashMap(pileup.size()); // lazy init + nameMap.put(readName, p); } } } - if ( oneReadPile == null ) - oneReadPile = nameMap == null ? Collections.emptyList() : nameMap.values(); - else if ( nameMap != null ) - oneReadPile.addAll(nameMap.values()); - } - - private final Map addToNameMap(Map map, final PileupElement p) { - if ( map == null ) map = new HashMap(); - map.put(p.getRead().getReadName(), p); - return map; - } - - private final PileupElement getFromNameMap(Map map, final PileupElement p) { - return map == null ? null : map.get(p.getRead().getReadName()); - } - - - private final void addToOnePile(final PileupElement p) { - if ( oneReadPile == null ) oneReadPile = new ArrayList(); - oneReadPile.add(p); - } - - private final void addToTwoPile(final PileupElement p1, final PileupElement p2) { - // assumes we have at most 2 reads per fragment - if ( twoReadPile == null ) twoReadPile = new ArrayList(); - twoReadPile.add(new TwoReadPileupElement(p1, p2)); + // add all of the reads that are potentially overlapping but whose mate never showed + // up to the oneReadPile + if ( nameMap != null && ! nameMap.isEmpty() ) { + if ( oneReadPile == null ) + oneReadPile = nameMap.values(); + else + oneReadPile.addAll(nameMap.values()); + } } /** 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 4eba323831..1b36411289 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ArtificialSAMUtils.java @@ -2,11 +2,15 @@ import net.sf.samtools.*; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; 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.pileup.ReadBackedPileupImpl; import java.io.File; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * @author aaron @@ -29,7 +33,7 @@ public static void createArtificialBamFile( String filename, int numberOfChromos File outFile = new File(filename); SAMFileWriter out = new SAMFileWriterFactory().makeBAMWriter(header, true, outFile); - + for (int x = startingChromosome; x < startingChromosome + numberOfChromosomes; x++) { for (int readNumber = 1; readNumber < readsPerChomosome; readNumber++) { out.addAlignment(createArtificialRead(header, "Read_" + readNumber, x - startingChromosome, readNumber, DEFAULT_READ_LENGTH)); @@ -145,7 +149,7 @@ public static SAMFileHeader createEnumeratedReadGroups( SAMFileHeader header, Li */ public static GATKSAMRecord createArtificialRead(SAMFileHeader header, String name, int refIndex, int alignmentStart, int length) { if( (refIndex == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart != SAMRecord.NO_ALIGNMENT_START) || - (refIndex != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart == SAMRecord.NO_ALIGNMENT_START) ) + (refIndex != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX && alignmentStart == SAMRecord.NO_ALIGNMENT_START) ) throw new ReviewedStingException("Invalid alignment start for artificial read, start = " + alignmentStart); GATKSAMRecord record = new GATKSAMRecord(header); record.setReadName(name); @@ -197,6 +201,37 @@ public static GATKSAMRecord createArtificialRead( SAMFileHeader header, String n return rec; } + public final static List createPair(SAMFileHeader header, String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { + SAMRecord left = ArtificialSAMUtils.createArtificialRead(header, name, 0, leftStart, readLen); + SAMRecord right = ArtificialSAMUtils.createArtificialRead(header, name, 0, rightStart, readLen); + + left.setReadPairedFlag(true); + right.setReadPairedFlag(true); + + left.setProperPairFlag(true); + right.setProperPairFlag(true); + + left.setFirstOfPairFlag(leftIsFirst); + right.setFirstOfPairFlag(! leftIsFirst); + + left.setReadNegativeStrandFlag(leftIsNegative); + left.setMateNegativeStrandFlag(!leftIsNegative); + right.setReadNegativeStrandFlag(!leftIsNegative); + right.setMateNegativeStrandFlag(leftIsNegative); + + left.setMateAlignmentStart(right.getAlignmentStart()); + right.setMateAlignmentStart(left.getAlignmentStart()); + + left.setMateReferenceIndex(0); + right.setMateReferenceIndex(0); + + int isize = rightStart + readLen - leftStart; + left.setInferredInsertSize(isize); + right.setInferredInsertSize(-isize); + + return Arrays.asList(left, right); + } + /** * create an iterator containing the specified read piles * @@ -258,4 +293,52 @@ public static StingSAMIterator queryReadIterator( int startingChr, int endingChr return new ArtificialSAMQueryIterator(startingChr, endingChr, readCount, unmappedReadCount, header); } + + private final static int ranIntInclusive(Random ran, int start, int stop) { + final int range = stop - start; + return ran.nextInt(range) + start; + } + + /** + * Creates a read backed pileup containing up to pileupSize reads at refID 0 from header at loc with + * reads created that have readLen bases. Pairs are sampled from a gaussian distribution with mean insert + * size of insertSize and variation of insertSize / 10. The first read will be in the pileup, and the second + * may be, depending on where this sampled insertSize puts it. + * @param header + * @param loc + * @param readLen + * @param insertSize + * @param pileupSize + * @return + */ + public static ReadBackedPileup createReadBackedPileup(final SAMFileHeader header, final GenomeLoc loc, final int readLen, final int insertSize, final int pileupSize) { + final Random ran = new Random(); + final boolean leftIsFirst = true; + final boolean leftIsNegative = false; + final int insertSizeVariation = insertSize / 10; + final int pos = loc.getStart(); + + final List pileupElements = new ArrayList(); + for ( int i = 0; i < pileupSize / 2; i++ ) { + final String readName = "read" + i; + final int leftStart = ranIntInclusive(ran, 1, pos); + final int fragmentSize = (int)(ran.nextGaussian() * insertSizeVariation + insertSize); + final int rightStart = leftStart + fragmentSize - readLen; + + if ( rightStart <= 0 ) continue; + + List pair = createPair(header, readName, readLen, leftStart, rightStart, leftIsFirst, leftIsNegative); + final SAMRecord left = pair.get(0); + final SAMRecord right = pair.get(1); + + pileupElements.add(new PileupElement(left, pos - leftStart)); + + if ( pos >= right.getAlignmentStart() && pos <= right.getAlignmentEnd() ) { + pileupElements.add(new PileupElement(right, pos - rightStart)); + } + } + + Collections.sort(pileupElements); + return new ReadBackedPileupImpl(loc, pileupElements); + } } 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 f093e6539c..f8e4927ed8 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/ReadUtils.java @@ -214,54 +214,6 @@ public static OverlapType readPairBaseOverlapType(final SAMRecord rec, long base return state; } - /** - * s1 e1 - * |-----------------------> [record in hand] - * s2 - * <-----------------------| - * - * s1, e1, and s2 are all in the record. Assuming that s1 < s2 (we are the left most read), - * we can compute whether we overlap with our mate by seeing if s2 <= e1 or no. If e1 < - * s2 then we known that we cannot over. - * - * If we are looking at the right read - * - * s1 - * |-----------------------> - * s2 e2 - * <-----------------------| [record in hand] - * - * we know the position of s1 and s2, but we don't know e1, so we cannot tell if we - * overlap with our mate or not, so in this case we return MAYBE. - * - * Note that if rec has an unmapped mate or is unpaired we certainly know the answer - * - * @param rec - * @return - */ - public static ReadOverlapsMateType readMightOverlapMate(final SAMRecord rec) { - if ( ! rec.getReadPairedFlag() || rec.getMateUnmappedFlag() ) { - return ReadOverlapsMateType.NO; - } else { // read is actually paired - final int recStart = rec.getAlignmentStart(); - final int recEnd = rec.getAlignmentEnd(); - final int mateStart = rec.getMateAlignmentStart(); - - if ( recStart < mateStart ) { - // we are the left most read - return mateStart <= recEnd ? ReadOverlapsMateType.LEFT_YES: ReadOverlapsMateType.NO; - } else if ( recStart == mateStart ) { - // we are the left most read - return ReadOverlapsMateType.SAME_START; - } else { - // we are the right most read, so we cannot tell - return ReadOverlapsMateType.RIGHT_MAYBE; - } - } - } - - public enum ReadOverlapsMateType { LEFT_YES, NO, SAME_START, RIGHT_MAYBE } - private static Pair getAdaptorBoundaries(SAMRecord rec, int adaptorLength) { int isize = rec.getInferredInsertSize(); if ( isize == 0 ) diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java index 6a5378d46e..8b797def45 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupBenchmark.java @@ -53,38 +53,12 @@ public class FragmentPileupBenchmark extends SimpleBenchmark { SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); GenomeLocParser genomeLocParser; genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); - final int pos = 50; - GenomeLoc loc = genomeLocParser.createGenomeLoc("chr1", pos); - - final Random ran = new Random(); + GenomeLoc loc = genomeLocParser.createGenomeLoc("chr1", 50); final int readLen = 100; - final boolean leftIsFirst = true; - final boolean leftIsNegative = false; - final int insertSizeVariation = insertSize / 10; for ( int pileupN = 0; pileupN < nPileupsToGenerate; pileupN++ ) { - List pileupElements = new ArrayList(); - for ( int i = 0; i < pileupSize / 2; i++ ) { - final String readName = "read" + i; - final int leftStart = new Random().nextInt(49) + 1; - final int fragmentSize = (int)(ran.nextGaussian() * insertSizeVariation + insertSize); - final int rightStart = leftStart + fragmentSize - readLen; - - if ( rightStart <= 0 ) continue; - - List pair = FragmentPileupUnitTest.createPair(header, readName, readLen, leftStart, rightStart, leftIsFirst, leftIsNegative); - SAMRecord left = pair.get(0); - SAMRecord right = pair.get(1); - - pileupElements.add(new PileupElement(left, pos - leftStart)); - - if ( pos >= right.getAlignmentStart() && pos <= right.getAlignmentEnd() ) { - pileupElements.add(new PileupElement(right, pos - rightStart)); - } - } - - Collections.sort(pileupElements); - pileups.add(new ReadBackedPileupImpl(loc, pileupElements)); + ReadBackedPileup rbp = ArtificialSAMUtils.createReadBackedPileup(header, loc, readLen, insertSize, pileupSize); + pileups.add(rbp); } } @@ -100,18 +74,10 @@ public void timeOriginal(int rep) { run(rep, FragmentPileup.FragmentMatchingAlgorithm.ORIGINAL); } - public void timeFullOverlapPotential(int rep) { - run(rep, FragmentPileup.FragmentMatchingAlgorithm.FAST_V1); - } - public void timeSkipNonOverlapping(int rep) { run(rep, FragmentPileup.FragmentMatchingAlgorithm.skipNonOverlapping); } - public void timeSkipNonOverlappingNotLazy(int rep) { - run(rep, FragmentPileup.FragmentMatchingAlgorithm.skipNonOverlappingNotLazy); - } - public static void main(String[] args) { CaliperMain.main(FragmentPileupBenchmark.class, args); } diff --git a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java index be4ecc0c64..c42c01c656 100644 --- a/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/pileup/FragmentPileupUnitTest.java @@ -43,44 +43,13 @@ public class FragmentPileupUnitTest extends BaseTest { private static SAMFileHeader header; - public final static List createPair(SAMFileHeader header, String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { - SAMRecord left = ArtificialSAMUtils.createArtificialRead(header, name, 0, leftStart, readLen); - SAMRecord right = ArtificialSAMUtils.createArtificialRead(header, name, 0, rightStart, readLen); - - left.setReadPairedFlag(true); - right.setReadPairedFlag(true); - - left.setProperPairFlag(true); - right.setProperPairFlag(true); - - left.setFirstOfPairFlag(leftIsFirst); - right.setFirstOfPairFlag(! leftIsFirst); - - left.setReadNegativeStrandFlag(leftIsNegative); - left.setMateNegativeStrandFlag(!leftIsNegative); - right.setReadNegativeStrandFlag(!leftIsNegative); - right.setMateNegativeStrandFlag(leftIsNegative); - - left.setMateAlignmentStart(right.getAlignmentStart()); - right.setMateAlignmentStart(left.getAlignmentStart()); - - left.setMateReferenceIndex(0); - right.setMateReferenceIndex(0); - - int isize = rightStart + readLen - leftStart; - left.setInferredInsertSize(isize); - right.setInferredInsertSize(-isize); - - return Arrays.asList(left, right); - } - private class FragmentPileupTest extends TestDataProvider { List states = new ArrayList(); private FragmentPileupTest(String name, int readLen, int leftStart, int rightStart, boolean leftIsFirst, boolean leftIsNegative) { super(FragmentPileupTest.class, String.format("%s-leftIsFirst:%b-leftIsNegative:%b", name, leftIsFirst, leftIsNegative)); - List pair = createPair(header, "readpair", readLen, leftStart, rightStart, leftIsFirst, leftIsNegative); + List pair = ArtificialSAMUtils.createPair(header, "readpair", readLen, leftStart, rightStart, leftIsFirst, leftIsNegative); SAMRecord left = pair.get(0); SAMRecord right = pair.get(1); From 89a581a66f2c6ae1437301ffa6ab7243ebc5ca32 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Fri, 14 Oct 2011 12:06:41 -0400 Subject: [PATCH 283/363] Added ability to specify arguments in files via -args/--arg_file Pushing back downsample and read filter args so they show up in getApproximateCommandLineArgs() --- .../sting/commandline/ArgumentMatch.java | 83 ++++++++-------- .../sting/commandline/ArgumentMatchSite.java | 76 ++++++++++++++ .../commandline/ArgumentMatchSource.java | 98 +++++++++++++++++++ .../commandline/ArgumentMatchSourceType.java | 32 ++++++ .../sting/commandline/ArgumentMatches.java | 16 +-- .../sting/commandline/CommandLineProgram.java | 14 +-- .../sting/commandline/ParsingEngine.java | 93 +++++++++++++++--- .../sting/commandline/ParsingMethod.java | 6 +- .../sting/gatk/GenomeAnalysisEngine.java | 31 ++++-- .../arguments/GATKArgumentCollection.java | 16 ++- .../sting/utils/help/HelpFormatter.java | 33 +++++-- .../ArgumentMatchSiteUnitTest.java | 79 +++++++++++++++ .../ArgumentMatchSourceUnitTest.java | 98 +++++++++++++++++++ .../commandline/ParsingEngineUnitTest.java | 26 +++++ 14 files changed, 610 insertions(+), 91 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSite.java create mode 100644 public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSource.java create mode 100644 public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSourceType.java create mode 100644 public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSiteUnitTest.java create mode 100644 public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSourceUnitTest.java diff --git a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatch.java b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatch.java index 351583c077..c0823e5c5f 100755 --- a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatch.java +++ b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatch.java @@ -46,7 +46,7 @@ public class ArgumentMatch implements Iterable { /** * Maps indices of command line arguments to values paired with that argument. */ - public final SortedMap> indices = new TreeMap>(); + public final SortedMap> sites = new TreeMap>(); /** * An ordered, freeform collection of tags. @@ -72,32 +72,32 @@ private ArgumentMatch(final String label, final ArgumentDefinition definition) { } /** - * A simple way of indicating that an argument with the given label and definition exists at this index. + * A simple way of indicating that an argument with the given label and definition exists at this site. * @param label Label of the argument match. Must not be null. * @param definition The associated definition, if one exists. May be null. - * @param index Position of the argument. Must not be null. + * @param site Position of the argument. Must not be null. * @param tags ordered freeform text tags associated with this argument. */ - public ArgumentMatch(final String label, final ArgumentDefinition definition, final int index, final Tags tags) { - this( label, definition, index, null, tags ); + public ArgumentMatch(final String label, final ArgumentDefinition definition, final ArgumentMatchSite site, final Tags tags) { + this( label, definition, site, null, tags ); } /** - * A simple way of indicating that an argument with the given label and definition exists at this index. + * A simple way of indicating that an argument with the given label and definition exists at this site. * @param label Label of the argument match. Must not be null. * @param definition The associated definition, if one exists. May be null. - * @param index Position of the argument. Must not be null. + * @param site Position of the argument. Must not be null. * @param value Value for the argument at this position. * @param tags ordered freeform text tags associated with this argument. */ - private ArgumentMatch(final String label, final ArgumentDefinition definition, final int index, final String value, final Tags tags) { + private ArgumentMatch(final String label, final ArgumentDefinition definition, final ArgumentMatchSite site, final String value, final Tags tags) { this.label = label; this.definition = definition; ArrayList values = new ArrayList(); if( value != null ) values.add(value); - indices.put(index,values ); + sites.put(site,values ); this.tags = tags; } @@ -117,7 +117,7 @@ public boolean equals(Object other) { ArgumentMatch otherArgumentMatch = (ArgumentMatch)other; return this.definition.equals(otherArgumentMatch.definition) && this.label.equals(otherArgumentMatch.label) && - this.indices.equals(otherArgumentMatch.indices) && + this.sites.equals(otherArgumentMatch.sites) && this.tags.equals(otherArgumentMatch.tags); } @@ -129,16 +129,17 @@ public boolean equals(Object other) { * @param key Key which specifies the transform. * @return A variant of this ArgumentMatch with all keys transformed. */ + @SuppressWarnings("unchecked") ArgumentMatch transform(Multiplexer multiplexer, Object key) { - SortedMap> newIndices = new TreeMap>(); - for(Map.Entry> index: indices.entrySet()) { + SortedMap> newIndices = new TreeMap>(); + for(Map.Entry> site: sites.entrySet()) { List newEntries = new ArrayList(); - for(String entry: index.getValue()) + for(String entry: site.getValue()) newEntries.add(multiplexer.transformArgument(key,entry)); - newIndices.put(index.getKey(),newEntries); + newIndices.put(site.getKey(),newEntries); } ArgumentMatch newArgumentMatch = new ArgumentMatch(label,definition); - newArgumentMatch.indices.putAll(newIndices); + newArgumentMatch.sites.putAll(newIndices); return newArgumentMatch; } @@ -157,9 +158,9 @@ public String toString() { public Iterator iterator() { return new Iterator() { /** - * Iterate over each the available index. + * Iterate over each the available site. */ - private Iterator indexIterator = null; + private Iterator siteIterator = null; /** * Iterate over each available token. @@ -167,9 +168,9 @@ public Iterator iterator() { private Iterator tokenIterator = null; /** - * The next index to return. Null if none remain. + * The next site to return. Null if none remain. */ - Integer nextIndex = null; + ArgumentMatchSite nextSite = null; /** * The next token to return. Null if none remain. @@ -177,7 +178,7 @@ public Iterator iterator() { String nextToken = null; { - indexIterator = indices.keySet().iterator(); + siteIterator = sites.keySet().iterator(); prepareNext(); } @@ -186,7 +187,7 @@ public Iterator iterator() { * @return True if there's another token waiting in the wings. False otherwise. */ public boolean hasNext() { - return nextToken != null; + return nextToken != null; } /** @@ -194,32 +195,32 @@ public boolean hasNext() { * @return The next ArgumentMatch in the series. Should never be null. */ public ArgumentMatch next() { - if( nextIndex == null || nextToken == null ) + if( nextSite == null || nextToken == null ) throw new IllegalStateException( "No more ArgumentMatches are available" ); - ArgumentMatch match = new ArgumentMatch( label, definition, nextIndex, nextToken, tags ); + ArgumentMatch match = new ArgumentMatch( label, definition, nextSite, nextToken, tags ); prepareNext(); return match; } /** * Initialize the next ArgumentMatch to return. If no ArgumentMatches are available, - * initialize nextIndex / nextToken to null. + * initialize nextSite / nextToken to null. */ private void prepareNext() { if( tokenIterator != null && tokenIterator.hasNext() ) { nextToken = tokenIterator.next(); } else { - nextIndex = null; + nextSite = null; nextToken = null; // Do a nested loop. While more data is present in the inner loop, grab that data. // Otherwise, troll the outer iterator looking for more data. - while( indexIterator.hasNext() ) { - nextIndex = indexIterator.next(); - if( indices.get(nextIndex) != null ) { - tokenIterator = indices.get(nextIndex).iterator(); + while( siteIterator.hasNext() ) { + nextSite = siteIterator.next(); + if( sites.get(nextSite) != null ) { + tokenIterator = sites.get(nextSite).iterator(); if( tokenIterator.hasNext() ) { nextToken = tokenIterator.next(); break; @@ -245,29 +246,29 @@ public void remove() { * @param other The other match to merge into. */ public void mergeInto( ArgumentMatch other ) { - indices.putAll(other.indices); + sites.putAll(other.sites); } /** * Associate a value with this merge maapping. - * @param index index of the command-line argument to which this value is mated. + * @param site site of the command-line argument to which this value is mated. * @param value Text representation of value to add. */ - public void addValue( int index, String value ) { - if( !indices.containsKey(index) || indices.get(index) == null ) - indices.put(index, new ArrayList() ); - indices.get(index).add(value); + public void addValue( ArgumentMatchSite site, String value ) { + if( !sites.containsKey(site) || sites.get(site) == null ) + sites.put(site, new ArrayList() ); + sites.get(site).add(value); } /** * Does this argument already have a value at the given site? * Arguments are only allowed to be single-valued per site, and * flags aren't allowed a value at all. - * @param index Index at which to check for values. + * @param site Site at which to check for values. * @return True if the argument has a value at the given site. False otherwise. */ - public boolean hasValueAtSite( int index ) { - return (indices.get(index) != null && indices.get(index).size() >= 1) || isArgumentFlag(); + public boolean hasValueAtSite( ArgumentMatchSite site ) { + return (sites.get(site) != null && sites.get(site).size() >= 1) || isArgumentFlag(); } /** @@ -276,9 +277,9 @@ public boolean hasValueAtSite( int index ) { */ public List values() { List values = new ArrayList(); - for( int index: indices.keySet() ) { - if( indices.get(index) != null ) - values.addAll(indices.get(index)); + for( ArgumentMatchSite site: sites.keySet() ) { + if( sites.get(site) != null ) + values.addAll(sites.get(site)); } return values; } diff --git a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSite.java b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSite.java new file mode 100644 index 0000000000..8a41201014 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSite.java @@ -0,0 +1,76 @@ +/* + * 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.commandline; + +/** + * Which source and the index within the source where an argument match was found. + */ +public class ArgumentMatchSite implements Comparable { + private final ArgumentMatchSource source; + private final int index; + + public ArgumentMatchSite(ArgumentMatchSource source, int index) { + this.source = source; + this.index = index; + } + + public ArgumentMatchSource getSource() { + return source; + } + + public int getIndex() { + return index; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ArgumentMatchSite that = (ArgumentMatchSite) o; + + return (index == that.index) && (source == null ? that.source == null : source.equals(that.source)); + } + + @Override + public int hashCode() { + int result = source != null ? source.hashCode() : 0; + // Generated by intellij. No other special reason to this implementation. -ks + result = 31 * result + index; + return result; + } + + @Override + public int compareTo(ArgumentMatchSite that) { + int comp = this.source.compareTo(that.source); + if (comp != 0) + return comp; + + // Both files are the same. + if (this.index == that.index) + return 0; + return this.index < that.index ? -1 : 1; + } +} diff --git a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSource.java b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSource.java new file mode 100644 index 0000000000..ed27000062 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSource.java @@ -0,0 +1,98 @@ +/* + * 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.commandline; + +import java.io.File; + +/** + * Where an argument match originated, via the commandline or a file. + */ +public class ArgumentMatchSource implements Comparable { + public static final ArgumentMatchSource COMMAND_LINE = new ArgumentMatchSource(ArgumentMatchSourceType.CommandLine, null); + + private final ArgumentMatchSourceType type; + private final File file; + + /** + * Creates an argument match source from the specified file. + * @param file File specifying the arguments. Must not be null. + */ + public ArgumentMatchSource(File file) { + this(ArgumentMatchSourceType.File, file); + } + + private ArgumentMatchSource(ArgumentMatchSourceType type, File file) { + if (type == ArgumentMatchSourceType.File && file == null) + throw new IllegalArgumentException("An argument match source of type File cannot have a null file."); + this.type = type; + this.file = file; + } + + public ArgumentMatchSourceType getType() { + return type; + } + + public File getFile() { + return file; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ArgumentMatchSource that = (ArgumentMatchSource) o; + + return (type == that.type) && (file == null ? that.file == null : file.equals(that.file)); + } + + @Override + public int hashCode() { + int result = type != null ? type.hashCode() : 0; + result = 31 * result + (file != null ? file.hashCode() : 0); + return result; + } + + /** + * Compares two sources, putting the command line first, then files. + */ + @Override + public int compareTo(ArgumentMatchSource that) { + int comp = this.type.compareTo(that.type); + if (comp != 0) + return comp; + + File f1 = this.file; + File f2 = that.file; + + if ((f1 == null) ^ (f2 == null)) { + // If one of the files is null and the other is not + // put the null file first + return f1 == null ? -1 : 1; + } + + return f1 == null ? 0 : f1.compareTo(f2); + } +} diff --git a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSourceType.java b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSourceType.java new file mode 100644 index 0000000000..3ff6e21d45 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatchSourceType.java @@ -0,0 +1,32 @@ +/* + * 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.commandline; + +/** + * Type of where an argument match originated, via the commandline or a file. + */ +public enum ArgumentMatchSourceType { + CommandLine, File +} diff --git a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatches.java b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatches.java index 52d3b8232b..3da28c4209 100755 --- a/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatches.java +++ b/public/java/src/org/broadinstitute/sting/commandline/ArgumentMatches.java @@ -37,7 +37,7 @@ public class ArgumentMatches implements Iterable { * Collection matches from argument definition to argument value. * Package protected access is deliberate. */ - Map argumentMatches = new TreeMap(); + Map argumentMatches = new TreeMap(); /** * Provide a place to put command-line argument values that don't seem to belong to @@ -80,7 +80,7 @@ public int size() { * @param site Site at which to check. * @return True if the site has a match. False otherwise. */ - boolean hasMatch( int site ) { + boolean hasMatch( ArgumentMatchSite site ) { return argumentMatches.containsKey( site ); } @@ -90,7 +90,7 @@ boolean hasMatch( int site ) { * @return The match present at the given site. * @throws IllegalArgumentException if site does not contain a match. */ - ArgumentMatch getMatch( int site ) { + ArgumentMatch getMatch( ArgumentMatchSite site ) { if( !argumentMatches.containsKey(site) ) throw new IllegalArgumentException( "Site does not contain an argument: " + site ); return argumentMatches.get(site); @@ -107,6 +107,7 @@ boolean hasMatch( ArgumentDefinition definition ) { /** * Return all argument matches of this source. + * @param parsingEngine Parsing engine. * @param argumentSource Argument source to match. * @return List of all matches. */ @@ -167,6 +168,7 @@ ArgumentMatches findUnmatched() { * TODO: Generify this. * @param multiplexer Multiplexer that controls the transformation process. * @param key Key which specifies the transform. + * @return new argument matches. */ ArgumentMatches transform(Multiplexer multiplexer, Object key) { ArgumentMatches newArgumentMatches = new ArgumentMatches(); @@ -187,15 +189,15 @@ void mergeInto( ArgumentMatch match ) { for( ArgumentMatch argumentMatch: getUniqueMatches() ) { if( argumentMatch.definition == match.definition && argumentMatch.tags.equals(match.tags) ) { argumentMatch.mergeInto( match ); - for( int index: match.indices.keySet() ) - argumentMatches.put( index, argumentMatch ); + for( ArgumentMatchSite site: match.sites.keySet() ) + argumentMatches.put( site, argumentMatch ); definitionExists = true; } } if( !definitionExists ) { - for( int index: match.indices.keySet() ) - argumentMatches.put( index, match ); + for( ArgumentMatchSite site: match.sites.keySet() ) + argumentMatches.put( site, match ); } } diff --git a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java index d88e7030e9..bed1e710e6 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java +++ b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java @@ -35,10 +35,7 @@ import org.broadinstitute.sting.utils.help.HelpFormatter; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Locale; +import java.util.*; public abstract class CommandLineProgram { @@ -155,6 +152,7 @@ public static void start(CommandLineProgram clp, String[] args) throws Exception * * @param clp the command line program to execute * @param args the command line arguments passed in + * @param dryRun dry run * @throws Exception when an exception occurs */ @SuppressWarnings("unchecked") @@ -176,6 +174,8 @@ public static void start(CommandLineProgram clp, String[] args, boolean dryRun) ParsingEngine parser = clp.parser = new ParsingEngine(clp); parser.addArgumentSource(clp.getClass()); + Map> parsedArgs; + // process the args if (clp.canAddArgumentsDynamically()) { // if the command-line program can toss in extra args, fetch them and reparse the arguments. @@ -196,14 +196,14 @@ public static void start(CommandLineProgram clp, String[] args, boolean dryRun) Class[] argumentSources = clp.getArgumentSources(); for (Class argumentSource : argumentSources) parser.addArgumentSource(clp.getArgumentSourceName(argumentSource), argumentSource); - parser.parse(args); + parsedArgs = parser.parse(args); if (isHelpPresent(parser)) printHelpAndExit(clp, parser); if ( ! dryRun ) parser.validate(); } else { - parser.parse(args); + parsedArgs = parser.parse(args); if ( ! dryRun ) { if (isHelpPresent(parser)) @@ -230,7 +230,7 @@ public static void start(CommandLineProgram clp, String[] args, boolean dryRun) } // regardless of what happens next, generate the header information - HelpFormatter.generateHeaderInformation(clp.getApplicationDetails(), args); + HelpFormatter.generateHeaderInformation(clp.getApplicationDetails(), parsedArgs); // call the execute CommandLineProgram.result = clp.execute(); diff --git a/public/java/src/org/broadinstitute/sting/commandline/ParsingEngine.java b/public/java/src/org/broadinstitute/sting/commandline/ParsingEngine.java index fbf8c65168..c199603559 100755 --- a/public/java/src/org/broadinstitute/sting/commandline/ParsingEngine.java +++ b/public/java/src/org/broadinstitute/sting/commandline/ParsingEngine.java @@ -26,6 +26,7 @@ package org.broadinstitute.sting.commandline; import com.google.java.contract.Requires; +import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.classloader.JVMUtils; @@ -35,6 +36,8 @@ import org.broadinstitute.sting.utils.help.ApplicationDetails; import org.broadinstitute.sting.utils.help.HelpFormatter; +import java.io.File; +import java.io.IOException; import java.lang.reflect.Field; import java.util.*; @@ -100,6 +103,8 @@ public ParsingEngine( CommandLineProgram clp ) { if(clp != null) argumentTypeDescriptors.addAll(clp.getArgumentTypeDescriptors()); argumentTypeDescriptors.addAll(STANDARD_ARGUMENT_TYPE_DESCRIPTORS); + + addArgumentSource(ParsingEngineArgumentFiles.class); } /** @@ -148,21 +153,43 @@ public boolean isArgumentPresent( String argumentFullName ) { * command-line arguments to the arguments that are actually * required. * @param tokens Tokens passed on the command line. + * @return The parsed arguments by file. */ - public void parse( String[] tokens ) { + public SortedMap> parse( String[] tokens ) { argumentMatches = new ArgumentMatches(); + SortedMap> parsedArgs = new TreeMap>(); + + List cmdLineTokens = Arrays.asList(tokens); + parse(ArgumentMatchSource.COMMAND_LINE, cmdLineTokens, argumentMatches, parsedArgs); + + ParsingEngineArgumentFiles argumentFiles = new ParsingEngineArgumentFiles(); - int lastArgumentMatchSite = -1; + // Load the arguments ONLY into the argument files. + // Validation may optionally run on the rest of the arguments. + loadArgumentsIntoObject(argumentFiles); - for( int i = 0; i < tokens.length; i++ ) { - String token = tokens[i]; + for (File file: argumentFiles.files) { + List fileTokens = getArguments(file); + parse(new ArgumentMatchSource(file), fileTokens, argumentMatches, parsedArgs); + } + + return parsedArgs; + } + + private void parse(ArgumentMatchSource matchSource, List tokens, + ArgumentMatches argumentMatches, SortedMap> parsedArgs) { + ArgumentMatchSite lastArgumentMatchSite = new ArgumentMatchSite(matchSource, -1); + + int i = 0; + for (String token: tokens) { // If the token is of argument form, parse it into its own argument match. // Otherwise, pair it with the most recently used argument discovered. + ArgumentMatchSite site = new ArgumentMatchSite(matchSource, i); if( isArgumentForm(token) ) { - ArgumentMatch argumentMatch = parseArgument( token, i ); + ArgumentMatch argumentMatch = parseArgument( token, site ); if( argumentMatch != null ) { argumentMatches.mergeInto( argumentMatch ); - lastArgumentMatchSite = i; + lastArgumentMatchSite = site; } } else { @@ -170,10 +197,31 @@ public void parse( String[] tokens ) { !argumentMatches.getMatch(lastArgumentMatchSite).hasValueAtSite(lastArgumentMatchSite)) argumentMatches.getMatch(lastArgumentMatchSite).addValue( lastArgumentMatchSite, token ); else - argumentMatches.MissingArgument.addValue( i, token ); + argumentMatches.MissingArgument.addValue( site, token ); } + i++; } + + parsedArgs.put(matchSource, tokens); + } + + private List getArguments(File file) { + try { + if (file.getAbsolutePath().endsWith(".list")) { + return getListArguments(file); + } + } catch (IOException e) { + throw new UserException.CouldNotReadInputFile(file, e); + } + throw new UserException.CouldNotReadInputFile(file, "file extension is not .list"); + } + + private List getListArguments(File file) throws IOException { + ArrayList argsList = new ArrayList(); + for (String line: FileUtils.readLines(file)) + argsList.addAll(Arrays.asList(Utils.escapeExpressions(line))); + return argsList; } public enum ValidationType { MissingRequiredArgument, @@ -494,7 +542,7 @@ private boolean isArgumentForm( String token ) { * @param position The position of the token in question. * @return ArgumentMatch associated with this token, or null if no match exists. */ - private ArgumentMatch parseArgument( String token, int position ) { + private ArgumentMatch parseArgument( String token, ArgumentMatchSite position ) { if( !isArgumentForm(token) ) throw new IllegalArgumentException( "Token is not recognizable as an argument: " + token ); @@ -579,9 +627,21 @@ public UnmatchedArgumentException( ArgumentMatch invalidValues ) { private static String formatArguments( ArgumentMatch invalidValues ) { StringBuilder sb = new StringBuilder(); - for( int index: invalidValues.indices.keySet() ) - for( String value: invalidValues.indices.get(index) ) { - sb.append( String.format("%nInvalid argument value '%s' at position %d.", value, index) ); + for( ArgumentMatchSite site: invalidValues.sites.keySet() ) + for( String value: invalidValues.sites.get(site) ) { + switch (site.getSource().getType()) { + case CommandLine: + sb.append( String.format("%nInvalid argument value '%s' at position %d.", + value, site.getIndex()) ); + break; + case File: + sb.append( String.format("%nInvalid argument value '%s' in file %s at position %d.", + value, site.getSource().getFile().getAbsolutePath(), site.getIndex()) ); + break; + default: + throw new RuntimeException( String.format("Unexpected argument match source type: %s", + site.getSource().getType())); + } if(value != null && Utils.dupString(' ',value.length()).equals(value)) sb.append(" Please make sure any line continuation backslashes on your command line are not followed by whitespace."); } @@ -634,4 +694,13 @@ public UnknownEnumeratedValueException(ArgumentDefinition definition, String arg private static String formatArguments(ArgumentDefinition definition, String argumentPassed) { return String.format("Invalid value %s specified for argument %s; valid options are (%s).", argumentPassed, definition.fullName, Utils.join(",",definition.validOptions)); } -} \ No newline at end of file +} + +/** + * Container class to store the list of argument files. + * The files will be parsed after the command line arguments. + */ +class ParsingEngineArgumentFiles { + @Argument(fullName = "arg_file", shortName = "args", doc = "Reads arguments from the specified file", required = false) + public List files = new ArrayList(); +} diff --git a/public/java/src/org/broadinstitute/sting/commandline/ParsingMethod.java b/public/java/src/org/broadinstitute/sting/commandline/ParsingMethod.java index a070cb5a11..452309e896 100755 --- a/public/java/src/org/broadinstitute/sting/commandline/ParsingMethod.java +++ b/public/java/src/org/broadinstitute/sting/commandline/ParsingMethod.java @@ -68,7 +68,7 @@ public boolean matches( String token ) { * @return An argument match. Definition field will be populated if a match was found or * empty if no appropriate definition could be found. */ - public ArgumentMatch match( ArgumentDefinitions definitions, String token, int position ) { + public ArgumentMatch match( ArgumentDefinitions definitions, String token, ArgumentMatchSite position ) { // If the argument is valid, parse out the argument. Matcher matcher = pattern.matcher(token); @@ -102,9 +102,7 @@ public ArgumentMatch match( ArgumentDefinitions definitions, String token, int p // Try to find a matching argument. If found, label that as the match. If not found, add the argument // with a null definition. - ArgumentMatch argumentMatch = new ArgumentMatch(argument,argumentDefinition,position,tags); - - return argumentMatch; + return new ArgumentMatch(argument,argumentDefinition,position,tags); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 7bc3daa9a8..fb0dcc6cd1 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -220,12 +220,12 @@ public Object execute() { ShardStrategy shardStrategy = getShardStrategy(readsDataSource,microScheduler.getReference(),intervals); // execute the microscheduler, storing the results - Object result = microScheduler.execute(this.walker, shardStrategy); + return microScheduler.execute(this.walker, shardStrategy); //monitor.stop(); //logger.info(String.format("Maximum heap size consumed: %d",monitor.getMaxMemoryUsed())); - return result; + //return result; } /** @@ -296,10 +296,14 @@ protected DownsamplingMethod getDownsamplingMethod() { else if(WalkerManager.getDownsamplingMethod(walker) != null) method = WalkerManager.getDownsamplingMethod(walker); else - method = argCollection.getDefaultDownsamplingMethod(); + method = GATKArgumentCollection.getDefaultDownsamplingMethod(); return method; } + protected void setDownsamplingMethod(DownsamplingMethod method) { + argCollection.setDownsamplingMethod(method); + } + public BAQ.QualityMode getWalkerBAQQualityMode() { return WalkerManager.getBAQQualityMode(walker); } public BAQ.ApplicationTime getWalkerBAQApplicationTime() { return WalkerManager.getBAQApplicationTime(walker); } @@ -389,7 +393,9 @@ protected void validateSuppliedIntervals() { /** * Get the sharding strategy given a driving data source. * + * @param readsDataSource readsDataSource * @param drivingDataSource Data on which to shard. + * @param intervals intervals * @return the sharding strategy */ protected ShardStrategy getShardStrategy(SAMDataSource readsDataSource, ReferenceSequenceFile drivingDataSource, GenomeLocSortedSet intervals) { @@ -426,7 +432,7 @@ else if(walker instanceof ReadWalker || walker instanceof DuplicateWalker || wal return new MonolithicShardStrategy(getGenomeLocParser(), readsDataSource,shardType,region); } - ShardStrategy shardStrategy = null; + ShardStrategy shardStrategy; ShardStrategyFactory.SHATTER_STRATEGY shardType; long SHARD_SIZE = 100000L; @@ -435,6 +441,8 @@ else if(walker instanceof ReadWalker || walker instanceof DuplicateWalker || wal 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."); @@ -498,7 +506,8 @@ protected boolean flashbackData() { */ private void initializeTempDirectory() { File tempDir = new File(System.getProperty("java.io.tmpdir")); - tempDir.mkdirs(); + if (!tempDir.exists() && !tempDir.mkdirs()) + throw new UserException.BadTmpDir("Unable to create directory"); } /** @@ -729,6 +738,7 @@ public File getSourceFileForReaderID(final SAMReaderID id) { * @param reads Reads data source. * @param reference Reference data source. * @param rods a collection of the reference ordered data tracks + * @param manager manager */ private void validateSourcesAgainstReference(SAMDataSource reads, ReferenceSequenceFile reference, Collection rods, RMDTrackBuilder manager) { if ((reads.isEmpty() && (rods == null || rods.isEmpty())) || reference == null ) @@ -757,15 +767,22 @@ private void validateSourcesAgainstReference(SAMDataSource reads, ReferenceSeque /** * Gets a data source for the given set of reads. * + * @param argCollection arguments + * @param genomeLocParser parser + * @param refReader reader * @return A data source for the given set of reads. */ private SAMDataSource createReadsDataSource(GATKArgumentCollection argCollection, GenomeLocParser genomeLocParser, IndexedFastaSequenceFile refReader) { DownsamplingMethod method = getDownsamplingMethod(); + // Synchronize the method back into the collection so that it shows up when + // interrogating for the downsample method during command line recreation. + setDownsamplingMethod(method); + if ( getWalkerBAQApplicationTime() == BAQ.ApplicationTime.FORBIDDEN && argCollection.BAQMode != BAQ.CalculationMode.OFF) throw new UserException.BadArgumentValue("baq", "Walker cannot accept BAQ'd base qualities, and yet BAQ mode " + argCollection.BAQMode + " was requested."); - SAMDataSource dataSource = new SAMDataSource( + return new SAMDataSource( samReaderIDs, genomeLocParser, argCollection.useOriginalBaseQualities, @@ -781,14 +798,12 @@ private SAMDataSource createReadsDataSource(GATKArgumentCollection argCollection refReader, argCollection.defaultBaseQualities, !argCollection.disableLowMemorySharding); - return dataSource; } /** * Opens a reference sequence file paired with an index. Only public for testing purposes * * @param refFile Handle to a reference sequence file. Non-null. - * @return A thread-safe file wrapper. */ public void setReferenceDataSource(File refFile) { this.referenceDataSource = new ReferenceDataSource(refFile); 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 486868dc2b..18e71bc2bb 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -135,8 +135,8 @@ public GATKArgumentCollection() { /** * Gets the downsampling method explicitly specified by the user. If the user didn't specify - * a default downsampling mechanism, return null. - * @return The explicitly specified downsampling mechanism, or null if none exists. + * a default downsampling mechanism, return the default. + * @return The explicitly specified downsampling mechanism, or the default if none exists. */ public DownsamplingMethod getDownsamplingMethod() { if(downsamplingType == null && downsampleFraction == null && downsampleCoverage == null) @@ -146,6 +146,18 @@ public DownsamplingMethod getDownsamplingMethod() { return new DownsamplingMethod(downsamplingType,downsampleCoverage,downsampleFraction); } + /** + * Set the downsampling method stored in the argument collection so that it is read back out when interrogating the command line arguments. + * @param method The downsampling mechanism. + */ + public void setDownsamplingMethod(DownsamplingMethod method) { + if (method == null) + throw new IllegalArgumentException("method is null"); + downsamplingType = method.type; + downsampleCoverage = method.toCoverage; + downsampleFraction = method.toFraction; + } + // -------------------------------------------------------------------------------------------------------------- // // BAQ arguments diff --git a/public/java/src/org/broadinstitute/sting/utils/help/HelpFormatter.java b/public/java/src/org/broadinstitute/sting/utils/help/HelpFormatter.java index a9d71ef986..25ef8ccd2d 100755 --- a/public/java/src/org/broadinstitute/sting/utils/help/HelpFormatter.java +++ b/public/java/src/org/broadinstitute/sting/utils/help/HelpFormatter.java @@ -29,6 +29,7 @@ import org.broadinstitute.sting.commandline.ArgumentDefinition; import org.broadinstitute.sting.commandline.ArgumentDefinitionGroup; import org.broadinstitute.sting.commandline.ArgumentDefinitions; +import org.broadinstitute.sting.commandline.ArgumentMatchSource; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.text.TextFormattingUtils; @@ -47,6 +48,7 @@ public class HelpFormatter { /** * Prints the help, given a collection of argument definitions. + * @param applicationDetails Application details * @param argumentDefinitions Argument definitions for which help should be printed. */ public void printHelp( ApplicationDetails applicationDetails, ArgumentDefinitions argumentDefinitions ) { @@ -233,7 +235,7 @@ private int findLongestArgumentCallingInfo( Collection argum private List prepareArgumentGroups( ArgumentDefinitions argumentDefinitions ) { // Sort the list of argument definitions according to how they should be shown. // Put the sorted results into a new cloned data structure. - Comparator definitionComparator = new Comparator() { + Comparator definitionComparator = new Comparator() { public int compare( ArgumentDefinition lhs, ArgumentDefinition rhs ) { if( lhs.required && rhs.required ) return 0; if( lhs.required ) return -1; @@ -242,15 +244,15 @@ public int compare( ArgumentDefinition lhs, ArgumentDefinition rhs ) { } }; - List argumentGroups = new ArrayList(); + List argumentGroups = new ArrayList(); for( ArgumentDefinitionGroup argumentGroup: argumentDefinitions.getArgumentDefinitionGroups() ) { - List sortedDefinitions = new ArrayList( argumentGroup.argumentDefinitions ); + List sortedDefinitions = new ArrayList( argumentGroup.argumentDefinitions ); Collections.sort( sortedDefinitions, definitionComparator ); argumentGroups.add( new ArgumentDefinitionGroup(argumentGroup.groupName,sortedDefinitions) ); } // Sort the argument groups themselves with main arguments first, followed by plugins sorted in name order. - Comparator groupComparator = new Comparator() { + Comparator groupComparator = new Comparator() { public int compare( ArgumentDefinitionGroup lhs, ArgumentDefinitionGroup rhs ) { if( lhs.groupName == null && rhs.groupName == null ) return 0; if( lhs.groupName == null ) return -1; @@ -271,9 +273,9 @@ public int compare( ArgumentDefinitionGroup lhs, ArgumentDefinitionGroup rhs ) { * Generate a standard header for the logger * * @param applicationDetails details of the application to run. - * @param args the command line arguments passed in + * @param parsedArgs the command line arguments passed in */ - public static void generateHeaderInformation(ApplicationDetails applicationDetails, String[] args) { + public static void generateHeaderInformation(ApplicationDetails applicationDetails, Map> parsedArgs) { DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); java.util.Date date = new java.util.Date(); @@ -283,11 +285,22 @@ public static void generateHeaderInformation(ApplicationDetails applicationDetai logger.info(barrier); for (String headerLine : applicationDetails.applicationHeader) logger.info(headerLine); - String output = ""; - for (String str : args) { - output = output + str + " "; + logger.debug("Current directory: " + System.getProperty("user.dir")); + for (Map.Entry> entry: parsedArgs.entrySet()) { + ArgumentMatchSource matchSource = entry.getKey(); + final String sourceName; + switch (matchSource.getType()) { + case CommandLine: sourceName = "Program"; break; + case File: sourceName = matchSource.getFile().getPath(); break; + default: throw new RuntimeException("Unexpected argument match source type: " + matchSource.getType()); + } + + String output = sourceName + " Args:"; + for (String str : entry.getValue()) { + output = output + " " + str; + } + logger.info(output); } - logger.info("Program Args: " + output); logger.info("Date/Time: " + dateFormat.format(date)); logger.info(barrier); diff --git a/public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSiteUnitTest.java b/public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSiteUnitTest.java new file mode 100644 index 0000000000..99d6b88f3a --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSiteUnitTest.java @@ -0,0 +1,79 @@ +/* + * 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.commandline; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; + +public class ArgumentMatchSiteUnitTest { + @Test + public void testCommandLine() { + ArgumentMatchSite site = new ArgumentMatchSite(ArgumentMatchSource.COMMAND_LINE, 1); + Assert.assertEquals(site.getSource(), ArgumentMatchSource.COMMAND_LINE); + Assert.assertEquals(site.getIndex(), 1); + } + + @Test + public void testFile() { + ArgumentMatchSource source = new ArgumentMatchSource(new File("test")); + ArgumentMatchSite site = new ArgumentMatchSite(source, 1); + Assert.assertEquals(site.getSource(), source); + Assert.assertEquals(site.getIndex(), 1); + } + + @Test + public void testEquals() { + ArgumentMatchSource cmdLine = ArgumentMatchSource.COMMAND_LINE; + ArgumentMatchSite site1 = new ArgumentMatchSite(cmdLine, 1); + ArgumentMatchSite site2 = new ArgumentMatchSite(cmdLine, 2); + + Assert.assertFalse(site1.equals(null)); + + Assert.assertTrue(site1.equals(site1)); + Assert.assertFalse(site1.equals(site2)); + + Assert.assertFalse(site2.equals(site1)); + Assert.assertTrue(site2.equals(site2)); + } + + @Test + public void testCompareTo() { + ArgumentMatchSource cmdLine = ArgumentMatchSource.COMMAND_LINE; + ArgumentMatchSite site1 = new ArgumentMatchSite(cmdLine, 1); + ArgumentMatchSite site2 = new ArgumentMatchSite(cmdLine, 2); + + Assert.assertTrue(site1.compareTo(site1) == 0); + Assert.assertTrue(site1.compareTo(site2) < 0); + Assert.assertTrue(site2.compareTo(site1) > 0); + Assert.assertTrue(site2.compareTo(site2) == 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testCompareToNull() { + new ArgumentMatchSite(ArgumentMatchSource.COMMAND_LINE, 0).compareTo(null); + } +} diff --git a/public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSourceUnitTest.java new file mode 100644 index 0000000000..4bc7eb822a --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/commandline/ArgumentMatchSourceUnitTest.java @@ -0,0 +1,98 @@ +/* + * 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.commandline; + +import org.broadinstitute.sting.BaseTest; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; + +public class ArgumentMatchSourceUnitTest extends BaseTest { + @Test + public void testCommandLine() { + ArgumentMatchSource source = ArgumentMatchSource.COMMAND_LINE; + Assert.assertEquals(source.getType(), ArgumentMatchSourceType.CommandLine); + Assert.assertNull(source.getFile()); + } + + @Test + public void testFile() { + File f = new File("test"); + ArgumentMatchSource source = new ArgumentMatchSource(f); + Assert.assertEquals(source.getType(), ArgumentMatchSourceType.File); + Assert.assertEquals(source.getFile(), f); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullFile() { + new ArgumentMatchSource(null); + } + + @Test + public void testEquals() { + ArgumentMatchSource cmdLine = ArgumentMatchSource.COMMAND_LINE; + ArgumentMatchSource fileA = new ArgumentMatchSource(new File("a")); + ArgumentMatchSource fileB = new ArgumentMatchSource(new File("b")); + + Assert.assertFalse(cmdLine.equals(null)); + + Assert.assertTrue(cmdLine.equals(cmdLine)); + Assert.assertFalse(cmdLine.equals(fileA)); + Assert.assertFalse(cmdLine.equals(fileB)); + + Assert.assertFalse(fileA.equals(cmdLine)); + Assert.assertTrue(fileA.equals(fileA)); + Assert.assertFalse(fileA.equals(fileB)); + + Assert.assertFalse(fileB.equals(cmdLine)); + Assert.assertFalse(fileB.equals(fileA)); + Assert.assertTrue(fileB.equals(fileB)); + } + + @Test + public void testCompareTo() { + ArgumentMatchSource cmdLine = ArgumentMatchSource.COMMAND_LINE; + ArgumentMatchSource fileA = new ArgumentMatchSource(new File("a")); + ArgumentMatchSource fileB = new ArgumentMatchSource(new File("b")); + + Assert.assertTrue(cmdLine.compareTo(cmdLine) == 0); + Assert.assertTrue(cmdLine.compareTo(fileA) < 0); + Assert.assertTrue(cmdLine.compareTo(fileB) < 0); + + Assert.assertTrue(fileA.compareTo(cmdLine) > 0); + Assert.assertTrue(fileA.compareTo(fileA) == 0); + Assert.assertTrue(fileA.compareTo(fileB) < 0); + + Assert.assertTrue(fileB.compareTo(cmdLine) > 0); + Assert.assertTrue(fileB.compareTo(fileA) > 0); + Assert.assertTrue(fileB.compareTo(fileB) == 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testCompareToNull() { + ArgumentMatchSource.COMMAND_LINE.compareTo(null); + } +} diff --git a/public/java/test/org/broadinstitute/sting/commandline/ParsingEngineUnitTest.java b/public/java/test/org/broadinstitute/sting/commandline/ParsingEngineUnitTest.java index f04731214d..87f0e6ff05 100755 --- a/public/java/test/org/broadinstitute/sting/commandline/ParsingEngineUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/commandline/ParsingEngineUnitTest.java @@ -25,6 +25,7 @@ package org.broadinstitute.sting.commandline; +import org.apache.commons.io.FileUtils; import org.broad.tribble.Feature; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.VariantContext; @@ -34,6 +35,8 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.io.File; +import java.io.IOException; import java.util.List; import java.util.EnumSet; /** @@ -493,6 +496,7 @@ public void correctDefaultArgNameTest() { Assert.assertNotNull(definition, "Invalid default argument name assigned"); } + @SuppressWarnings("unused") private class CamelCaseArgProvider { @Argument(doc="my arg") Integer myArg; @@ -507,6 +511,7 @@ public void booleanWithParameterTest() { parsingEngine.validate(); } + @SuppressWarnings("unused") private class BooleanArgProvider { @Argument(doc="my bool") boolean myBool; @@ -561,6 +566,7 @@ public void mutuallyExclusiveArgumentsTest() { parsingEngine.validate(); } + @SuppressWarnings("unused") private class MutuallyExclusiveArgProvider { @Argument(doc="foo",exclusiveOf="bar") Integer foo; @@ -618,6 +624,7 @@ public void multipleArgumentCollectionTest() { parsingEngine.addArgumentSource( MultipleArgumentCollectionProvider.class ); } + @SuppressWarnings("unused") private class MultipleArgumentCollectionProvider { @ArgumentCollection RequiredArgProvider rap1 = new RequiredArgProvider(); @@ -937,4 +944,23 @@ public void variantContextBindingTestDynamicTypingUnknownTribbleType() { VariantContextRodBindingArgProvider argProvider = new VariantContextRodBindingArgProvider(); parsingEngine.loadArgumentsIntoObject( argProvider ); } + + @Test + public void argumentListTest() throws IOException { + File argsFile = BaseTest.createTempFile("args.", ".list"); + try { + FileUtils.write(argsFile, "-I na12878.bam"); + final String[] commandLine = new String[] {"-args", argsFile.getPath()}; + parsingEngine.addArgumentSource(InputFileArgProvider.class); + parsingEngine.parse(commandLine); + parsingEngine.validate(); + + InputFileArgProvider argProvider = new InputFileArgProvider(); + parsingEngine.loadArgumentsIntoObject(argProvider); + + Assert.assertEquals(argProvider.inputFile, "na12878.bam", "Argument is not correctly initialized"); + } finally { + FileUtils.deleteQuietly(argsFile); + } + } } From fac9932938f1275015aa0b93a82fb9bd52dd391d Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Mon, 24 Oct 2011 15:49:02 -0400 Subject: [PATCH 284/363] Embedding gsalib source and queueJobReport R scripts in the dist and package jars. Moved gsalib and queueJobReport.R to embeddable namespaced locations. Updated packager dependencies/dir to add an @includes which filters the embedded fileset. RScriptExecutor can now JIT compiles the gsalib. RScriptExecutor uses ProcessController and sends the Rscript output to java's stdout when run under -l DEBUG. Refactored ProcessController and IOUtils from Queue to Sting Utils. Added more unit tests to ProcessController along with a utility class to hard stop OutputStreams at a specified byte count. Replaced uses of some IOUtils with Apache Commons IO. ShellJobRunner refactored to use direct ProcessController and now kills jobs on shutdown. Better QGraph responsiveness on shutdown by using Object.wait() instead of Thread.sleep(). --- build.xml | 138 +++-- .../sting/queue/util}/queueJobReport.R | 0 .../sting/utils/R}/gsalib/DESCRIPTION | 0 .../sting/utils/R}/gsalib/R/gsa.error.R | 0 .../sting/utils/R}/gsalib/R/gsa.getargs.R | 0 .../sting/utils/R}/gsalib/R/gsa.message.R | 0 .../sting/utils/R}/gsalib/R/gsa.plot.venn.R | 0 .../sting/utils/R}/gsalib/R/gsa.read.eval.R | 0 .../utils/R}/gsalib/R/gsa.read.gatkreport.R | 0 .../utils/R}/gsalib/R/gsa.read.squidmetrics.R | 0 .../sting/utils/R}/gsalib/R/gsa.read.vcf.R | 0 .../sting/utils/R}/gsalib/R/gsa.warn.R | 0 .../sting/utils/R}/gsalib/Read-and-delete-me | 0 .../utils/R}/gsalib/data/tearsheetdrop.jpg | Bin .../sting/utils/R}/gsalib/man/gsa.error.Rd | 0 .../sting/utils/R}/gsalib/man/gsa.getargs.Rd | 0 .../sting/utils/R}/gsalib/man/gsa.message.Rd | 0 .../utils/R}/gsalib/man/gsa.plot.venn.Rd | 0 .../utils/R}/gsalib/man/gsa.read.eval.Rd | 0 .../R}/gsalib/man/gsa.read.gatkreport.Rd | 0 .../R}/gsalib/man/gsa.read.squidmetrics.Rd | 0 .../sting/utils/R}/gsalib/man/gsa.read.vcf.Rd | 0 .../sting/utils/R}/gsalib/man/gsa.warn.Rd | 0 .../utils/R}/gsalib/man/gsalib-package.Rd | 0 .../sting/utils/R/RScriptExecutor.java | 129 ++++- .../sting/utils/R/RScriptLibrary.java | 59 ++ .../sting/utils/io/FileExtension.java | 36 ++ .../io/HardThresholdingOutputStream.java | 54 ++ .../sting/utils/io/IOUtils.java | 353 ++++++++++++ .../sting/utils/io/Resource.java | 53 ++ .../utils/runtime/CapturedStreamOutput.java | 133 +++++ .../utils/runtime/InputStreamSettings.java | 115 ++++ .../utils/runtime/OutputStreamSettings.java | 126 +++++ .../utils/runtime/ProcessController.java | 363 ++++++++++++ .../sting/utils/runtime/ProcessOutput.java | 56 ++ .../sting/utils/runtime/ProcessSettings.java | 140 +++++ .../sting/utils/runtime/StreamLocation.java | 32 ++ .../sting/utils/runtime/StreamOutput.java | 68 +++ .../sting/utils/R/RScriptLibraryUnitTest.java | 46 ++ .../sting/utils/io/IOUtilsUnitTest.java | 197 +++++++ .../runtime/ProcessControllerUnitTest.java | 517 ++++++++++++++++++ public/packages/CreatePackager.xsl | 20 +- public/packages/GATKEngine.xml | 2 + public/packages/Queue.xml | 4 + .../sting/queue/QCommandLine.scala | 26 +- .../sting/queue/QScriptManager.scala | 16 +- .../queue/engine/CommandLineJobRunner.scala | 5 +- .../sting/queue/engine/FunctionEdge.scala | 20 +- .../sting/queue/engine/InProcessRunner.scala | 6 +- .../sting/queue/engine/QGraph.scala | 24 +- .../queue/engine/drmaa/DrmaaJobManager.scala | 2 +- .../queue/engine/drmaa/DrmaaJobRunner.scala | 20 +- .../queue/engine/shell/ShellJobManager.scala | 1 + .../queue/engine/shell/ShellJobRunner.scala | 51 +- .../extensions/gatk/GATKScatterFunction.scala | 2 +- .../sting/queue/extensions/gatk/RodBind.scala | 2 +- .../queue/extensions/gatk/TaggedFile.scala | 2 +- .../function/JavaCommandLineFunction.scala | 2 +- .../sting/queue/function/QFunction.scala | 1 + .../scattergather/GatherFunction.scala | 5 +- .../ScatterGatherableFunction.scala | 19 +- .../sting/queue/util/CommandLineJob.scala | 51 -- .../sting/queue/util/FileExtension.scala | 15 - .../sting/queue/util/IOUtils.scala | 253 --------- .../sting/queue/util/JobExitException.scala | 11 - .../sting/queue/util/ProcessController.scala | 369 ------------- .../sting/queue/util/QJobReport.scala | 14 +- .../sting/queue/util/ShellJob.scala | 27 - .../sting/queue/pipeline/PipelineTest.scala | 7 +- .../sting/queue/util/IOUtilsUnitTest.scala | 122 ----- .../sting/queue/util/ShellJobUnitTest.scala | 73 --- 71 files changed, 2676 insertions(+), 1111 deletions(-) rename public/R/{ => scripts/org/broadinstitute/sting/queue/util}/queueJobReport.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/DESCRIPTION (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.error.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.getargs.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.message.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.plot.venn.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.read.eval.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.read.gatkreport.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.read.squidmetrics.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.read.vcf.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/R/gsa.warn.R (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/Read-and-delete-me (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/data/tearsheetdrop.jpg (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.error.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.getargs.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.message.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.plot.venn.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.read.eval.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.read.gatkreport.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.read.squidmetrics.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.read.vcf.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsa.warn.Rd (100%) rename public/R/src/{ => org/broadinstitute/sting/utils/R}/gsalib/man/gsalib-package.Rd (100%) create mode 100644 public/java/src/org/broadinstitute/sting/utils/R/RScriptLibrary.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/io/FileExtension.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/io/HardThresholdingOutputStream.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/io/Resource.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/CapturedStreamOutput.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/InputStreamSettings.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/OutputStreamSettings.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/ProcessController.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/ProcessOutput.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/ProcessSettings.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/StreamLocation.java create mode 100755 public/java/src/org/broadinstitute/sting/utils/runtime/StreamOutput.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/R/RScriptLibraryUnitTest.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/runtime/ProcessControllerUnitTest.java delete mode 100644 public/scala/src/org/broadinstitute/sting/queue/util/CommandLineJob.scala delete mode 100644 public/scala/src/org/broadinstitute/sting/queue/util/FileExtension.scala delete mode 100644 public/scala/src/org/broadinstitute/sting/queue/util/IOUtils.scala delete mode 100644 public/scala/src/org/broadinstitute/sting/queue/util/JobExitException.scala delete mode 100644 public/scala/src/org/broadinstitute/sting/queue/util/ProcessController.scala delete mode 100755 public/scala/src/org/broadinstitute/sting/queue/util/ShellJob.scala delete mode 100644 public/scala/test/org/broadinstitute/sting/queue/util/IOUtilsUnitTest.scala delete mode 100644 public/scala/test/org/broadinstitute/sting/queue/util/ShellJobUnitTest.scala diff --git a/build.xml b/build.xml index 446982a44a..6ca959c387 100644 --- a/build.xml +++ b/build.xml @@ -28,6 +28,8 @@ + + @@ -35,18 +37,25 @@ + + + + + + - + - + @@ -60,7 +69,7 @@ - + @@ -82,7 +91,7 @@ - + @@ -113,7 +122,7 @@ - + @@ -154,7 +163,7 @@ - + @@ -211,11 +220,11 @@ - + - + @@ -224,11 +233,11 @@ - + - + @@ -266,7 +275,7 @@ - + @@ -312,13 +321,13 @@ - + - + @@ -327,11 +336,11 @@ - + - @@ -341,9 +350,9 @@ - + - + @@ -362,14 +371,14 @@ - + - + - - + @@ -413,9 +422,9 @@ - + - + @@ -424,12 +433,12 @@ - + - + @@ -532,6 +541,11 @@ + + + + + @@ -539,7 +553,7 @@ - + @@ -551,6 +565,12 @@ + + + + + + @@ -579,6 +599,10 @@ + + + + @@ -593,6 +617,10 @@ + + + + @@ -605,28 +633,7 @@ - @@ -643,6 +650,9 @@ + + + @@ -682,20 +692,7 @@ - + @@ -780,10 +777,6 @@ - @@ -800,10 +793,6 @@ - @@ -851,6 +840,8 @@ + + @@ -1187,19 +1178,18 @@ - - + - + - + diff --git a/public/R/queueJobReport.R b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R similarity index 100% rename from public/R/queueJobReport.R rename to public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R diff --git a/public/R/src/gsalib/DESCRIPTION b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/DESCRIPTION similarity index 100% rename from public/R/src/gsalib/DESCRIPTION rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/DESCRIPTION diff --git a/public/R/src/gsalib/R/gsa.error.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.error.R similarity index 100% rename from public/R/src/gsalib/R/gsa.error.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.error.R diff --git a/public/R/src/gsalib/R/gsa.getargs.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.getargs.R similarity index 100% rename from public/R/src/gsalib/R/gsa.getargs.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.getargs.R diff --git a/public/R/src/gsalib/R/gsa.message.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.message.R similarity index 100% rename from public/R/src/gsalib/R/gsa.message.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.message.R diff --git a/public/R/src/gsalib/R/gsa.plot.venn.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.plot.venn.R similarity index 100% rename from public/R/src/gsalib/R/gsa.plot.venn.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.plot.venn.R diff --git a/public/R/src/gsalib/R/gsa.read.eval.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.eval.R similarity index 100% rename from public/R/src/gsalib/R/gsa.read.eval.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.eval.R diff --git a/public/R/src/gsalib/R/gsa.read.gatkreport.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.gatkreport.R similarity index 100% rename from public/R/src/gsalib/R/gsa.read.gatkreport.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.gatkreport.R diff --git a/public/R/src/gsalib/R/gsa.read.squidmetrics.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.squidmetrics.R similarity index 100% rename from public/R/src/gsalib/R/gsa.read.squidmetrics.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.squidmetrics.R diff --git a/public/R/src/gsalib/R/gsa.read.vcf.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.vcf.R similarity index 100% rename from public/R/src/gsalib/R/gsa.read.vcf.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.read.vcf.R diff --git a/public/R/src/gsalib/R/gsa.warn.R b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.warn.R similarity index 100% rename from public/R/src/gsalib/R/gsa.warn.R rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/R/gsa.warn.R diff --git a/public/R/src/gsalib/Read-and-delete-me b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/Read-and-delete-me similarity index 100% rename from public/R/src/gsalib/Read-and-delete-me rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/Read-and-delete-me diff --git a/public/R/src/gsalib/data/tearsheetdrop.jpg b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/data/tearsheetdrop.jpg similarity index 100% rename from public/R/src/gsalib/data/tearsheetdrop.jpg rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/data/tearsheetdrop.jpg diff --git a/public/R/src/gsalib/man/gsa.error.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.error.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.error.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.error.Rd diff --git a/public/R/src/gsalib/man/gsa.getargs.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.getargs.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.getargs.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.getargs.Rd diff --git a/public/R/src/gsalib/man/gsa.message.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.message.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.message.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.message.Rd diff --git a/public/R/src/gsalib/man/gsa.plot.venn.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.plot.venn.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.plot.venn.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.plot.venn.Rd diff --git a/public/R/src/gsalib/man/gsa.read.eval.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.eval.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.read.eval.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.eval.Rd diff --git a/public/R/src/gsalib/man/gsa.read.gatkreport.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.gatkreport.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.read.gatkreport.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.gatkreport.Rd diff --git a/public/R/src/gsalib/man/gsa.read.squidmetrics.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.squidmetrics.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.read.squidmetrics.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.squidmetrics.Rd diff --git a/public/R/src/gsalib/man/gsa.read.vcf.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.vcf.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.read.vcf.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.read.vcf.Rd diff --git a/public/R/src/gsalib/man/gsa.warn.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.warn.Rd similarity index 100% rename from public/R/src/gsalib/man/gsa.warn.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsa.warn.Rd diff --git a/public/R/src/gsalib/man/gsalib-package.Rd b/public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsalib-package.Rd similarity index 100% rename from public/R/src/gsalib/man/gsalib-package.Rd rename to public/R/src/org/broadinstitute/sting/utils/R/gsalib/man/gsalib-package.Rd 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 58f7942fe5..9180447b94 100644 --- a/public/java/src/org/broadinstitute/sting/utils/R/RScriptExecutor.java +++ b/public/java/src/org/broadinstitute/sting/utils/R/RScriptExecutor.java @@ -25,35 +25,35 @@ package org.broadinstitute.sting.utils.R; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.broadinstitute.sting.commandline.Advanced; import org.broadinstitute.sting.commandline.Argument; -import org.broadinstitute.sting.commandline.ArgumentCollection; -import org.broadinstitute.sting.gatk.walkers.recalibration.Covariate; -import org.broadinstitute.sting.utils.PathUtils; import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.exceptions.StingException; import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.io.IOUtils; +import org.broadinstitute.sting.utils.io.Resource; +import org.broadinstitute.sting.utils.runtime.ProcessController; +import org.broadinstitute.sting.utils.runtime.ProcessSettings; import java.io.File; -import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** - * Generic service for executing RScripts in the GATK directory - * - * @author Your Name - * @since Date created + * Generic service for executing RScripts */ public class RScriptExecutor { /** * our log */ - protected static Logger logger = Logger.getLogger(RScriptExecutor.class); + private static Logger logger = Logger.getLogger(RScriptExecutor.class); public static class RScriptArgumentCollection { @Advanced - @Argument(fullName = "path_to_Rscript", shortName = "Rscript", doc = "The path to your implementation of Rscript. For Broad users this is maybe /broad/software/free/Linux/redhat_5_x86_64/pkgs/r_2.12.0/bin/Rscript", required = false) + @Argument(fullName = "path_to_Rscript", shortName = "Rscript", doc = "The path to your implementation of Rscript. Defaults Rscript meaning to use the first available on the environment PATH. For Broad users should 'use R-2.12' or later.", required = false) public String PATH_TO_RSCRIPT = "Rscript"; @Advanced @@ -62,40 +62,119 @@ public static class RScriptArgumentCollection { public RScriptArgumentCollection() {} - /** For testing and convenience */ + /* For testing and convenience */ public RScriptArgumentCollection(final String PATH_TO_RSCRIPT, final List PATH_TO_RESOURCES) { this.PATH_TO_RSCRIPT = PATH_TO_RSCRIPT; this.PATH_TO_RESOURCES = PATH_TO_RESOURCES; } } - final RScriptArgumentCollection myArgs; - final boolean exceptOnError; + private final RScriptArgumentCollection myArgs; + private final boolean exceptOnError; + private final List libraries = new ArrayList(); + private final List scriptResources = new ArrayList(); + private final List scriptFiles = new ArrayList(); + private final List args = new ArrayList(); public RScriptExecutor(final RScriptArgumentCollection myArgs, final boolean exceptOnError) { this.myArgs = myArgs; this.exceptOnError = exceptOnError; } - public void callRScripts(String scriptName, Object... scriptArgs) { - callRScripts(scriptName, Arrays.asList(scriptArgs)); + public void addLibrary(RScriptLibrary library) { + this.libraries.add(library); + } + + public void addScript(Resource script) { + this.scriptResources.add(script); + } + + public void addScript(File script) { + this.scriptFiles.add(script); } - public void callRScripts(String scriptName, List scriptArgs) { + /** + * Adds args to the end of the Rscript command line. + * @param args the args. + * @throws NullPointerException if any of the args are null. + */ + public void addArgs(Object... args) { + for (Object arg: args) + this.args.add(arg.toString()); + } + + public void exec() { + List tempFiles = new ArrayList(); try { - final File pathToScript = findScript(scriptName); - if ( pathToScript == null ) return; // we failed but shouldn't exception out - final String argString = Utils.join(" ", scriptArgs); - final String cmdLine = Utils.join(" ", Arrays.asList(myArgs.PATH_TO_RSCRIPT, pathToScript, argString)); - logger.info("Executing RScript: " + cmdLine); - Runtime.getRuntime().exec(cmdLine).waitFor(); - } catch (InterruptedException e) { + File tempLibDir = IOUtils.tempDir("R.", ".lib"); + tempFiles.add(tempLibDir); + + StringBuilder expression = new StringBuilder("tempLibDir = '").append(tempLibDir).append("';"); + + if (this.libraries.size() > 0) { + List tempLibraryPaths = new ArrayList(); + for (RScriptLibrary library: this.libraries) { + File tempLibrary = library.writeTemp(); + tempFiles.add(tempLibrary); + tempLibraryPaths.add(tempLibrary.getAbsolutePath()); + } + + expression.append("install.packages("); + expression.append("pkgs=c('").append(StringUtils.join(tempLibraryPaths, "', '")).append("'), lib=tempLibDir, repos=NULL, type='source', "); + // Install faster by eliminating cruft. + expression.append("INSTALL_opts=c('--no-libs', '--no-data', '--no-help', '--no-demo', '--no-exec')"); + expression.append(");"); + + for (RScriptLibrary library: this.libraries) { + expression.append("require('").append(library.getLibraryName()).append("', lib.loc=tempLibDir);"); + } + } + + for (Resource script: this.scriptResources) { + File tempScript = IOUtils.writeTempResource(script); + tempFiles.add(tempScript); + expression.append("source('").append(tempScript.getAbsolutePath()).append("');"); + } + + for (File script: this.scriptFiles) { + expression.append("source('").append(script.getAbsolutePath()).append("');"); + } + + String[] cmd = new String[this.args.size() + 3]; + int i = 0; + cmd[i++] = myArgs.PATH_TO_RSCRIPT; + cmd[i++] = "-e"; + cmd[i++] = expression.toString(); + for (String arg: this.args) + cmd[i++] = arg; + + ProcessSettings processSettings = new ProcessSettings(cmd); + if (logger.isDebugEnabled()) { + processSettings.getStdoutSettings().printStandard(true); + processSettings.getStderrSettings().printStandard(true); + } + + ProcessController controller = ProcessController.getThreadLocal(); + + logger.debug("Executing: " + Utils.join(" ", cmd)); + logger.debug("Result: " + controller.exec(processSettings).getExitValue()); + + } catch (StingException e) { generateException(e); - } catch (IOException e) { - generateException("Fatal Exception: Perhaps RScript jobs are being spawned too quickly?", e); + } finally { + for (File temp: tempFiles) + FileUtils.deleteQuietly(temp); } } + public void callRScripts(String scriptName, Object... scriptArgs) { + final File pathToScript = findScript(scriptName); + if (pathToScript == null) return; // we failed but shouldn't exception out + addScript(pathToScript); + addArgs(scriptArgs); + exec(); + } + public File findScript(final String scriptName) { for ( String pathToResource : myArgs.PATH_TO_RESOURCES ) { final File f = new File(pathToResource + "/" + scriptName); diff --git a/public/java/src/org/broadinstitute/sting/utils/R/RScriptLibrary.java b/public/java/src/org/broadinstitute/sting/utils/R/RScriptLibrary.java new file mode 100644 index 0000000000..60cd7504b9 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/R/RScriptLibrary.java @@ -0,0 +1,59 @@ +/* + * 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.R; + +import org.broadinstitute.sting.utils.io.IOUtils; +import org.broadinstitute.sting.utils.io.Resource; + +import java.io.File; + +/** + * Libraries embedded in the StingUtils package. + */ +public enum RScriptLibrary { + GSALIB("gsalib"); + + private final String name; + + private RScriptLibrary(String name) { + this.name = name; + } + + public String getLibraryName() { + return this.name; + } + + public String getResourcePath() { + return name + ".tar.gz"; + } + + /** + * Writes the library source code to a temporary tar.gz file and returns the path. + * @return The path to the library source code. The caller must delete the code when done. + */ + public File writeTemp() { + return IOUtils.writeTempResource(new Resource(getResourcePath(), RScriptLibrary.class)); + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/io/FileExtension.java b/public/java/src/org/broadinstitute/sting/utils/io/FileExtension.java new file mode 100644 index 0000000000..cd69ee1263 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/io/FileExtension.java @@ -0,0 +1,36 @@ +/* + * 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.io; + +import java.io.File; + +public interface FileExtension { + /** + * Returns a clone of the FileExtension with a new path. + * @param path New path. + * @return New FileExtension + */ + public File withPath(String path); +} diff --git a/public/java/src/org/broadinstitute/sting/utils/io/HardThresholdingOutputStream.java b/public/java/src/org/broadinstitute/sting/utils/io/HardThresholdingOutputStream.java new file mode 100755 index 0000000000..26b5ae6fd1 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/io/HardThresholdingOutputStream.java @@ -0,0 +1,54 @@ +/* + * 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.io; + +import org.apache.commons.io.output.ThresholdingOutputStream; + +import java.io.IOException; + +/** + * An output stream which stops at the threshold + * instead of potentially triggering early. + */ +public abstract class HardThresholdingOutputStream extends ThresholdingOutputStream { + protected HardThresholdingOutputStream(int threshold) { + super(threshold); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int remaining = this.getThreshold() - (int)this.getByteCount(); + if (!isThresholdExceeded() && len > remaining) { + super.write(b, off, remaining); + super.write(b, off + remaining, len - remaining); + } else { + super.write(b, off, len); + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java new file mode 100644 index 0000000000..7bfaa01947 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/io/IOUtils.java @@ -0,0 +1,353 @@ +/* + * 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.io; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.LineIterator; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.broadinstitute.sting.utils.exceptions.StingException; +import org.broadinstitute.sting.utils.exceptions.UserException; + +import java.io.*; +import java.util.*; + +public class IOUtils { + private static Logger logger = Logger.getLogger(IOUtils.class); + + /** + * Checks if the temp directory has been setup and throws an exception if they user hasn't set it correctly. + * + * @param tempDir Temporary directory. + */ + public static void checkTempDir(File tempDir) { + String tempDirPath = tempDir.getAbsolutePath(); + // Keeps the user from leaving the temp directory as the default, and on Macs from having pluses + // in the path which can cause problems with the Google Reflections library. + // see also: http://benjchristensen.com/2009/09/22/mac-osx-10-6-java-java-io-tmpdir/ + if (tempDirPath.startsWith("/var/folders/") || (tempDirPath.equals("/tmp")) || (tempDirPath.equals("/tmp/"))) + throw new UserException.BadTmpDir("java.io.tmpdir must be explicitly set"); + if (!tempDir.exists() && !tempDir.mkdirs()) + throw new UserException.BadTmpDir("Could not create directory: " + tempDir.getAbsolutePath()); + } + + /** + * Creates a temp directory with the prefix and optional suffix. + * + * @param prefix Prefix for the directory name. + * @param suffix Optional suffix for the directory name. + * @return The created temporary directory. + */ + public static File tempDir(String prefix, String suffix) { + return tempDir(prefix, suffix, null); + } + + /** + * Creates a temp directory with the prefix and optional suffix. + * + * @param prefix Prefix for the directory name. + * @param suffix Optional suffix for the directory name. + * @param tempDirParent Parent directory for the temp directory. + * @return The created temporary directory. + */ + public static File tempDir(String prefix, String suffix, File tempDirParent) { + try { + if (tempDirParent == null) + 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); + if (!temp.delete()) + throw new UserException.BadTmpDir("Could not delete sub file: " + temp.getAbsolutePath()); + if (!temp.mkdir()) + throw new UserException.BadTmpDir("Could not create sub directory: " + temp.getAbsolutePath()); + return absolute(temp); + } catch (IOException e) { + throw new UserException.BadTmpDir(e.getMessage()); + } + } + + /** + * Writes content to a temp file and returns the path to the temporary file. + * + * @param content to write. + * @param prefix Prefix for the temp file. + * @param suffix Suffix for the temp file. + * @param directory Directory for the temp file. + * @return the path to the temp file. + */ + public static File writeTempFile(String content, String prefix, String suffix, File directory) { + try { + File tempFile = absolute(File.createTempFile(prefix, suffix, directory)); + FileUtils.writeStringToFile(tempFile, content); + return tempFile; + } catch (IOException e) { + throw new UserException.BadTmpDir(e.getMessage()); + } + } + + /** + * Waits for NFS to propagate a file creation, imposing a timeout. + * + * Based on Apache Commons IO FileUtils.waitFor() + * + * @param file The file to wait for. + * @param seconds The maximum time in seconds to wait. + * @return true if the file exists + */ + public static boolean waitFor(File file, int seconds) { + return waitFor(Collections.singletonList(file), seconds).isEmpty(); + } + + /** + * Waits for NFS to propagate a file creation, imposing a timeout. + * + * Based on Apache Commons IO FileUtils.waitFor() + * + * @param files The list of files to wait for. + * @param seconds The maximum time in seconds to wait. + * @return Files that still do not exists at the end of the timeout, or a empty list if all files exists. + */ + public static List waitFor(Collection files, int seconds) { + long timeout = 0; + long tick = 0; + List missingFiles = new ArrayList(); + for (File file : files) + if (!file.exists()) + missingFiles.add(file); + + while (!missingFiles.isEmpty() && timeout <= seconds) { + if (tick >= 10) { + tick = 0; + timeout++; + } + tick++; + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + } + List newMissingFiles = new ArrayList(); + for (File file : missingFiles) + if (!file.exists()) + newMissingFiles.add(file); + missingFiles = newMissingFiles; + } + return missingFiles; + } + + /** + * Returns the directory at the number of levels deep. + * For example 2 levels of /path/to/dir will return /path/to + * + * @param dir Directory path. + * @param level how many levels deep from the root. + * @return The path to the parent directory that is level-levels deep. + */ + public static File dirLevel(File dir, int level) { + List directories = new ArrayList(); + File parentDir = absolute(dir); + while (parentDir != null) { + directories.add(0, parentDir); + parentDir = parentDir.getParentFile(); + } + if (directories.size() <= level) + return directories.get(directories.size() - 1); + else + return directories.get(level); + } + + /** + * Returns the sub path rooted at the parent. + * + * @param parent The parent directory. + * @param path The sub path to append to the parent, if the path is not absolute. + * @return The absolute path to the file in the parent dir if the path was not absolute, otherwise the original path. + */ + public static File absolute(File parent, String path) { + return absolute(parent, new File(path)); + } + + /** + * Returns the sub path rooted at the parent. + * + * @param parent The parent directory. + * @param file The sub path to append to the parent, if the path is not absolute. + * @return The absolute path to the file in the parent dir if the path was not absolute, otherwise the original path. + */ + public static File absolute(File parent, File file) { + String newPath; + if (file.isAbsolute()) + newPath = absolutePath(file); + else + newPath = absolutePath(new File(parent, file.getPath())); + return replacePath(file, newPath); + } + + /** + * A mix of getCanonicalFile and getAbsoluteFile that returns the + * absolute path to the file without deferencing symbolic links. + * + * @param file the file. + * @return the absolute path to the file. + */ + public static File absolute(File file) { + return replacePath(file, absolutePath(file)); + } + + private static String absolutePath(File file) { + File fileAbs = file.getAbsoluteFile(); + LinkedList names = new LinkedList(); + while (fileAbs != null) { + String name = fileAbs.getName(); + fileAbs = fileAbs.getParentFile(); + + if (".".equals(name)) { + /* skip */ + + /* TODO: What do we do for ".."? + } else if (name == "..") { + + CentOS tcsh says use getCanonicalFile: + ~ $ mkdir -p test1/test2 + ~ $ ln -s test1/test2 test3 + ~ $ cd test3/.. + ~/test1 $ + + Mac bash says keep going with getAbsoluteFile: + ~ $ mkdir -p test1/test2 + ~ $ ln -s test1/test2 test3 + ~ $ cd test3/.. + ~ $ + + For now, leave it and let the shell figure it out. + */ + } else { + names.add(0, name); + } + } + + return ("/" + StringUtils.join(names, "/")); + } + + private static File replacePath(File file, String path) { + if (file instanceof FileExtension) + return ((FileExtension)file).withPath(path); + if (!File.class.equals(file.getClass())) + throw new StingException("Sub classes of java.io.File must also implement FileExtension"); + return new File(path); + } + + /** + * Returns the last lines of the file. + * NOTE: This is only safe to run on smaller files! + * + * @param file File to read. + * @param count Maximum number of lines to return. + * @return The last count lines from file. + * @throws IOException When unable to read the file. + */ + public static List tail(File file, int count) throws IOException { + LinkedList tailLines = new LinkedList(); + FileReader reader = new FileReader(file); + try { + LineIterator iterator = org.apache.commons.io.IOUtils.lineIterator(reader); + int lineCount = 0; + while (iterator.hasNext()) { + String line = iterator.nextLine(); + lineCount++; + if (lineCount > count) + tailLines.removeFirst(); + tailLines.offer(line); + } + } finally { + org.apache.commons.io.IOUtils.closeQuietly(reader); + } + return tailLines; + } + + /** + * Tries to delete a file. Emits a warning if the file was unable to be deleted. + * + * @param file File to delete. + * @return true if the file was deleted. + */ + public static boolean tryDelete(File file) { + boolean deleted = FileUtils.deleteQuietly(file); + if (deleted) + logger.debug("Deleted " + file); + else if (file.exists()) + logger.warn("Unable to delete " + file); + return deleted; + } + + /** + * Writes the an embedded resource to a temp file. + * File is not scheduled for deletion and must be cleaned up by the caller. + * @param resource Embedded resource. + * @return Path to the temp file with the contents of the resource. + */ + public static File writeTempResource(Resource resource) { + File temp; + try { + temp = File.createTempFile(FilenameUtils.getBaseName(resource.getPath()) + ".", "." + FilenameUtils.getExtension(resource.getPath())); + } catch (IOException e) { + throw new UserException.BadTmpDir(e.getMessage()); + } + writeResource(resource, temp); + return temp; + } + + /** + * Writes the an embedded resource to a file. + * File is not scheduled for deletion and must be cleaned up by the caller. + * @param resource Embedded resource. + * @param file File path to write. + */ + public static void writeResource(Resource resource, File file) { + String path = resource.getPath(); + Class clazz = resource.getRelativeClass(); + InputStream inputStream = null; + OutputStream outputStream = null; + try { + if (clazz == null) { + inputStream = ClassLoader.getSystemResourceAsStream(path); + if (inputStream == null) + throw new IllegalArgumentException("Resource not found: " + path); + } else { + inputStream = clazz.getResourceAsStream(path); + if (inputStream == null) + throw new IllegalArgumentException("Resource not found relative to " + clazz + ": " + path); + } + outputStream = FileUtils.openOutputStream(file); + org.apache.commons.io.IOUtils.copy(inputStream, outputStream); + } catch (IOException e) { + throw new StingException(String.format("Unable to copy resource '%s' to '%s'", path, file), e); + } finally { + org.apache.commons.io.IOUtils.closeQuietly(inputStream); + org.apache.commons.io.IOUtils.closeQuietly(outputStream); + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/io/Resource.java b/public/java/src/org/broadinstitute/sting/utils/io/Resource.java new file mode 100644 index 0000000000..5473511b4a --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/io/Resource.java @@ -0,0 +1,53 @@ +/* + * 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.io; + +/** + * Stores a resource by path and a relative class. + */ +public class Resource { + private final String path; + private final Class relativeClass; + + /** + * Create a resource with a path and a relative class. + * @param path Relative or absolute path to the class. + * @param relativeClass Relative class to use as a class loader and for a relative package. + * + * If the relative class is null then the system classloader will be used and the path must be absolute. + */ + public Resource(String path, Class relativeClass) { + this.path = path; + this.relativeClass = relativeClass; + } + + public Class getRelativeClass() { + return relativeClass; + } + + public String getPath() { + return path; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/CapturedStreamOutput.java b/public/java/src/org/broadinstitute/sting/utils/runtime/CapturedStreamOutput.java new file mode 100755 index 0000000000..50622cef1c --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/CapturedStreamOutput.java @@ -0,0 +1,133 @@ +/* + * 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.runtime; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.io.HardThresholdingOutputStream; + +import java.io.*; +import java.util.EnumMap; + +/** + * Stream output captured from a stream. + */ +public class CapturedStreamOutput extends StreamOutput { + private final InputStream processStream; + private final EnumMap outputStreams = new EnumMap(StreamLocation.class); + + /** + * The byte stream to capture content or null if no output string content was requested. + */ + private final ByteArrayOutputStream bufferStream; + + /** + * True if the buffer is truncated. + */ + private boolean bufferTruncated = false; + + /** + * @param settings Settings that define what to capture. + * @param processStream Stream to capture output. + * @param standardStream Stream to write debug output. + */ + public CapturedStreamOutput(OutputStreamSettings settings, InputStream processStream, PrintStream standardStream) { + this.processStream = processStream; + int bufferSize = settings.getBufferSize(); + this.bufferStream = (bufferSize < 0) ? new ByteArrayOutputStream() : new ByteArrayOutputStream(bufferSize); + + for (StreamLocation location : settings.getStreamLocations()) { + OutputStream outputStream; + switch (location) { + case Buffer: + if (bufferSize < 0) { + outputStream = this.bufferStream; + } else { + outputStream = new HardThresholdingOutputStream(bufferSize) { + @Override + protected OutputStream getStream() throws IOException { + return bufferTruncated ? NullOutputStream.NULL_OUTPUT_STREAM : bufferStream; + } + + @Override + protected void thresholdReached() throws IOException { + bufferTruncated = true; + } + }; + } + break; + case File: + try { + outputStream = new FileOutputStream(settings.getOutputFile(), settings.isAppendFile()); + } catch (IOException e) { + throw new UserException.BadInput(e.getMessage()); + } + break; + case Standard: + outputStream = standardStream; + break; + default: + throw new ReviewedStingException("Unexpected stream location: " + location); + } + this.outputStreams.put(location, outputStream); + } + } + + @Override + public byte[] getBufferBytes() { + return bufferStream.toByteArray(); + } + + @Override + public boolean isBufferTruncated() { + return bufferTruncated; + } + + /** + * Drain the input stream to keep the process from backing up until it's empty. + * File streams will be closed automatically when this method returns. + * + * @throws java.io.IOException When unable to read or write. + */ + public void readAndClose() throws IOException { + try { + byte[] buf = new byte[4096]; + int readCount; + while ((readCount = processStream.read(buf)) >= 0) + for (OutputStream outputStream : this.outputStreams.values()) { + outputStream.write(buf, 0, readCount); + } + } finally { + for (StreamLocation location : this.outputStreams.keySet()) { + OutputStream outputStream = this.outputStreams.get(location); + outputStream.flush(); + if (location != StreamLocation.Standard) + IOUtils.closeQuietly(outputStream); + } + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/InputStreamSettings.java b/public/java/src/org/broadinstitute/sting/utils/runtime/InputStreamSettings.java new file mode 100755 index 0000000000..dfa380a683 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/InputStreamSettings.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.utils.runtime; + +import java.io.File; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +/** + * Settings that define text to write to the process stdin. + */ +public class InputStreamSettings { + private final EnumSet streamLocations = EnumSet.noneOf(StreamLocation.class); + private byte[] inputBuffer; + private File inputFile; + + public InputStreamSettings() { + } + + /** + * @param inputBuffer String to write to stdin. + */ + public InputStreamSettings(String inputBuffer) { + setInputBuffer(inputBuffer); + } + + /** + * @param inputFile File to write to stdin. + */ + public InputStreamSettings(File inputFile) { + setInputFile(inputFile); + } + + /** + * @param inputBuffer String to write to stdin. + * @param inputFile File to write to stdin. + */ + public InputStreamSettings(byte[] inputBuffer, File inputFile) { + setInputBuffer(inputBuffer); + setInputFile(inputFile); + } + + public Set getStreamLocations() { + return Collections.unmodifiableSet(streamLocations); + } + + public byte[] getInputBuffer() { + return inputBuffer; + } + + public void setInputBuffer(String inputBuffer) { + if (inputBuffer == null) + throw new IllegalArgumentException("inputBuffer cannot be null"); + this.streamLocations.add(StreamLocation.Buffer); + this.inputBuffer = inputBuffer.getBytes(); + } + + public void setInputBuffer(byte[] inputBuffer) { + if (inputBuffer == null) + throw new IllegalArgumentException("inputBuffer cannot be null"); + this.streamLocations.add(StreamLocation.Buffer); + this.inputBuffer = inputBuffer; + } + + public void clearInputBuffer() { + this.streamLocations.remove(StreamLocation.Buffer); + this.inputBuffer = null; + } + + public File getInputFile() { + return inputFile; + } + + public void setInputFile(File inputFile) { + if (inputFile == null) + throw new IllegalArgumentException("inputFile cannot be null"); + this.streamLocations.add(StreamLocation.File); + this.inputFile = inputFile; + } + + public void clearInputFile() { + this.streamLocations.remove(StreamLocation.File); + this.inputFile = null; + } + + public void setInputStandard(boolean inputStandard) { + if (inputStandard) + this.streamLocations.add(StreamLocation.Standard); + else + this.streamLocations.remove(StreamLocation.Standard); + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/OutputStreamSettings.java b/public/java/src/org/broadinstitute/sting/utils/runtime/OutputStreamSettings.java new file mode 100755 index 0000000000..468ece1783 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/OutputStreamSettings.java @@ -0,0 +1,126 @@ +/* + * 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.runtime; + +import java.io.File; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +/** + * Settings that define text to capture from a process stream. + */ +public class OutputStreamSettings { + private final EnumSet streamLocations = EnumSet.noneOf(StreamLocation.class); + private int bufferSize; + private File outputFile; + private boolean appendFile; + + public OutputStreamSettings() { + } + + /** + * @param bufferSize The number of bytes to capture, or -1 for unlimited. + */ + public OutputStreamSettings(int bufferSize) { + setBufferSize(bufferSize); + } + + /** + * @param outputFile The file to write output to. + */ + public OutputStreamSettings(File outputFile) { + setOutputFile(outputFile); + } + + /** + * @param outputFile The file to write output to. + * @param append true if the output file should be appended to. + */ + public OutputStreamSettings(File outputFile, boolean append) { + setOutputFile(outputFile, append); + } + + public OutputStreamSettings(int bufferSize, File outputFile, boolean appendFile) { + setBufferSize(bufferSize); + setOutputFile(outputFile, appendFile); + } + + public Set getStreamLocations() { + return Collections.unmodifiableSet(streamLocations); + } + + public int getBufferSize() { + return bufferSize; + } + + public void setBufferSize(int bufferSize) { + this.streamLocations.add(StreamLocation.Buffer); + this.bufferSize = bufferSize; + } + + public void clearBufferSize() { + this.streamLocations.remove(StreamLocation.Buffer); + this.bufferSize = 0; + } + + public File getOutputFile() { + return outputFile; + } + + public boolean isAppendFile() { + return appendFile; + } + + /** + * Overwrites the outputFile with the process output. + * + * @param outputFile File to overwrite. + */ + public void setOutputFile(File outputFile) { + setOutputFile(outputFile, false); + } + + public void setOutputFile(File outputFile, boolean append) { + if (outputFile == null) + throw new IllegalArgumentException("outputFile cannot be null"); + streamLocations.add(StreamLocation.File); + this.outputFile = outputFile; + this.appendFile = append; + } + + public void clearOutputFile() { + streamLocations.remove(StreamLocation.File); + this.outputFile = null; + this.appendFile = false; + } + + public void printStandard(boolean print) { + if (print) + this.streamLocations.add(StreamLocation.Standard); + else + this.streamLocations.remove(StreamLocation.Standard); + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessController.java b/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessController.java new file mode 100755 index 0000000000..6a3f9c7537 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessController.java @@ -0,0 +1,363 @@ +/* + * 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.runtime; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; + +/** + * Facade to Runtime.exec() and java.lang.Process. Handles + * running a process to completion and returns stdout and stderr + * as strings. Creates separate threads for reading stdout and stderr, + * then reuses those threads for each process most efficient use is + * to create one of these and use it repeatedly. Instances are not + * thread-safe, however. + * + * TODO: java.io sometimes zombies the backround threads locking up on read(). + * Supposedly NIO has better ways of interrupting a blocked stream but will + * require a little bit of refactoring. + * + * @author Michael Koehrsen + * @author Khalid Shakir + */ +public class ProcessController { + private static Logger logger = Logger.getLogger(ProcessController.class); + + private static enum ProcessStream {Stdout, Stderr} + + // Tracks running processes. + private static final Set running = Collections.synchronizedSet(new HashSet()); + + // Tracks this running process. + private Process process; + + // Threads that capture stdout and stderr + private final OutputCapture stdoutCapture; + private final OutputCapture stderrCapture; + + // When a caller destroyes a controller a new thread local version will be created + private boolean destroyed = false; + + // Communication channels with output capture threads + + // Holds the stdout and stderr sent to the background capture threads + private final Map toCapture = + new EnumMap(ProcessStream.class); + + // Holds the results of the capture from the background capture threads. + // May be the content via toCapture or an StreamOutput.EMPTY if the capture was interrupted. + private final Map fromCapture = + new EnumMap(ProcessStream.class); + + // Useful for debugging if background threads have shut down correctly + private static int nextControllerId = 0; + private final int controllerId; + + public ProcessController() { + // Start the background threads for this controller. + synchronized (running) { + controllerId = nextControllerId++; + } + stdoutCapture = new OutputCapture(ProcessStream.Stdout, controllerId); + stderrCapture = new OutputCapture(ProcessStream.Stderr, controllerId); + stdoutCapture.start(); + stderrCapture.start(); + } + + /** + * Returns a thread local ProcessController. + * Should NOT be closed when finished so it can be reused by the thread. + * + * @return a thread local ProcessController. + */ + public static ProcessController getThreadLocal() { + // If the local controller was destroyed get a fresh instance. + if (threadProcessController.get().destroyed) + threadProcessController.remove(); + return threadProcessController.get(); + } + + /** + * Thread local process controller container. + */ + private static final ThreadLocal threadProcessController = + new ThreadLocal() { + @Override + protected ProcessController initialValue() { + return new ProcessController(); + } + }; + + /** + * Similar to Runtime.exec() but drains the output and error streams. + * + * @param command Command to run. + * @return The result code. + */ + public static int exec(String[] command) { + ProcessController controller = ProcessController.getThreadLocal(); + return controller.exec(new ProcessSettings(command)).getExitValue(); + } + + /** + * Executes a command line program with the settings and waits for it to return, + * processing the output on a background thread. + * + * @param settings Settings to be run. + * @return The output of the command. + */ + public ProcessOutput exec(ProcessSettings settings) { + if (destroyed) + throw new IllegalStateException("This controller was destroyed"); + + ProcessBuilder builder = new ProcessBuilder(settings.getCommand()); + builder.directory(settings.getDirectory()); + + Map settingsEnvironment = settings.getEnvironment(); + if (settingsEnvironment != null) { + Map builderEnvironment = builder.environment(); + builderEnvironment.clear(); + builderEnvironment.putAll(settingsEnvironment); + } + + builder.redirectErrorStream(settings.isRedirectErrorStream()); + + StreamOutput stdout = null; + StreamOutput stderr = null; + + // Start the process running. + + try { + synchronized (toCapture) { + process = builder.start(); + } + running.add(this); + } catch (IOException e) { + throw new ReviewedStingException("Unable to start command: " + StringUtils.join(builder.command(), " ")); + } + + int exitCode; + + try { + // Notify the background threads to start capturing. + synchronized (toCapture) { + toCapture.put(ProcessStream.Stdout, + new CapturedStreamOutput(settings.getStdoutSettings(), process.getInputStream(), System.out)); + toCapture.put(ProcessStream.Stderr, + new CapturedStreamOutput(settings.getStderrSettings(), process.getErrorStream(), System.err)); + toCapture.notifyAll(); + } + + // Write stdin content + InputStreamSettings stdinSettings = settings.getStdinSettings(); + Set streamLocations = stdinSettings.getStreamLocations(); + if (!streamLocations.isEmpty()) { + try { + OutputStream stdinStream = process.getOutputStream(); + for (StreamLocation location : streamLocations) { + InputStream inputStream; + switch (location) { + case Buffer: + inputStream = new ByteArrayInputStream(stdinSettings.getInputBuffer()); + break; + case File: + try { + inputStream = FileUtils.openInputStream(stdinSettings.getInputFile()); + } catch (IOException e) { + throw new UserException.BadInput(e.getMessage()); + } + break; + case Standard: + inputStream = System.in; + break; + default: + throw new ReviewedStingException("Unexpected stream location: " + location); + } + try { + IOUtils.copy(inputStream, stdinStream); + } finally { + if (location != StreamLocation.Standard) + IOUtils.closeQuietly(inputStream); + } + } + stdinStream.flush(); + } catch (IOException e) { + throw new ReviewedStingException("Error writing to stdin on command: " + StringUtils.join(builder.command(), " "), e); + } + } + + // Wait for the process to complete. + try { + process.getOutputStream().close(); + process.waitFor(); + } catch (IOException e) { + throw new ReviewedStingException("Unable to close stdin on command: " + StringUtils.join(builder.command(), " "), e); + } catch (InterruptedException e) { + throw new ReviewedStingException("Process interrupted", e); + } finally { + while (!destroyed && stdout == null || stderr == null) { + synchronized (fromCapture) { + if (fromCapture.containsKey(ProcessStream.Stdout)) + stdout = fromCapture.remove(ProcessStream.Stdout); + if (fromCapture.containsKey(ProcessStream.Stderr)) + stderr = fromCapture.remove(ProcessStream.Stderr); + try { + if (stdout == null || stderr == null) + fromCapture.wait(); + } catch (InterruptedException e) { + // Log the error, ignore the interrupt and wait patiently + // for the OutputCaptures to (via finally) return their + // stdout and stderr. + logger.error(e); + } + } + } + + if (destroyed) { + if (stdout == null) + stdout = StreamOutput.EMPTY; + if (stderr == null) + stderr = StreamOutput.EMPTY; + } + } + } finally { + synchronized (toCapture) { + exitCode = process.exitValue(); + process = null; + } + running.remove(this); + } + + return new ProcessOutput(exitCode, stdout, stderr); + } + + /** + * @return The set of still running processes. + */ + public static Set getRunning() { + synchronized (running) { + return new HashSet(running); + } + } + + /** + * Stops the process from running and tries to ensure process is cleaned up properly. + * NOTE: sub-processes started by process may be zombied with their parents set to pid 1. + * NOTE: capture threads may block on read. + * TODO: Try to use NIO to interrupt streams. + */ + public void tryDestroy() { + destroyed = true; + synchronized (toCapture) { + if (process != null) { + process.destroy(); + IOUtils.closeQuietly(process.getInputStream()); + IOUtils.closeQuietly(process.getErrorStream()); + } + stdoutCapture.interrupt(); + stderrCapture.interrupt(); + toCapture.notifyAll(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + tryDestroy(); + } catch (Exception e) { + logger.error(e); + } + super.finalize(); + } + + private class OutputCapture extends Thread { + private final int controllerId; + private final ProcessStream key; + + /** + * Reads in the output of a stream on a background thread to keep the output pipe from backing up and freezing the called process. + * + * @param key The stdout or stderr key for this output capture. + * @param controllerId Unique id of the controller. + */ + public OutputCapture(ProcessStream key, int controllerId) { + super(String.format("OutputCapture-%d-%s-%s-%d", controllerId, key.name().toLowerCase(), + Thread.currentThread().getName(), Thread.currentThread().getId())); + this.controllerId = controllerId; + this.key = key; + setDaemon(true); + } + + /** + * Runs the capture. + */ + @Override + public void run() { + while (!destroyed) { + StreamOutput processStream = StreamOutput.EMPTY; + try { + // Wait for a new input stream to be passed from this process controller. + CapturedStreamOutput capturedProcessStream = null; + while (!destroyed && capturedProcessStream == null) { + synchronized (toCapture) { + if (toCapture.containsKey(key)) { + capturedProcessStream = toCapture.remove(key); + } else { + toCapture.wait(); + } + } + } + + if (!destroyed) { + // Read in the input stream + processStream = capturedProcessStream; + capturedProcessStream.readAndClose(); + } + } catch (InterruptedException e) { + logger.info("OutputCapture interrupted, exiting"); + break; + } catch (IOException e) { + logger.error("Error reading process output", e); + } finally { + // Send the string back to the process controller. + synchronized (fromCapture) { + fromCapture.put(key, processStream); + fromCapture.notify(); + } + } + } + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessOutput.java b/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessOutput.java new file mode 100755 index 0000000000..2110089507 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessOutput.java @@ -0,0 +1,56 @@ +/* + * 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.runtime; + +public class ProcessOutput { + private final int exitValue; + private final StreamOutput stdout; + private final StreamOutput stderr; + + /** + * The output of a process. + * + * @param exitValue The exit value. + * @param stdout The capture of stdout as defined by the stdout OutputStreamSettings. + * @param stderr The capture of stderr as defined by the stderr OutputStreamSettings. + */ + public ProcessOutput(int exitValue, StreamOutput stdout, StreamOutput stderr) { + this.exitValue = exitValue; + this.stdout = stdout; + this.stderr = stderr; + } + + public int getExitValue() { + return exitValue; + } + + public StreamOutput getStdout() { + return stdout; + } + + public StreamOutput getStderr() { + return stderr; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessSettings.java b/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessSettings.java new file mode 100755 index 0000000000..b9f67f3a40 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/ProcessSettings.java @@ -0,0 +1,140 @@ +/* + * 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.runtime; + +import com.sun.corba.se.spi.orbutil.fsm.Input; + +import java.io.File; +import java.util.Map; + +public class ProcessSettings { + private String[] command; + private Map environment; + private File directory; + private boolean redirectErrorStream; + private InputStreamSettings stdinSettings; + private OutputStreamSettings stdoutSettings; + private OutputStreamSettings stderrSettings; + + /** + * @param command Command line to run. + */ + public ProcessSettings(String[] command) { + this(command, false, null, null, null, null, null); + } + + /** + * @param command Command line to run. + * @param redirectErrorStream true if stderr should be sent to stdout. + * @param environment Environment settings to override System.getEnv, or null to use System.getEnv. + * @param directory The directory to run the command in, or null to run in the current directory. + * @param stdinSettings Settings for writing to the process stdin. + * @param stdoutSettings Settings for capturing the process stdout. + * @param stderrSettings Setting for capturing the process stderr. + */ + public ProcessSettings(String[] command, boolean redirectErrorStream, File directory, Map environment, + InputStreamSettings stdinSettings, OutputStreamSettings stdoutSettings, OutputStreamSettings stderrSettings) { + this.command = checkCommand(command); + this.redirectErrorStream = redirectErrorStream; + this.directory = directory; + this.environment = environment; + this.stdinSettings = checkSettings(stdinSettings); + this.stdoutSettings = checkSettings(stdoutSettings); + this.stderrSettings = checkSettings(stderrSettings); + } + + public String[] getCommand() { + return command; + } + + public void setCommand(String[] command) { + this.command = checkCommand(command); + } + + public boolean isRedirectErrorStream() { + return redirectErrorStream; + } + + public void setRedirectErrorStream(boolean redirectErrorStream) { + this.redirectErrorStream = redirectErrorStream; + } + + public File getDirectory() { + return directory; + } + + public void setDirectory(File directory) { + this.directory = directory; + } + + public Map getEnvironment() { + return environment; + } + + public void setEnvironment(Map environment) { + this.environment = environment; + } + + public InputStreamSettings getStdinSettings() { + return stdinSettings; + } + + public void setStdinSettings(InputStreamSettings stdinSettings) { + this.stdinSettings = checkSettings(stdinSettings); + } + + public OutputStreamSettings getStdoutSettings() { + return stdoutSettings; + } + + public void setStdoutSettings(OutputStreamSettings stdoutSettings) { + this.stdoutSettings = checkSettings(stdoutSettings); + } + + public OutputStreamSettings getStderrSettings() { + return stderrSettings; + } + + public void setStderrSettings(OutputStreamSettings stderrSettings) { + this.stderrSettings = checkSettings(stderrSettings); + } + + protected String[] checkCommand(String[] command) { + if (command == null) + throw new IllegalArgumentException("Command is not allowed to be null"); + for (String s: command) + if (s == null) + throw new IllegalArgumentException("Command is not allowed to contain nulls"); + return command; + } + + protected InputStreamSettings checkSettings(InputStreamSettings settings) { + return settings == null ? new InputStreamSettings() : settings; + } + + protected OutputStreamSettings checkSettings(OutputStreamSettings settings) { + return settings == null ? new OutputStreamSettings() : settings; + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/StreamLocation.java b/public/java/src/org/broadinstitute/sting/utils/runtime/StreamLocation.java new file mode 100755 index 0000000000..df72180f14 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/StreamLocation.java @@ -0,0 +1,32 @@ +/* + * 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.runtime; + +/** + * Where to read/write a stream + */ +public enum StreamLocation { + Buffer, File, Standard +} diff --git a/public/java/src/org/broadinstitute/sting/utils/runtime/StreamOutput.java b/public/java/src/org/broadinstitute/sting/utils/runtime/StreamOutput.java new file mode 100755 index 0000000000..5dc94815f5 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/runtime/StreamOutput.java @@ -0,0 +1,68 @@ +/* + * 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.runtime; + +/** + * The content of stdout or stderr. + */ +public abstract class StreamOutput { + /** + * Empty stream output when no output is captured due to an error. + */ + public static final StreamOutput EMPTY = new StreamOutput() { + @Override + public byte[] getBufferBytes() { + return new byte[0]; + } + + @Override + public boolean isBufferTruncated() { + return false; + } + }; + + /** + * Returns the content as a string. + * + * @return The content as a string. + */ + public String getBufferString() { + return new String(getBufferBytes()); + } + + /** + * Returns the content as a string. + * + * @return The content as a string. + */ + public abstract byte[] getBufferBytes(); + + /** + * Returns true if the buffer was truncated. + * + * @return true if the buffer was truncated. + */ + public abstract boolean isBufferTruncated(); +} diff --git a/public/java/test/org/broadinstitute/sting/utils/R/RScriptLibraryUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/R/RScriptLibraryUnitTest.java new file mode 100644 index 0000000000..19fd5b316a --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/R/RScriptLibraryUnitTest.java @@ -0,0 +1,46 @@ +/* + * 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.R; + +import org.apache.commons.io.FileUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; + +public class RScriptLibraryUnitTest { + @Test + public void testProperties() { + Assert.assertEquals(RScriptLibrary.GSALIB.getLibraryName(), "gsalib"); + Assert.assertEquals(RScriptLibrary.GSALIB.getResourcePath(), "gsalib.tar.gz"); + } + + @Test + public void testWriteTemp() { + File file = RScriptLibrary.GSALIB.writeTemp(); + Assert.assertTrue(file.exists(), "R library was not written to temp file: " + file); + FileUtils.deleteQuietly(file); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java new file mode 100644 index 0000000000..4caf7f485c --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/io/IOUtilsUnitTest.java @@ -0,0 +1,197 @@ +package org.broadinstitute.sting.utils.io; + +import org.apache.commons.io.FileUtils; +import org.broadinstitute.sting.BaseTest; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class IOUtilsUnitTest extends BaseTest { + @Test + public void testGoodTempDir() { + IOUtils.checkTempDir(new File("/tmp/queue")); + } + + @Test(expectedExceptions=UserException.BadTmpDir.class) + public void testBadTempDir() { + IOUtils.checkTempDir(new File("/tmp")); + } + + @Test + public void testAbsoluteSubDir() { + File subDir = IOUtils.absolute(new File("."), new File("/path/to/file")); + Assert.assertEquals(subDir, new File("/path/to/file")); + + subDir = IOUtils.absolute(new File("/different/path"), new File("/path/to/file")); + Assert.assertEquals(subDir, new File("/path/to/file")); + + subDir = IOUtils.absolute(new File("/different/path"), new File(".")); + Assert.assertEquals(subDir, new File("/different/path")); + } + + @Test + public void testRelativeSubDir() throws IOException { + File subDir = IOUtils.absolute(new File("."), new File("path/to/file")); + Assert.assertEquals(subDir.getCanonicalFile(), new File("path/to/file").getCanonicalFile()); + + subDir = IOUtils.absolute(new File("/different/path"), new File("path/to/file")); + Assert.assertEquals(subDir, new File("/different/path/path/to/file")); + } + + @Test + public void testDottedSubDir() throws IOException { + File subDir = IOUtils.absolute(new File("."), new File("path/../to/file")); + Assert.assertEquals(subDir.getCanonicalFile(), new File("path/../to/./file").getCanonicalFile()); + + subDir = IOUtils.absolute(new File("."), new File("/path/../to/file")); + Assert.assertEquals(subDir, new File("/path/../to/file")); + + subDir = IOUtils.absolute(new File("/different/../path"), new File("path/to/file")); + Assert.assertEquals(subDir, new File("/different/../path/path/to/file")); + + subDir = IOUtils.absolute(new File("/different/./path"), new File("/path/../to/file")); + Assert.assertEquals(subDir, new File("/path/../to/file")); + } + + @Test + public void testTempDir() { + File tempDir = IOUtils.tempDir("Q-Unit-Test", "", new File("queueTempDirToDelete")); + Assert.assertTrue(tempDir.exists()); + Assert.assertFalse(tempDir.isFile()); + Assert.assertTrue(tempDir.isDirectory()); + boolean deleted = IOUtils.tryDelete(tempDir); + Assert.assertTrue(deleted); + Assert.assertFalse(tempDir.exists()); + } + + @Test + public void testDirLevel() { + File dir = IOUtils.dirLevel(new File("/path/to/directory"), 1); + Assert.assertEquals(dir, new File("/path")); + + dir = IOUtils.dirLevel(new File("/path/to/directory"), 2); + Assert.assertEquals(dir, new File("/path/to")); + + dir = IOUtils.dirLevel(new File("/path/to/directory"), 3); + Assert.assertEquals(dir, new File("/path/to/directory")); + + dir = IOUtils.dirLevel(new File("/path/to/directory"), 4); + Assert.assertEquals(dir, new File("/path/to/directory")); + } + + @Test + public void testAbsolute() { + File dir = IOUtils.absolute(new File("/path/./to/./directory/.")); + Assert.assertEquals(dir, new File("/path/to/directory")); + + dir = IOUtils.absolute(new File("/")); + Assert.assertEquals(dir, new File("/")); + + dir = IOUtils.absolute(new File("/.")); + Assert.assertEquals(dir, new File("/")); + + dir = IOUtils.absolute(new File("/././.")); + Assert.assertEquals(dir, new File("/")); + + dir = IOUtils.absolute(new File("/./directory/.")); + Assert.assertEquals(dir, new File("/directory")); + + dir = IOUtils.absolute(new File("/./directory/./")); + Assert.assertEquals(dir, new File("/directory")); + + dir = IOUtils.absolute(new File("/./directory./")); + Assert.assertEquals(dir, new File("/directory.")); + + dir = IOUtils.absolute(new File("/./.directory/")); + Assert.assertEquals(dir, new File("/.directory")); + } + + @Test + public void testTail() throws IOException { + List lines = Arrays.asList( + "chr18_random 4262 3154410390 50 51", + "chr19_random 301858 3154414752 50 51", + "chr21_random 1679693 3154722662 50 51", + "chr22_random 257318 3156435963 50 51", + "chrX_random 1719168 3156698441 50 51"); + List tail = IOUtils.tail(new File(BaseTest.hg18Reference + ".fai"), 5); + Assert.assertEquals(tail.size(), 5); + for (int i = 0; i < 5; i++) + Assert.assertEquals(tail.get(i), lines.get(i)); + } + + @Test + public void testWriteSystemFile() throws IOException { + File temp = createTempFile("temp.", ".properties"); + try { + IOUtils.writeResource(new Resource("StingText.properties", null), temp); + } finally { + FileUtils.deleteQuietly(temp); + } + } + + @Test + public void testWriteSystemTempFile() throws IOException { + File temp = IOUtils.writeTempResource(new Resource("StingText.properties", null)); + try { + Assert.assertTrue(temp.getName().startsWith("StingText"), "File does not start with 'StingText.': " + temp); + Assert.assertTrue(temp.getName().endsWith(".properties"), "File does not end with '.properties': " + temp); + } finally { + FileUtils.deleteQuietly(temp); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMissingSystemFile() throws IOException { + File temp = createTempFile("temp.", ".properties"); + try { + IOUtils.writeResource(new Resource("MissingStingText.properties", null), temp); + } finally { + FileUtils.deleteQuietly(temp); + } + } + + @Test + public void testWriteRelativeFile() throws IOException { + File temp = createTempFile("temp.", ".properties"); + try { + IOUtils.writeResource(new Resource("/StingText.properties", IOUtils.class), temp); + } finally { + FileUtils.deleteQuietly(temp); + } + } + + @Test + public void testWriteRelativeTempFile() throws IOException { + File temp = IOUtils.writeTempResource(new Resource("/StingText.properties", IOUtils.class)); + try { + Assert.assertTrue(temp.getName().startsWith("StingText"), "File does not start with 'StingText.': " + temp); + Assert.assertTrue(temp.getName().endsWith(".properties"), "File does not end with '.properties': " + temp); + } finally { + FileUtils.deleteQuietly(temp); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMissingRelativeFile() throws IOException { + File temp = createTempFile("temp.", ".properties"); + try { + // Looking for /org/broadinstitute/sting/utils/file/StingText.properties + IOUtils.writeResource(new Resource("StingText.properties", IOUtils.class), temp); + } finally { + FileUtils.deleteQuietly(temp); + } + } + + @Test + public void testResourceProperties() { + Resource resource = new Resource("foo", Resource.class); + Assert.assertEquals(resource.getPath(), "foo"); + Assert.assertEquals(resource.getRelativeClass(), Resource.class); + } +} diff --git a/public/java/test/org/broadinstitute/sting/utils/runtime/ProcessControllerUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/runtime/ProcessControllerUnitTest.java new file mode 100644 index 0000000000..7a31ceee06 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/runtime/ProcessControllerUnitTest.java @@ -0,0 +1,517 @@ +/* + * 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.runtime; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.broadinstitute.sting.utils.io.IOUtils; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class ProcessControllerUnitTest extends BaseTest { + private static final String NL = String.format("%n"); + + @Test(timeOut = 60 * 1000) + public void testDestroyThreadLocal() throws InterruptedException { + for (int i = 0; i < 3; i++) { + final ProcessController controller = ProcessController.getThreadLocal(); + final ProcessSettings job = new ProcessSettings( + new String[] {"sh", "-c", "echo Hello World && sleep 600 && echo Goodbye"}); + job.getStdoutSettings().setBufferSize(-1); + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + System.out.println("BACK: Starting on background thread"); + ProcessOutput result = controller.exec(job); + // Assert in background thread doesn't make it to main thread but does print a trace. + Assert.assertTrue(result.getExitValue() != 0, "Destroy-attempted job returned zero exit status"); + System.out.println("BACK: Background thread exiting"); + } + }); + + System.out.println("MAIN: Starting background thread"); + t.start(); + System.out.println("MAIN: Sleeping main thread 3s"); + Thread.sleep(3000); + System.out.println("MAIN: Destroying job"); + controller.tryDestroy(); + System.out.println("MAIN: Not waiting on background thread to exit"); + // Using standard java.io this was blocking on linux. + // TODO: try again with NIO. + //t.join(); + //System.out.println("MAIN: Background thread exited"); + } + } + + @Test + public void testReuseAfterError() { + ProcessController controller = new ProcessController(); + + ProcessSettings job; + + for (int i = 0; i < 3; i++) { + // Test bad command + job = new ProcessSettings(new String[] {"no_such_command"}); + try { + controller.exec(job); + } catch (ReviewedStingException e) { + /* Was supposed to throw an exception */ + } + + // Test exit != 0 + job = new ProcessSettings(new String[] {"cat", "non_existent_file"}); + int exitValue = controller.exec(job).getExitValue(); + Assert.assertTrue(exitValue != 0, "'cat' non existent file returned 0"); + + // Text success + job = new ProcessSettings(new String[] {"echo", "Hello World"}); + exitValue = controller.exec(job).getExitValue(); + Assert.assertEquals(exitValue, 0, "Echo failed"); + } + } + + @Test + public void testEnvironment() { + String key = "MY_NEW_VAR"; + String value = "value is here"; + + ProcessSettings job = new ProcessSettings(new String[] {"sh", "-c", "echo $"+key}); + job.getStdoutSettings().setBufferSize(-1); + job.setRedirectErrorStream(true); + + Map env = new HashMap(System.getenv()); + env.put(key, value); + job.setEnvironment(env); + + ProcessController controller = new ProcessController(); + ProcessOutput result = controller.exec(job); + int exitValue = result.getExitValue(); + + Assert.assertEquals(exitValue, 0, "Echo environment variable failed"); + Assert.assertEquals(result.getStdout().getBufferString(), value + NL, "Echo environment returned unexpected output"); + } + + @Test + public void testDirectory() throws IOException { + File dir = null; + try { + dir = IOUtils.tempDir("temp.", "").getCanonicalFile(); + + ProcessSettings job = new ProcessSettings(new String[] {"pwd"}); + job.getStdoutSettings().setBufferSize(-1); + job.setRedirectErrorStream(true); + job.setDirectory(dir); + + ProcessController controller = new ProcessController(); + ProcessOutput result = controller.exec(job); + int exitValue = result.getExitValue(); + + Assert.assertEquals(exitValue, 0, "Getting working directory failed"); + + Assert.assertEquals(result.getStdout().getBufferString(), dir.getAbsolutePath() + NL, + "Setting/getting working directory returned unexpected output"); + } finally { + FileUtils.deleteQuietly(dir); + } + } + + @Test + public void testReadStdInBuffer() { + String bufferText = "Hello from buffer"; + ProcessSettings job = new ProcessSettings(new String[] {"cat"}); + job.getStdoutSettings().setBufferSize(-1); + job.setRedirectErrorStream(true); + job.getStdinSettings().setInputBuffer(bufferText); + + ProcessController controller = new ProcessController(); + ProcessOutput output = controller.exec(job); + + Assert.assertEquals(output.getStdout().getBufferString(), bufferText, + "Unexpected output from cat stdin buffer"); + } + + @Test + public void testReadStdInFile() { + File input = null; + try { + String fileText = "Hello from file"; + input = IOUtils.writeTempFile(fileText, "stdin.", ".txt", null); + + ProcessSettings job = new ProcessSettings(new String[] {"cat"}); + job.getStdoutSettings().setBufferSize(-1); + job.setRedirectErrorStream(true); + job.getStdinSettings().setInputFile(input); + + ProcessController controller = new ProcessController(); + ProcessOutput output = controller.exec(job); + + Assert.assertEquals(output.getStdout().getBufferString(), fileText, + "Unexpected output from cat stdin file"); + } finally { + FileUtils.deleteQuietly(input); + } + } + + @Test + public void testWriteStdOut() { + ProcessSettings job = new ProcessSettings(new String[] {"echo", "Testing to stdout"}); + // Not going to call the System.setOut() for now. Just running a basic visual test. + job.getStdoutSettings().printStandard(true); + job.setRedirectErrorStream(true); + + System.out.println("testWriteStdOut: Writing two lines to std out..."); + ProcessController controller = new ProcessController(); + controller.exec(job); + job.setCommand(new String[]{"cat", "non_existent_file"}); + controller.exec(job); + System.out.println("testWriteStdOut: ...two lines should have been printed to std out"); + } + + @Test + public void testErrorToOut() throws IOException { + File outFile = null; + File errFile = null; + try { + outFile = BaseTest.createTempFile("temp", ""); + errFile = BaseTest.createTempFile("temp", ""); + + ProcessSettings job = new ProcessSettings(new String[]{"cat", "non_existent_file"}); + job.getStdoutSettings().setOutputFile(outFile); + job.getStdoutSettings().setBufferSize(-1); + job.getStderrSettings().setOutputFile(errFile); + job.getStderrSettings().setBufferSize(-1); + job.setRedirectErrorStream(true); + + ProcessOutput result = new ProcessController().exec(job); + int exitValue = result.getExitValue(); + + Assert.assertTrue(exitValue != 0, "'cat' non existent file returned 0"); + + String fileString, bufferString; + + fileString = FileUtils.readFileToString(outFile); + Assert.assertTrue(fileString.length() > 0, "Out file was length 0"); + + bufferString = result.getStdout().getBufferString(); + Assert.assertTrue(bufferString.length() > 0, "Out buffer was length 0"); + + Assert.assertFalse(result.getStdout().isBufferTruncated(), "Out buffer was truncated"); + Assert.assertEquals(bufferString.length(), fileString.length(), "Out buffer length did not match file length"); + + fileString = FileUtils.readFileToString(errFile); + Assert.assertEquals(fileString, "", "Unexpected output to err file"); + + bufferString = result.getStderr().getBufferString(); + Assert.assertEquals(bufferString, "", "Unexepected output to err buffer"); + } finally { + FileUtils.deleteQuietly(outFile); + FileUtils.deleteQuietly(errFile); + } + } + + @Test + public void testErrorToErr() throws IOException { + File outFile = null; + File errFile = null; + try { + outFile = BaseTest.createTempFile("temp", ""); + errFile = BaseTest.createTempFile("temp", ""); + + ProcessSettings job = new ProcessSettings(new String[]{"cat", "non_existent_file"}); + job.getStdoutSettings().setOutputFile(outFile); + job.getStdoutSettings().setBufferSize(-1); + job.getStderrSettings().setOutputFile(errFile); + job.getStderrSettings().setBufferSize(-1); + job.setRedirectErrorStream(false); + + ProcessOutput result = new ProcessController().exec(job); + int exitValue = result.getExitValue(); + + Assert.assertTrue(exitValue != 0, "'cat' non existent file returned 0"); + + String fileString, bufferString; + + fileString = FileUtils.readFileToString(errFile); + Assert.assertTrue(fileString.length() > 0, "Err file was length 0"); + + bufferString = result.getStderr().getBufferString(); + Assert.assertTrue(bufferString.length() > 0, "Err buffer was length 0"); + + Assert.assertFalse(result.getStderr().isBufferTruncated(), "Err buffer was truncated"); + Assert.assertEquals(bufferString.length(), fileString.length(), "Err buffer length did not match file length"); + + fileString = FileUtils.readFileToString(outFile); + Assert.assertEquals(fileString, "", "Unexpected output to out file"); + + bufferString = result.getStdout().getBufferString(); + Assert.assertEquals(bufferString, "", "Unexepected output to out buffer"); + } finally { + FileUtils.deleteQuietly(outFile); + FileUtils.deleteQuietly(errFile); + } + } + + private static final String TRUNCATE_TEXT = "Hello World"; + private static final byte[] TRUNCATE_OUTPUT_BYTES = (TRUNCATE_TEXT + NL).getBytes(); + + /** + * @return Test truncating content vs. not truncating (run at -1/+1 size) + */ + @DataProvider(name = "truncateSizes") + public Object[][] getTruncateBufferSizes() { + int l = TRUNCATE_OUTPUT_BYTES.length; + return new Object[][]{ + new Object[]{0, 0}, + new Object[]{l, l}, + new Object[]{l + 1, l}, + new Object[]{l - 1, l - 1} + }; + } + + @Test(dataProvider = "truncateSizes") + public void testTruncateBuffer(int truncateLen, int expectedLen) { + byte[] expected = Arrays.copyOf(TRUNCATE_OUTPUT_BYTES, expectedLen); + + String[] command = {"echo", TRUNCATE_TEXT}; + ProcessController controller = new ProcessController(); + + ProcessSettings job = new ProcessSettings(command); + job.getStdoutSettings().setBufferSize(truncateLen); + ProcessOutput result = controller.exec(job); + + int exitValue = result.getExitValue(); + + Assert.assertEquals(exitValue, 0, + String.format("Echo returned %d: %s", exitValue, TRUNCATE_TEXT)); + + byte[] bufferBytes = result.getStdout().getBufferBytes(); + + Assert.assertEquals(bufferBytes, expected, + String.format("Output buffer didn't match (%d vs %d)", expected.length, bufferBytes.length)); + + boolean truncated = result.getStdout().isBufferTruncated(); + + Assert.assertEquals(truncated, TRUNCATE_OUTPUT_BYTES.length > truncateLen, + "Unexpected buffer truncation result"); + } + + private static final String[] LONG_COMMAND = getLongCommand(); + private static final String LONG_COMMAND_STRING = StringUtils.join(LONG_COMMAND, " "); + private static final String LONG_COMMAND_DESCRIPTION = ""; + + @DataProvider(name = "echoCommands") + public Object[][] getEchoCommands() { + + new EchoCommand(new String[]{"echo", "Hello", "World"}, "Hello World" + NL); + new EchoCommand(new String[]{"echo", "'Hello", "World"}, "'Hello World" + NL); + new EchoCommand(new String[]{"echo", "Hello", "World'"}, "Hello World'" + NL); + new EchoCommand(new String[]{"echo", "'Hello", "World'"}, "'Hello World'" + NL); + + String[] longCommand = new String[LONG_COMMAND.length + 1]; + longCommand[0] = "echo"; + System.arraycopy(LONG_COMMAND, 0, longCommand, 1, LONG_COMMAND.length); + new EchoCommand(longCommand, LONG_COMMAND_STRING + NL) { + @Override + public String toString() { + return LONG_COMMAND_DESCRIPTION; + } + }; + + return TestDataProvider.getTests(EchoCommand.class); + } + + @Test(dataProvider = "echoCommands") + public void testEcho(EchoCommand script) throws IOException { + File outputFile = null; + try { + outputFile = BaseTest.createTempFile("temp", ""); + + ProcessSettings job = new ProcessSettings(script.command); + if (script.output != null) { + job.getStdoutSettings().setOutputFile(outputFile); + job.getStdoutSettings().setBufferSize(script.output.getBytes().length); + } + + ProcessOutput result = new ProcessController().exec(job); + int exitValue = result.getExitValue(); + + Assert.assertEquals(exitValue, 0, + String.format("Echo returned %d: %s", exitValue, script)); + + if (script.output != null) { + + String fileString = FileUtils.readFileToString(outputFile); + Assert.assertEquals(fileString, script.output, + String.format("Output file didn't match (%d vs %d): %s", + fileString.length(), script.output.length(), script)); + + String bufferString = result.getStdout().getBufferString(); + Assert.assertEquals(bufferString, script.output, + String.format("Output content didn't match (%d vs %d): %s", + bufferString.length(), script.output.length(), script)); + + Assert.assertFalse(result.getStdout().isBufferTruncated(), + "Output content was truncated: " + script); + } + } finally { + FileUtils.deleteQuietly(outputFile); + } + } + + @Test(expectedExceptions = ReviewedStingException.class) + public void testUnableToStart() { + ProcessSettings job = new ProcessSettings(new String[]{"no_such_command"}); + new ProcessController().exec(job); + } + + @DataProvider(name = "scriptCommands") + public Object[][] getScriptCommands() { + new ScriptCommand(true, "echo Hello World", "Hello World" + NL); + new ScriptCommand(false, "echo 'Hello World", null); + new ScriptCommand(false, "echo Hello World'", null); + new ScriptCommand(true, "echo 'Hello World'", "Hello World" + NL); + new ScriptCommand(true, "echo \"Hello World\"", "Hello World" + NL); + new ScriptCommand(false, "no_such_echo Hello World", null); + new ScriptCommand(true, "echo #", NL); + new ScriptCommand(true, "echo \\#", "#" + NL); + new ScriptCommand(true, "echo \\\\#", "\\#" + NL); + + new ScriptCommand(true, "echo " + LONG_COMMAND_STRING, LONG_COMMAND_STRING + NL) { + @Override + public String toString() { + return LONG_COMMAND_DESCRIPTION; + } + }; + + return TestDataProvider.getTests(ScriptCommand.class); + } + + @Test(dataProvider = "scriptCommands") + public void testScript(ScriptCommand script) throws IOException { + File scriptFile = null; + File outputFile = null; + try { + scriptFile = writeScript(script.content); + outputFile = BaseTest.createTempFile("temp", ""); + + ProcessSettings job = new ProcessSettings(new String[]{"sh", scriptFile.getAbsolutePath()}); + if (script.output != null) { + job.getStdoutSettings().setOutputFile(outputFile); + job.getStdoutSettings().setBufferSize(script.output.getBytes().length); + } + + ProcessOutput result = new ProcessController().exec(job); + int exitValue = result.getExitValue(); + + Assert.assertEquals(exitValue == 0, script.succeed, + String.format("Script returned %d: %s", exitValue, script)); + + if (script.output != null) { + + String fileString = FileUtils.readFileToString(outputFile); + Assert.assertEquals(fileString, script.output, + String.format("Output file didn't match (%d vs %d): %s", + fileString.length(), script.output.length(), script)); + + String bufferString = result.getStdout().getBufferString(); + Assert.assertEquals(bufferString, script.output, + String.format("Output content didn't match (%d vs %d): %s", + bufferString.length(), script.output.length(), script)); + + Assert.assertFalse(result.getStdout().isBufferTruncated(), + "Output content was truncated: " + script); + } + } finally { + FileUtils.deleteQuietly(scriptFile); + FileUtils.deleteQuietly(outputFile); + } + } + + private static String[] getLongCommand() { + // This command fails on some systems with a 4096 character limit when run via the old sh -c "echo ...", + // but works on the same systems when run via sh