From 09d66d592e36f0e93907b790ef2205b479d3d0ef Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 7 Jun 2019 16:56:16 -0400 Subject: [PATCH 1/5] Added in capability to filter based on ratio of soft clipped bases. --- .../engine/filters/OverclippedReadFilter.java | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java index 1b14f39632a..9125b8bb418 100644 --- a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java +++ b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java @@ -2,6 +2,8 @@ import htsjdk.samtools.CigarElement; import htsjdk.samtools.CigarOperator; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.broadinstitute.barclay.argparser.Argument; import org.broadinstitute.barclay.help.DocumentedFeature; import org.broadinstitute.hellbender.cmdline.ReadFilterArgumentDefinitions; @@ -21,14 +23,24 @@ */ @DocumentedFeature(groupName= HelpConstants.DOC_CAT_READFILTERS, groupSummary=HelpConstants.DOC_CAT_READFILTERS_SUMMARY, summary = "Filter out reads that are over-soft-clipped") public final class OverclippedReadFilter extends ReadFilter{ - static final long serialVersionUID = 1L; + private final Logger logger = LogManager.getLogger(this.getClass()); + + private static final double DEFAULT_MIN_SOFT_CLIPPED_RATIO = Double.MIN_VALUE; @Argument(fullName = ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, doc = "Minimum number of aligned bases", - optional = true) + optional = true, + mutex = {"soft-clipped-ratio-threshold"}) public int minimumSequenceLength = 30; + @Argument(fullName = "soft-clipped-ratio-threshold", + doc = "Minimum ratio of soft clipped bases to total bases in read for read to be included.", + optional = true, + mutex = {ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME} + ) + public double minimumSoftClippedRatio = DEFAULT_MIN_SOFT_CLIPPED_RATIO; + @Argument(fullName = ReadFilterArgumentDefinitions.DONT_REQUIRE_SOFT_CLIPS_BOTH_ENDS_NAME, doc = "Allow a read to be filtered out based on having only 1 soft-clipped block. By default, both ends must " + "have a soft-clipped block, setting this flag requires only 1 soft-clipped block", @@ -43,8 +55,7 @@ public OverclippedReadFilter(final int minimumSequenceLength, final boolean doNo this.doNotRequireSoftClipsOnBothEnds = doNotRequireSoftClipsOnBothEnds; } - @Override - public boolean test(final GATKRead read) { + private boolean testMinSequenceLength(final GATKRead read) { int alignedLength = 0; int softClipBlocks = 0; int minSoftClipBlocks = doNotRequireSoftClipsOnBothEnds ? 1 : 2; @@ -65,4 +76,34 @@ public boolean test(final GATKRead read) { return(alignedLength >= minimumSequenceLength || softClipBlocks < minSoftClipBlocks); } + + private boolean testMinSoftClippedRatio(final GATKRead read) { + int numSoftClippedBases = 0; + for ( final CigarElement element : read.getCigarElements() ) { + if (element.getOperator() == CigarOperator.S) { + numSoftClippedBases += element.getLength(); + } + } + + final double softClipRatio = ((double)numSoftClippedBases / (double)read.getLength()); + + logger.debug( " Num Soft Clipped Bases: " + numSoftClippedBases ); + logger.debug( " Read Length: " + read.getLength() ); + logger.debug( " Read Filter status: (" + softClipRatio + " > " + minimumSoftClippedRatio + ")" ); + logger.debug( " Read Filter status: " + (softClipRatio > minimumSoftClippedRatio) ); + + return softClipRatio > minimumSoftClippedRatio; + } + + @Override + public boolean test(final GATKRead read) { + logger.debug( "About to test read: " + read.toString()); + // If we didn't specify the clipping ratio, we use the min sequence length test: + if ( minimumSoftClippedRatio == DEFAULT_MIN_SOFT_CLIPPED_RATIO ) { + return testMinSequenceLength(read); + } + else { + return testMinSoftClippedRatio(read); + } + } } From 8a5a3708bc3faf7dac5a1525dd798603c90d8ad3 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jun 2019 15:51:15 -0400 Subject: [PATCH 2/5] Added in filter for leading/trailing soft clips only. Added in tests. --- .../engine/filters/OverclippedReadFilter.java | 110 +++++++++++++--- .../OverclippedReadFilterUnitTest.java | 123 +++++++++++++++++- 2 files changed, 207 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java index 9125b8bb418..eceaa80b5c7 100644 --- a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java +++ b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java @@ -1,5 +1,6 @@ package org.broadinstitute.hellbender.engine.filters; +import com.google.common.annotations.VisibleForTesting; import htsjdk.samtools.CigarElement; import htsjdk.samtools.CigarOperator; import org.apache.logging.log4j.LogManager; @@ -22,7 +23,7 @@ *

Note: Consecutive soft-clipped blocks are treated as a single block. For example, 1S2S10M1S2S is treated as 3S10M3S

*/ @DocumentedFeature(groupName= HelpConstants.DOC_CAT_READFILTERS, groupSummary=HelpConstants.DOC_CAT_READFILTERS_SUMMARY, summary = "Filter out reads that are over-soft-clipped") -public final class OverclippedReadFilter extends ReadFilter{ +public final class OverclippedReadFilter extends ReadFilter { static final long serialVersionUID = 1L; private final Logger logger = LogManager.getLogger(this.getClass()); @@ -30,35 +31,50 @@ public final class OverclippedReadFilter extends ReadFilter{ @Argument(fullName = ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, doc = "Minimum number of aligned bases", - optional = true, - mutex = {"soft-clipped-ratio-threshold"}) - public int minimumSequenceLength = 30; + optional = true) + private int minAlignedBases = 30; + @VisibleForTesting @Argument(fullName = "soft-clipped-ratio-threshold", - doc = "Minimum ratio of soft clipped bases to total bases in read for read to be included.", + doc = "Minimum ratio of soft clipped bases (anywhere in the cigar string) to total bases in read for read to be included.", + optional = true, + mutex = {ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, "leading-trailing-soft-clipped-ratio"} + ) + double minimumSoftClippedRatio = DEFAULT_MIN_SOFT_CLIPPED_RATIO; + + @VisibleForTesting + @Argument(fullName = "leading-trailing-soft-clipped-ratio", + doc = "Minimum ratio of soft clipped bases (leading / trailing the cigar string) to total bases in read for read to be included.", optional = true, - mutex = {ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME} + mutex = {ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, "soft-clipped-ratio-threshold"} ) - public double minimumSoftClippedRatio = DEFAULT_MIN_SOFT_CLIPPED_RATIO; + double minimumLeadingTrailingSoftClippedRatio = DEFAULT_MIN_SOFT_CLIPPED_RATIO; @Argument(fullName = ReadFilterArgumentDefinitions.DONT_REQUIRE_SOFT_CLIPS_BOTH_ENDS_NAME, doc = "Allow a read to be filtered out based on having only 1 soft-clipped block. By default, both ends must " + "have a soft-clipped block, setting this flag requires only 1 soft-clipped block", - optional = true) - public boolean doNotRequireSoftClipsOnBothEnds; + optional = true, + mutex = {"soft-clipped-ratio-threshold", "leading-trailing-soft-clipped-ratio"}) + private boolean doNotRequireSoftClipsOnBothEnds; // Command line parser requires a no-arg constructor public OverclippedReadFilter() {} - public OverclippedReadFilter(final int minimumSequenceLength, final boolean doNotRequireSoftClipsOnBothEnds) { - this.minimumSequenceLength = minimumSequenceLength; + @VisibleForTesting + OverclippedReadFilter(final int minAlignedBases) { + this.minAlignedBases = minAlignedBases; + } + + @VisibleForTesting + OverclippedReadFilter(final int minAlignedBases, final boolean doNotRequireSoftClipsOnBothEnds) { + this.minAlignedBases = minAlignedBases; this.doNotRequireSoftClipsOnBothEnds = doNotRequireSoftClipsOnBothEnds; } private boolean testMinSequenceLength(final GATKRead read) { int alignedLength = 0; int softClipBlocks = 0; - int minSoftClipBlocks = doNotRequireSoftClipsOnBothEnds ? 1 : 2; + final int minSoftClipBlocks = doNotRequireSoftClipsOnBothEnds ? 1 : 2; CigarOperator prevOperator = null; for ( final CigarElement element : read.getCigarElements() ) { @@ -74,36 +90,92 @@ private boolean testMinSequenceLength(final GATKRead read) { prevOperator = element.getOperator(); } - return(alignedLength >= minimumSequenceLength || softClipBlocks < minSoftClipBlocks); + return (alignedLength >= minAlignedBases || softClipBlocks < minSoftClipBlocks); } private boolean testMinSoftClippedRatio(final GATKRead read) { + int totalLength = 0; + int alignedLength = 0; int numSoftClippedBases = 0; for ( final CigarElement element : read.getCigarElements() ) { if (element.getOperator() == CigarOperator.S) { numSoftClippedBases += element.getLength(); + } else if ( element.getOperator().consumesReadBases() ) { // M, I, X, and EQ (S was already accounted for above) + alignedLength += element.getLength(); } + + totalLength += element.getLength(); } - final double softClipRatio = ((double)numSoftClippedBases / (double)read.getLength()); + final double softClipRatio = ((double)numSoftClippedBases / (double)totalLength); logger.debug( " Num Soft Clipped Bases: " + numSoftClippedBases ); logger.debug( " Read Length: " + read.getLength() ); logger.debug( " Read Filter status: (" + softClipRatio + " > " + minimumSoftClippedRatio + ")" ); logger.debug( " Read Filter status: " + (softClipRatio > minimumSoftClippedRatio) ); + logger.debug( " Aligned Length: " + alignedLength ); + + return (alignedLength >= minAlignedBases) && (softClipRatio > minimumSoftClippedRatio); + } + + private boolean testMinLeadingTrailingSoftClippedRatio(final GATKRead read) { + + if ( read.getCigarElements().size() < 1 ) { + return false; + } - return softClipRatio > minimumSoftClippedRatio; + // Get the index of the last cigar element: + final int lastCigarOpIndex = read.getCigarElements().size() - 1; + + // Calculate the number of bases here: + // Note: we don't want to double-count if the read has only 1 cigar operator. + final int numLeadingTrailingSoftClippedBases = + (read.getCigarElement(0).getOperator() == CigarOperator.S ? read.getCigarElement(0).getLength() : 0) + + + ((lastCigarOpIndex != 0) && (read.getCigarElement(lastCigarOpIndex).getOperator() == CigarOperator.S) + ? read.getCigarElement(lastCigarOpIndex).getLength() + : 0); + + // Determine lengths: + int totalLength = 0; + int alignedLength = 0; + for ( final CigarElement element : read.getCigarElements() ) { + if ( (element.getOperator() != CigarOperator.S) && element.getOperator().consumesReadBases() ) { // M, I, X, and EQ (S was already accounted for above) + alignedLength += element.getLength(); + } + totalLength += element.getLength(); + } + + // Calculate the ratio: + final double softClipRatio = ((double)numLeadingTrailingSoftClippedBases / (double)totalLength); + + logger.debug( " Num Leading / Trailing Soft Clipped Bases: " + numLeadingTrailingSoftClippedBases ); + logger.debug( " Read Length: " + read.getLength() ); + logger.debug( " Read Filter status: (" + softClipRatio + " > " + minimumLeadingTrailingSoftClippedRatio + ")" ); + logger.debug( " Read Filter status: " + (softClipRatio > minimumLeadingTrailingSoftClippedRatio) ); + logger.debug( " Aligned Length: " + alignedLength ); + + return (alignedLength >= minAlignedBases) && (softClipRatio > minimumLeadingTrailingSoftClippedRatio); } @Override public boolean test(final GATKRead read) { logger.debug( "About to test read: " + read.toString()); - // If we didn't specify the clipping ratio, we use the min sequence length test: - if ( minimumSoftClippedRatio == DEFAULT_MIN_SOFT_CLIPPED_RATIO ) { - return testMinSequenceLength(read); + + // NOTE: Since we have mutex'd the args for the clipping ratios, we only need to see if they + // have been specified. If they have, that's the filter logic we're using. + + // If we specified the clipping ratio, we use the min sequence length test: + if ( minimumSoftClippedRatio != DEFAULT_MIN_SOFT_CLIPPED_RATIO ) { + return testMinSoftClippedRatio(read); + } + // If we specified the leading/trailing clipping ratio, we use the min sequence length test: + if ( minimumLeadingTrailingSoftClippedRatio != DEFAULT_MIN_SOFT_CLIPPED_RATIO ) { + return testMinLeadingTrailingSoftClippedRatio(read); } + // Default condition is to test against sequence length: else { - return testMinSoftClippedRatio(read); + return testMinSequenceLength(read); } } } diff --git a/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java b/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java index b4e761c7764..f970a7e4a8a 100644 --- a/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java +++ b/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java @@ -24,22 +24,131 @@ public class OverclippedReadFilterUnitTest extends GATKBaseTest { private final SAMFileHeader header= ArtificialReadUtils.createArtificialSamHeaderWithGroups(CHR_COUNT, CHR_START, CHR_SIZE, GROUP_COUNT); + private GATKRead buildSAMRead(final String cigarString) { + final Cigar cigar = TextCigarCodec.decode(cigarString); + return ArtificialReadUtils.createArtificialRead(header, cigar); + } @Test(dataProvider= "OverclippedDataProvider") - public void testOverclippedFilter(final String cigarString, boolean doNotRequireSoftClipsOnBothEnds, final boolean expectedResult) { + public void testOverclippedFilter(final String cigarString, + final boolean doNotRequireSoftClipsOnBothEnds, + final boolean expectedResult) { - final OverclippedReadFilter filter = new OverclippedReadFilter(30, false); - filter.doNotRequireSoftClipsOnBothEnds = doNotRequireSoftClipsOnBothEnds; + final OverclippedReadFilter filter = new OverclippedReadFilter(30, doNotRequireSoftClipsOnBothEnds); final GATKRead read = buildSAMRead(cigarString); Assert.assertEquals(filter.test(read), expectedResult, cigarString); } - private GATKRead buildSAMRead(final String cigarString) { - final Cigar cigar = TextCigarCodec.decode(cigarString); - return ArtificialReadUtils.createArtificialRead(header, cigar); + @Test(dataProvider= "OverclippedSoftClipRatioDataProvider") + public void testOverclippedSoftClipRatioFilter(final String cigarString, + final double clipRatio, + final boolean expectedResult) { + + final OverclippedReadFilter filter = new OverclippedReadFilter(10); + filter.minimumSoftClippedRatio = clipRatio; + + final GATKRead read = buildSAMRead(cigarString); + Assert.assertEquals(filter.test(read), expectedResult, cigarString); + } + + @Test(dataProvider= "OverclippedLeadingTrailingSoftClipRatioDataProvider") + public void testOverclippedLeadingTrailingSoftClipRatioFilter(final String cigarString, + final double clipRatio, + final boolean expectedResult) { + + final OverclippedReadFilter filter = new OverclippedReadFilter(10); + filter.minimumLeadingTrailingSoftClippedRatio = clipRatio; + + final GATKRead read = buildSAMRead(cigarString); + Assert.assertEquals(filter.test(read), expectedResult, cigarString); + } + + @DataProvider(name = "OverclippedSoftClipRatioDataProvider") + public Iterator overclippedSoftClipRatioDataProvider() { + final List testData = new LinkedList<>(); + + // --------------------------------------- + // Null / trivial cases: + testData.add(new Object[] { "", 0.1, false }); + testData.add(new Object[] { "10H", 0.1, false }); + + // --------------------------------------- + // Min bases test: + testData.add(new Object[] { "20S5M", 0.2, false }); // 20/25 = .8 + testData.add(new Object[] { "20S20M", 0.2, true }); // 20/40 = .5 + + // --------------------------------------- + // Soft clip ratio test: + + testData.add(new Object[] { "1S1M1S17M", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "1S1M2S17M", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "1S1M3S17M", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "1S1M4S17M", 0.2, true }); // 5/23 = .217 + testData.add(new Object[] { "1S1M5S17M", 0.2, true }); // 6/24 = .250 + testData.add(new Object[] { "1S1M6S17M", 0.2, true }); // 7/25 = .280 + + // --------------------------------------- + // Soft clip placement: + + testData.add(new Object[] { "101S100M", 0.5, true }); + testData.add(new Object[] { "100M101S", 0.5, true }); + testData.add(new Object[] { "25H20S10M20S10M20S10M20S10M20S10M20S25H", 0.5, true }); + + return testData.iterator(); + } + + @DataProvider(name = "OverclippedLeadingTrailingSoftClipRatioDataProvider") + public Iterator overclippedLeadingTrailingSoftClipRatioDataProvider() { + final List testData = new LinkedList<>(); + + // --------------------------------------- + // Null / trivial cases: + testData.add(new Object[] { "", 0.1, false }); + testData.add(new Object[] { "10H", 0.1, false }); + + // --------------------------------------- + // Min bases test: + testData.add(new Object[] { "20S5M", 0.2, false }); // 20/25 = .8 + testData.add(new Object[] { "20S20M", 0.2, true }); // 20/40 = .5 + + // --------------------------------------- + // Soft clip ratio test: + + // Non-leading/-trailing + testData.add(new Object[] { "1S1M1S17M", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "1S1M2S17M", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "1S1M3S17M", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "1S1M4S17M", 0.2, false }); // 5/23 = .217 + testData.add(new Object[] { "1S1M5S17M", 0.2, false }); // 6/24 = .250 + testData.add(new Object[] { "1S1M6S17M", 0.2, false }); // 7/25 = .280 + + // Leading: + testData.add(new Object[] { "2S1S1S16M", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "3S1S1S16M", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "4S1S1S16M", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "5S1S1S16M", 0.2, true }); // 5/23 = .217 + testData.add(new Object[] { "6S1S1S16M", 0.2, true }); // 6/24 = .250 + testData.add(new Object[] { "7S1S1S16M", 0.2, true }); // 7/25 = .280 + + // Trailing: + testData.add(new Object[] { "1M1S16M2S", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "1M1S16M3S", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "1M1S16M4S", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "1M1S16M5S", 0.2, true }); // 5/23 = .217 + testData.add(new Object[] { "1M1S16M6S", 0.2, true }); // 6/24 = .250 + testData.add(new Object[] { "1M1S16M7S", 0.2, true }); // 7/25 = .280 + + // --------------------------------------- + // Soft clip placement: + + testData.add(new Object[] { "101S100M", 0.5, true }); + testData.add(new Object[] { "100M101S", 0.5, true }); + testData.add(new Object[] { "25H20S10M20S10M20S10M20S10M20S10M20S25H", 0.5, false }); + + return testData.iterator(); } - @DataProvider(name= "OverclippedDataProvider") + @DataProvider(name = "OverclippedDataProvider") public Iterator overclippedDataProvider() { final List result = new LinkedList<>(); From 9e3f0c21d8b09831529430a1ac1c09012c318694 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 13 Jun 2019 13:51:10 -0400 Subject: [PATCH 3/5] Refactored filter into new class: SoftClippedReadFilter Addressed review comments. --- .../ReadFilterArgumentDefinitions.java | 5 + .../engine/filters/OverclippedReadFilter.java | 107 +------------- .../engine/filters/SoftClippedReadFilter.java | 127 ++++++++++++++++ .../OverclippedReadFilterUnitTest.java | 109 -------------- .../SoftClippedReadFilterUnitTest.java | 139 ++++++++++++++++++ 5 files changed, 273 insertions(+), 214 deletions(-) create mode 100644 src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java create mode 100644 src/test/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilterUnitTest.java diff --git a/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java b/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java index b56c53aab36..e6a42f8dab9 100644 --- a/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java +++ b/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java @@ -48,4 +48,9 @@ private ReadFilterArgumentDefinitions(){} public static final String SAMPLE_NAME = "sample"; public static final String KEEP_INTERVAL_NAME = "keep-intervals"; + + public static final String SOFT_CLIPPED_RATIO_THRESHOLD = "soft-clipped-ratio-threshold"; + public static final String SOFT_CLIPPED_LEADING_TRAILING_RATIO_THRESHOLD = "soft-clipped-leading-trailing-ratio"; + + public static final String INVERT_FILTER = "invert-filter"; } diff --git a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java index eceaa80b5c7..21ed7933671 100644 --- a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java +++ b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java @@ -27,29 +27,11 @@ public final class OverclippedReadFilter extends ReadFilter { static final long serialVersionUID = 1L; private final Logger logger = LogManager.getLogger(this.getClass()); - private static final double DEFAULT_MIN_SOFT_CLIPPED_RATIO = Double.MIN_VALUE; - @Argument(fullName = ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, doc = "Minimum number of aligned bases", optional = true) private int minAlignedBases = 30; - @VisibleForTesting - @Argument(fullName = "soft-clipped-ratio-threshold", - doc = "Minimum ratio of soft clipped bases (anywhere in the cigar string) to total bases in read for read to be included.", - optional = true, - mutex = {ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, "leading-trailing-soft-clipped-ratio"} - ) - double minimumSoftClippedRatio = DEFAULT_MIN_SOFT_CLIPPED_RATIO; - - @VisibleForTesting - @Argument(fullName = "leading-trailing-soft-clipped-ratio", - doc = "Minimum ratio of soft clipped bases (leading / trailing the cigar string) to total bases in read for read to be included.", - optional = true, - mutex = {ReadFilterArgumentDefinitions.FILTER_TOO_SHORT_NAME, "soft-clipped-ratio-threshold"} - ) - double minimumLeadingTrailingSoftClippedRatio = DEFAULT_MIN_SOFT_CLIPPED_RATIO; - @Argument(fullName = ReadFilterArgumentDefinitions.DONT_REQUIRE_SOFT_CLIPS_BOTH_ENDS_NAME, doc = "Allow a read to be filtered out based on having only 1 soft-clipped block. By default, both ends must " + "have a soft-clipped block, setting this flag requires only 1 soft-clipped block", @@ -71,7 +53,8 @@ public OverclippedReadFilter() {} this.doNotRequireSoftClipsOnBothEnds = doNotRequireSoftClipsOnBothEnds; } - private boolean testMinSequenceLength(final GATKRead read) { + @Override + public boolean test(final GATKRead read) { int alignedLength = 0; int softClipBlocks = 0; final int minSoftClipBlocks = doNotRequireSoftClipsOnBothEnds ? 1 : 2; @@ -92,90 +75,4 @@ private boolean testMinSequenceLength(final GATKRead read) { return (alignedLength >= minAlignedBases || softClipBlocks < minSoftClipBlocks); } - - private boolean testMinSoftClippedRatio(final GATKRead read) { - int totalLength = 0; - int alignedLength = 0; - int numSoftClippedBases = 0; - for ( final CigarElement element : read.getCigarElements() ) { - if (element.getOperator() == CigarOperator.S) { - numSoftClippedBases += element.getLength(); - } else if ( element.getOperator().consumesReadBases() ) { // M, I, X, and EQ (S was already accounted for above) - alignedLength += element.getLength(); - } - - totalLength += element.getLength(); - } - - final double softClipRatio = ((double)numSoftClippedBases / (double)totalLength); - - logger.debug( " Num Soft Clipped Bases: " + numSoftClippedBases ); - logger.debug( " Read Length: " + read.getLength() ); - logger.debug( " Read Filter status: (" + softClipRatio + " > " + minimumSoftClippedRatio + ")" ); - logger.debug( " Read Filter status: " + (softClipRatio > minimumSoftClippedRatio) ); - logger.debug( " Aligned Length: " + alignedLength ); - - return (alignedLength >= minAlignedBases) && (softClipRatio > minimumSoftClippedRatio); - } - - private boolean testMinLeadingTrailingSoftClippedRatio(final GATKRead read) { - - if ( read.getCigarElements().size() < 1 ) { - return false; - } - - // Get the index of the last cigar element: - final int lastCigarOpIndex = read.getCigarElements().size() - 1; - - // Calculate the number of bases here: - // Note: we don't want to double-count if the read has only 1 cigar operator. - final int numLeadingTrailingSoftClippedBases = - (read.getCigarElement(0).getOperator() == CigarOperator.S ? read.getCigarElement(0).getLength() : 0) - + - ((lastCigarOpIndex != 0) && (read.getCigarElement(lastCigarOpIndex).getOperator() == CigarOperator.S) - ? read.getCigarElement(lastCigarOpIndex).getLength() - : 0); - - // Determine lengths: - int totalLength = 0; - int alignedLength = 0; - for ( final CigarElement element : read.getCigarElements() ) { - if ( (element.getOperator() != CigarOperator.S) && element.getOperator().consumesReadBases() ) { // M, I, X, and EQ (S was already accounted for above) - alignedLength += element.getLength(); - } - totalLength += element.getLength(); - } - - // Calculate the ratio: - final double softClipRatio = ((double)numLeadingTrailingSoftClippedBases / (double)totalLength); - - logger.debug( " Num Leading / Trailing Soft Clipped Bases: " + numLeadingTrailingSoftClippedBases ); - logger.debug( " Read Length: " + read.getLength() ); - logger.debug( " Read Filter status: (" + softClipRatio + " > " + minimumLeadingTrailingSoftClippedRatio + ")" ); - logger.debug( " Read Filter status: " + (softClipRatio > minimumLeadingTrailingSoftClippedRatio) ); - logger.debug( " Aligned Length: " + alignedLength ); - - return (alignedLength >= minAlignedBases) && (softClipRatio > minimumLeadingTrailingSoftClippedRatio); - } - - @Override - public boolean test(final GATKRead read) { - logger.debug( "About to test read: " + read.toString()); - - // NOTE: Since we have mutex'd the args for the clipping ratios, we only need to see if they - // have been specified. If they have, that's the filter logic we're using. - - // If we specified the clipping ratio, we use the min sequence length test: - if ( minimumSoftClippedRatio != DEFAULT_MIN_SOFT_CLIPPED_RATIO ) { - return testMinSoftClippedRatio(read); - } - // If we specified the leading/trailing clipping ratio, we use the min sequence length test: - if ( minimumLeadingTrailingSoftClippedRatio != DEFAULT_MIN_SOFT_CLIPPED_RATIO ) { - return testMinLeadingTrailingSoftClippedRatio(read); - } - // Default condition is to test against sequence length: - else { - return testMinSequenceLength(read); - } - } } diff --git a/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java b/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java new file mode 100644 index 00000000000..afc437a1895 --- /dev/null +++ b/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java @@ -0,0 +1,127 @@ +package org.broadinstitute.hellbender.engine.filters; + +import com.google.common.annotations.VisibleForTesting; +import htsjdk.samtools.CigarElement; +import htsjdk.samtools.CigarOperator; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.help.DocumentedFeature; +import org.broadinstitute.hellbender.cmdline.ReadFilterArgumentDefinitions; +import org.broadinstitute.hellbender.exceptions.UserException; +import org.broadinstitute.hellbender.utils.help.HelpConstants; +import org.broadinstitute.hellbender.utils.read.GATKRead; + +/** + * Filter out reads where the ratio of soft-clipped bases to total bases exceeds some given value. + * + * Can choose to filter by number of soft-clipped bases, or by the leading/trailing soft-clipped bases only. + */ +@DocumentedFeature( + groupName=HelpConstants.DOC_CAT_READFILTERS, + groupSummary=HelpConstants.DOC_CAT_READFILTERS_SUMMARY, + summary = "Filter out reads that are over-soft-clipped") +public final class SoftClippedReadFilter extends ReadFilter { + static final long serialVersionUID = 1L; + private final Logger logger = LogManager.getLogger(this.getClass()); + + @VisibleForTesting + @Argument(fullName = ReadFilterArgumentDefinitions.INVERT_FILTER, + doc = "Inverts the results from this filter, causing all variants that would pass to fail and visa-versa.", + optional = true + ) + boolean doInvertFilter = false; + + @VisibleForTesting + @Argument(fullName = ReadFilterArgumentDefinitions.SOFT_CLIPPED_RATIO_THRESHOLD, + doc = "Threshold ratio of soft clipped bases (anywhere in the cigar string) to total bases in read for read to be filtered.", + optional = true, + mutex = { ReadFilterArgumentDefinitions.SOFT_CLIPPED_LEADING_TRAILING_RATIO_THRESHOLD } + ) + Double minimumSoftClippedRatio = null; + + @VisibleForTesting + @Argument(fullName = ReadFilterArgumentDefinitions.SOFT_CLIPPED_LEADING_TRAILING_RATIO_THRESHOLD, + doc = "Threshold ratio of soft clipped bases (leading / trailing the cigar string) to total bases in read for read to be filtered.", + optional = true, + mutex = {ReadFilterArgumentDefinitions.SOFT_CLIPPED_RATIO_THRESHOLD} + ) + Double minimumLeadingTrailingSoftClippedRatio = null; + + // Command line parser requires a no-arg constructor + public SoftClippedReadFilter() {} + + private boolean testMinSoftClippedRatio(final GATKRead read) { + int totalLength = 0; + int numSoftClippedBases = 0; + for ( final CigarElement element : read.getCigarElements() ) { + if (element.getOperator() == CigarOperator.S) { + numSoftClippedBases += element.getLength(); + } + totalLength += element.getLength(); + } + + final double softClipRatio = ((double)numSoftClippedBases / (double)totalLength); + + return softClipRatio > minimumSoftClippedRatio; + } + + private boolean testMinLeadingTrailingSoftClippedRatio(final GATKRead read) { + + if ( read.getCigarElements().size() < 1 ) { + return false; + } + + // Get the index of the last cigar element: + final int lastCigarOpIndex = read.getCigarElements().size() - 1; + + // Calculate the number of bases here: + // Note: we don't want to double-count if the read has only 1 cigar operator. + final int numLeadingTrailingSoftClippedBases = + (read.getCigarElement(0).getOperator() == CigarOperator.S ? read.getCigarElement(0).getLength() : 0) + + + ((lastCigarOpIndex != 0) && (read.getCigarElement(lastCigarOpIndex).getOperator() == CigarOperator.S) + ? read.getCigarElement(lastCigarOpIndex).getLength() + : 0); + + // Determine length: + final int totalLength = read.getCigarElements().stream() + .mapToInt( CigarElement::getLength ) + .sum(); + + // Calculate the ratio: + final double softClipRatio = ((double)numLeadingTrailingSoftClippedBases / (double)totalLength); + + return softClipRatio > minimumLeadingTrailingSoftClippedRatio; + } + + @Override + public boolean test(final GATKRead read) { + + final boolean result; + + // NOTE: Since we have mutex'd the args for the clipping ratios, we only need to see if they + // have been specified. If they have, that's the filter logic we're using. + // If we specified the clipping ratio, we use the min sequence length test: + if ( minimumSoftClippedRatio != null ) { + result = testMinSoftClippedRatio(read); + } + // If we specified the leading/trailing clipping ratio, we use the min sequence length test: + else if ( minimumLeadingTrailingSoftClippedRatio != null ) { + result = testMinLeadingTrailingSoftClippedRatio(read); + } + else { + throw new UserException( + "Must provide one of the following arguments: " + + ReadFilterArgumentDefinitions.SOFT_CLIPPED_RATIO_THRESHOLD + "," + + ReadFilterArgumentDefinitions.SOFT_CLIPPED_LEADING_TRAILING_RATIO_THRESHOLD + ); + } + + // Check for if we want to invert our results: + if ( doInvertFilter ) { + return !result; + } + return result; + } +} diff --git a/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java b/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java index f970a7e4a8a..9c26ed7c3f1 100644 --- a/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java +++ b/src/test/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilterUnitTest.java @@ -39,115 +39,6 @@ public void testOverclippedFilter(final String cigarString, Assert.assertEquals(filter.test(read), expectedResult, cigarString); } - @Test(dataProvider= "OverclippedSoftClipRatioDataProvider") - public void testOverclippedSoftClipRatioFilter(final String cigarString, - final double clipRatio, - final boolean expectedResult) { - - final OverclippedReadFilter filter = new OverclippedReadFilter(10); - filter.minimumSoftClippedRatio = clipRatio; - - final GATKRead read = buildSAMRead(cigarString); - Assert.assertEquals(filter.test(read), expectedResult, cigarString); - } - - @Test(dataProvider= "OverclippedLeadingTrailingSoftClipRatioDataProvider") - public void testOverclippedLeadingTrailingSoftClipRatioFilter(final String cigarString, - final double clipRatio, - final boolean expectedResult) { - - final OverclippedReadFilter filter = new OverclippedReadFilter(10); - filter.minimumLeadingTrailingSoftClippedRatio = clipRatio; - - final GATKRead read = buildSAMRead(cigarString); - Assert.assertEquals(filter.test(read), expectedResult, cigarString); - } - - @DataProvider(name = "OverclippedSoftClipRatioDataProvider") - public Iterator overclippedSoftClipRatioDataProvider() { - final List testData = new LinkedList<>(); - - // --------------------------------------- - // Null / trivial cases: - testData.add(new Object[] { "", 0.1, false }); - testData.add(new Object[] { "10H", 0.1, false }); - - // --------------------------------------- - // Min bases test: - testData.add(new Object[] { "20S5M", 0.2, false }); // 20/25 = .8 - testData.add(new Object[] { "20S20M", 0.2, true }); // 20/40 = .5 - - // --------------------------------------- - // Soft clip ratio test: - - testData.add(new Object[] { "1S1M1S17M", 0.2, false }); // 2/20 = .100 - testData.add(new Object[] { "1S1M2S17M", 0.2, false }); // 3/21 = .143 - testData.add(new Object[] { "1S1M3S17M", 0.2, false }); // 4/22 = .182 - testData.add(new Object[] { "1S1M4S17M", 0.2, true }); // 5/23 = .217 - testData.add(new Object[] { "1S1M5S17M", 0.2, true }); // 6/24 = .250 - testData.add(new Object[] { "1S1M6S17M", 0.2, true }); // 7/25 = .280 - - // --------------------------------------- - // Soft clip placement: - - testData.add(new Object[] { "101S100M", 0.5, true }); - testData.add(new Object[] { "100M101S", 0.5, true }); - testData.add(new Object[] { "25H20S10M20S10M20S10M20S10M20S10M20S25H", 0.5, true }); - - return testData.iterator(); - } - - @DataProvider(name = "OverclippedLeadingTrailingSoftClipRatioDataProvider") - public Iterator overclippedLeadingTrailingSoftClipRatioDataProvider() { - final List testData = new LinkedList<>(); - - // --------------------------------------- - // Null / trivial cases: - testData.add(new Object[] { "", 0.1, false }); - testData.add(new Object[] { "10H", 0.1, false }); - - // --------------------------------------- - // Min bases test: - testData.add(new Object[] { "20S5M", 0.2, false }); // 20/25 = .8 - testData.add(new Object[] { "20S20M", 0.2, true }); // 20/40 = .5 - - // --------------------------------------- - // Soft clip ratio test: - - // Non-leading/-trailing - testData.add(new Object[] { "1S1M1S17M", 0.2, false }); // 2/20 = .100 - testData.add(new Object[] { "1S1M2S17M", 0.2, false }); // 3/21 = .143 - testData.add(new Object[] { "1S1M3S17M", 0.2, false }); // 4/22 = .182 - testData.add(new Object[] { "1S1M4S17M", 0.2, false }); // 5/23 = .217 - testData.add(new Object[] { "1S1M5S17M", 0.2, false }); // 6/24 = .250 - testData.add(new Object[] { "1S1M6S17M", 0.2, false }); // 7/25 = .280 - - // Leading: - testData.add(new Object[] { "2S1S1S16M", 0.2, false }); // 2/20 = .100 - testData.add(new Object[] { "3S1S1S16M", 0.2, false }); // 3/21 = .143 - testData.add(new Object[] { "4S1S1S16M", 0.2, false }); // 4/22 = .182 - testData.add(new Object[] { "5S1S1S16M", 0.2, true }); // 5/23 = .217 - testData.add(new Object[] { "6S1S1S16M", 0.2, true }); // 6/24 = .250 - testData.add(new Object[] { "7S1S1S16M", 0.2, true }); // 7/25 = .280 - - // Trailing: - testData.add(new Object[] { "1M1S16M2S", 0.2, false }); // 2/20 = .100 - testData.add(new Object[] { "1M1S16M3S", 0.2, false }); // 3/21 = .143 - testData.add(new Object[] { "1M1S16M4S", 0.2, false }); // 4/22 = .182 - testData.add(new Object[] { "1M1S16M5S", 0.2, true }); // 5/23 = .217 - testData.add(new Object[] { "1M1S16M6S", 0.2, true }); // 6/24 = .250 - testData.add(new Object[] { "1M1S16M7S", 0.2, true }); // 7/25 = .280 - - // --------------------------------------- - // Soft clip placement: - - testData.add(new Object[] { "101S100M", 0.5, true }); - testData.add(new Object[] { "100M101S", 0.5, true }); - testData.add(new Object[] { "25H20S10M20S10M20S10M20S10M20S10M20S25H", 0.5, false }); - - return testData.iterator(); - } - @DataProvider(name = "OverclippedDataProvider") public Iterator overclippedDataProvider() { final List result = new LinkedList<>(); diff --git a/src/test/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilterUnitTest.java b/src/test/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilterUnitTest.java new file mode 100644 index 00000000000..668a1c62a92 --- /dev/null +++ b/src/test/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilterUnitTest.java @@ -0,0 +1,139 @@ +package org.broadinstitute.hellbender.engine.filters; + +import htsjdk.samtools.Cigar; +import htsjdk.samtools.SAMFileHeader; +import htsjdk.samtools.TextCigarCodec; +import org.broadinstitute.hellbender.utils.read.ArtificialReadUtils; +import org.broadinstitute.hellbender.utils.read.GATKRead; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Unit tests for the {@link SoftClippedReadFilter} class. + * Created by jonn on 6/13/19. + */ +public class SoftClippedReadFilterUnitTest { + + private static final int CHR_COUNT = 1; + private static final int CHR_START = 1; + private static final int CHR_SIZE = 1000; + private static final int GROUP_COUNT = 5; + + private final SAMFileHeader header = ArtificialReadUtils.createArtificialSamHeaderWithGroups(CHR_COUNT, CHR_START, CHR_SIZE, GROUP_COUNT); + + private GATKRead buildSAMRead(final String cigarString) { + final Cigar cigar = TextCigarCodec.decode(cigarString); + return ArtificialReadUtils.createArtificialRead(header, cigar); + } + + @Test(dataProvider= "SoftClipRatioDataProvider") + public void testOverclippedSoftClipRatioFilter(final String cigarString, + final double clipRatio, + final boolean expectedResult) { + + final SoftClippedReadFilter filter = new SoftClippedReadFilter(); + filter.minimumSoftClippedRatio = clipRatio; + + final GATKRead read = buildSAMRead(cigarString); + Assert.assertEquals(filter.test(read), expectedResult, cigarString); + + filter.doInvertFilter = true; + Assert.assertEquals(filter.test(read), !expectedResult, "Inverted case: " + cigarString); + } + + @Test(dataProvider= "SoftClippedLeadingTrailingRatioDataProvider") + public void testSoftClippedLeadingTrailingRatioFilter(final String cigarString, + final double clipRatio, + final boolean expectedResult) { + + final SoftClippedReadFilter filter = new SoftClippedReadFilter(); + filter.minimumLeadingTrailingSoftClippedRatio = clipRatio; + + final GATKRead read = buildSAMRead(cigarString); + Assert.assertEquals(filter.test(read), expectedResult, cigarString); + + filter.doInvertFilter = true; + Assert.assertEquals(filter.test(read), !expectedResult, "Inverted case: " + cigarString); + } + + @DataProvider(name = "SoftClipRatioDataProvider") + public Iterator softClipRatioDataProvider() { + final List testData = new LinkedList<>(); + + // --------------------------------------- + // Null / trivial cases: + testData.add(new Object[] { "", 0.1, false }); + testData.add(new Object[] { "10H", 0.1, false }); + + // --------------------------------------- + // Soft clip ratio test: + + testData.add(new Object[] { "1S1M1S17M", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "1S1M2S17M", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "1S1M3S17M", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "1S1M4S17M", 0.2, true }); // 5/23 = .217 + testData.add(new Object[] { "1S1M5S17M", 0.2, true }); // 6/24 = .250 + testData.add(new Object[] { "1S1M6S17M", 0.2, true }); // 7/25 = .280 + + // --------------------------------------- + // Soft clip placement: + + testData.add(new Object[] { "101S100M", 0.5, true }); + testData.add(new Object[] { "100M101S", 0.5, true }); + testData.add(new Object[] { "25H20S10M20S10M20S10M20S10M20S10M20S25H", 0.5, true }); + + return testData.iterator(); + } + + @DataProvider(name = "SoftClippedLeadingTrailingRatioDataProvider") + public Iterator softClippedLeadingTrailingRatioDataProvider() { + final List testData = new LinkedList<>(); + + // --------------------------------------- + // Null / trivial cases: + testData.add(new Object[] { "", 0.1, false }); + testData.add(new Object[] { "10H", 0.1, false }); + + // --------------------------------------- + // Soft clip ratio test: + + // Non-leading/-trailing + testData.add(new Object[] { "1S1M1S17M", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "1S1M2S17M", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "1S1M3S17M", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "1S1M4S17M", 0.2, false }); // 5/23 = .217 + testData.add(new Object[] { "1S1M5S17M", 0.2, false }); // 6/24 = .250 + testData.add(new Object[] { "1S1M6S17M", 0.2, false }); // 7/25 = .280 + + // Leading: + testData.add(new Object[] { "2S1S1S16M", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "3S1S1S16M", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "4S1S1S16M", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "5S1S1S16M", 0.2, true }); // 5/23 = .217 + testData.add(new Object[] { "6S1S1S16M", 0.2, true }); // 6/24 = .250 + testData.add(new Object[] { "7S1S1S16M", 0.2, true }); // 7/25 = .280 + + // Trailing: + testData.add(new Object[] { "1M1S16M2S", 0.2, false }); // 2/20 = .100 + testData.add(new Object[] { "1M1S16M3S", 0.2, false }); // 3/21 = .143 + testData.add(new Object[] { "1M1S16M4S", 0.2, false }); // 4/22 = .182 + testData.add(new Object[] { "1M1S16M5S", 0.2, true }); // 5/23 = .217 + testData.add(new Object[] { "1M1S16M6S", 0.2, true }); // 6/24 = .250 + testData.add(new Object[] { "1M1S16M7S", 0.2, true }); // 7/25 = .280 + + // --------------------------------------- + // Soft clip placement: + + testData.add(new Object[] { "101S100M", 0.5, true }); + testData.add(new Object[] { "100M101S", 0.5, true }); + testData.add(new Object[] { "25H20S10M20S10M20S10M20S10M20S10M20S25H", 0.5, false }); + + return testData.iterator(); + } + +} From a157452fb30be3cdcb91e7f13687f243cec01223 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 14 Jun 2019 11:53:54 -0400 Subject: [PATCH 4/5] Addressing round 2 comments. --- .../hellbender/cmdline/ReadFilterArgumentDefinitions.java | 2 +- .../hellbender/engine/filters/SoftClippedReadFilter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java b/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java index e6a42f8dab9..6cc55e9a165 100644 --- a/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java +++ b/src/main/java/org/broadinstitute/hellbender/cmdline/ReadFilterArgumentDefinitions.java @@ -52,5 +52,5 @@ private ReadFilterArgumentDefinitions(){} public static final String SOFT_CLIPPED_RATIO_THRESHOLD = "soft-clipped-ratio-threshold"; public static final String SOFT_CLIPPED_LEADING_TRAILING_RATIO_THRESHOLD = "soft-clipped-leading-trailing-ratio"; - public static final String INVERT_FILTER = "invert-filter"; + public static final String INVERT_SOFT_CLIP_RATIO_FILTER = "invert-soft-clip-ratio-filter"; } diff --git a/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java b/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java index afc437a1895..fa0421255fa 100644 --- a/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java +++ b/src/main/java/org/broadinstitute/hellbender/engine/filters/SoftClippedReadFilter.java @@ -26,7 +26,7 @@ public final class SoftClippedReadFilter extends ReadFilter { private final Logger logger = LogManager.getLogger(this.getClass()); @VisibleForTesting - @Argument(fullName = ReadFilterArgumentDefinitions.INVERT_FILTER, + @Argument(fullName = ReadFilterArgumentDefinitions.INVERT_SOFT_CLIP_RATIO_FILTER, doc = "Inverts the results from this filter, causing all variants that would pass to fail and visa-versa.", optional = true ) From 019f99073046e88b9ee14e5108d840ccb4d628cd Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 14 Jun 2019 13:22:56 -0400 Subject: [PATCH 5/5] Fixing silly problem. --- .../hellbender/engine/filters/OverclippedReadFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java index 21ed7933671..26a3f2750d9 100644 --- a/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java +++ b/src/main/java/org/broadinstitute/hellbender/engine/filters/OverclippedReadFilter.java @@ -35,8 +35,7 @@ public final class OverclippedReadFilter extends ReadFilter { @Argument(fullName = ReadFilterArgumentDefinitions.DONT_REQUIRE_SOFT_CLIPS_BOTH_ENDS_NAME, doc = "Allow a read to be filtered out based on having only 1 soft-clipped block. By default, both ends must " + "have a soft-clipped block, setting this flag requires only 1 soft-clipped block", - optional = true, - mutex = {"soft-clipped-ratio-threshold", "leading-trailing-soft-clipped-ratio"}) + optional = true) private boolean doNotRequireSoftClipsOnBothEnds; // Command line parser requires a no-arg constructor