diff --git a/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryComparer.java b/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryComparer.java index 5f8f66fdea8..ee887f8d658 100644 --- a/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryComparer.java +++ b/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryComparer.java @@ -6,7 +6,6 @@ import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_ALT_FRAG_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_CHIMERIC_FRAG_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_DUPLICATE_FRAGS; -import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_ENRICHED_GENE_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_FORWARD_STRAND_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_FRAG_LENGTH_50TH; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_FRAG_LENGTH_5TH; @@ -71,7 +70,6 @@ public void registerThresholds(final DiffThresholds thresholds) thresholds.addFieldThreshold(FLD_FRAG_LENGTH_5TH, -1, 0.05); thresholds.addFieldThreshold(FLD_FRAG_LENGTH_50TH, -1, 0.05); thresholds.addFieldThreshold(FLD_FRAG_LENGTH_95TH, -1, 0.05); - thresholds.addFieldThreshold(FLD_ENRICHED_GENE_PERC, 0.01, -1); thresholds.addFieldThreshold(FLD_MEDIAN_GC_RATIO, 0.01, -1); thresholds.addFieldThreshold(FLD_FORWARD_STRAND_PERC, 0.01, -1); } @@ -81,8 +79,7 @@ public List comparedFieldNames() { return List.of( FLD_QC_STATUS, FLD_TOTAL_FRAGS, FLD_DUPLICATE_FRAGS, FLD_SPLICED_FRAG_PERC, FLD_UNSPLICED_FRAG_PERC, FLD_ALT_FRAG_PERC, - FLD_CHIMERIC_FRAG_PERC, FLD_READ_LENGTH, FLD_FRAG_LENGTH_5TH, FLD_FRAG_LENGTH_50TH, FLD_FRAG_LENGTH_95TH, - FLD_ENRICHED_GENE_PERC + FLD_CHIMERIC_FRAG_PERC, FLD_READ_LENGTH, FLD_FRAG_LENGTH_5TH, FLD_FRAG_LENGTH_50TH, FLD_FRAG_LENGTH_95TH ); } diff --git a/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryData.java b/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryData.java index 423d6876c33..0b263b9662d 100644 --- a/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryData.java +++ b/compar/src/main/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryData.java @@ -32,7 +32,6 @@ public record IsofoxSummaryData(RnaStatistics RnaStatistics) implements Comparab static final String FLD_FRAG_LENGTH_5TH = Column.FragLength5th.toString(); static final String FLD_FRAG_LENGTH_50TH = Column.FragLength50th.toString(); static final String FLD_FRAG_LENGTH_95TH = Column.FragLength95th.toString(); - static final String FLD_ENRICHED_GENE_PERC = Column.EnrichedGenePercent.toString(); static final String FLD_MEDIAN_GC_RATIO = Column.MedianGCRatio.toString(); static final String FLD_FORWARD_STRAND_PERC = Column.ForwardStrandPercent.toString(); @@ -63,7 +62,6 @@ public List displayValues() values.add(format("%.1f", RnaStatistics.fragmentLength5thPercent())); values.add(format("%.1f", RnaStatistics.fragmentLength50thPercent())); values.add(format("%.1f", RnaStatistics.fragmentLength95thPercent())); - values.add(format("%.2f", RnaStatistics.enrichedGenePercent())); return values; } @@ -101,7 +99,6 @@ public Mismatch findMismatch( checkDiff(diffs, FLD_FRAG_LENGTH_5TH, ref.fragmentLength5thPercent(), otherData.fragmentLength5thPercent(), thresholds); checkDiff(diffs, FLD_FRAG_LENGTH_50TH, ref.fragmentLength50thPercent(), otherData.fragmentLength50thPercent(), thresholds); checkDiff(diffs, FLD_FRAG_LENGTH_95TH, ref.fragmentLength95thPercent(), otherData.fragmentLength95thPercent(), thresholds); - checkDiff(diffs, FLD_ENRICHED_GENE_PERC, ref.enrichedGenePercent(), otherData.enrichedGenePercent(), thresholds); checkDiff(diffs, FLD_MEDIAN_GC_RATIO, ref.medianGCRatio(), otherData.medianGCRatio(), thresholds); checkDiff(diffs, FLD_FORWARD_STRAND_PERC, ref.forwardStrandPercent(), otherData.forwardStrandPercent(), thresholds); diff --git a/compar/src/test/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryDataTest.java b/compar/src/test/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryDataTest.java index d5bcb7b5386..56095e47aee 100644 --- a/compar/src/test/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryDataTest.java +++ b/compar/src/test/java/com/hartwig/hmftools/compar/isofox/IsofoxSummaryDataTest.java @@ -3,7 +3,6 @@ import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_ALT_FRAG_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_CHIMERIC_FRAG_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_DUPLICATE_FRAGS; -import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_ENRICHED_GENE_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_FORWARD_STRAND_PERC; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_FRAG_LENGTH_50TH; import static com.hartwig.hmftools.compar.isofox.IsofoxSummaryData.FLD_FRAG_LENGTH_5TH; @@ -45,7 +44,6 @@ public void setUp() Map.entry(FLD_FRAG_LENGTH_5TH, b -> b.fragmentLength5thPercent = alternateValueSource.RnaStatistics().fragmentLength5thPercent()), Map.entry(FLD_FRAG_LENGTH_50TH, b -> b.fragmentLength50thPercent = alternateValueSource.RnaStatistics().fragmentLength50thPercent()), Map.entry(FLD_FRAG_LENGTH_95TH, b -> b.fragmentLength95thPercent = alternateValueSource.RnaStatistics().fragmentLength95thPercent()), - Map.entry(FLD_ENRICHED_GENE_PERC, b -> b.enrichedGenePercent = alternateValueSource.RnaStatistics().enrichedGenePercent()), Map.entry(FLD_MEDIAN_GC_RATIO, b -> b.medianGCRatio = alternateValueSource.RnaStatistics().medianGCRatio()), Map.entry(FLD_FORWARD_STRAND_PERC, b -> b.forwardStrandPercent = alternateValueSource.RnaStatistics().forwardStrandPercent()) ); diff --git a/compar/src/test/java/com/hartwig/hmftools/compar/isofox/TestIsofoxSummaryDataBuilder.java b/compar/src/test/java/com/hartwig/hmftools/compar/isofox/TestIsofoxSummaryDataBuilder.java index 41785ece9f5..76ebfb9240b 100644 --- a/compar/src/test/java/com/hartwig/hmftools/compar/isofox/TestIsofoxSummaryDataBuilder.java +++ b/compar/src/test/java/com/hartwig/hmftools/compar/isofox/TestIsofoxSummaryDataBuilder.java @@ -21,7 +21,6 @@ public class TestIsofoxSummaryDataBuilder public double fragmentLength5thPercent = 31; public double fragmentLength50thPercent = 100; public double fragmentLength95thPercent = 150; - public double enrichedGenePercent = 0.2; public double medianGCRatio = 0.5; public double forwardStrandPercent = 0.9; @@ -38,7 +37,6 @@ public class TestIsofoxSummaryDataBuilder b.fragmentLength5thPercent = 25; b.fragmentLength50thPercent = 80; b.fragmentLength95thPercent = 120; - b.enrichedGenePercent = 0.4; b.medianGCRatio = 0.6; b.forwardStrandPercent = 0.5; }; @@ -61,7 +59,6 @@ private IsofoxSummaryData build() .fragmentLength5thPercent(fragmentLength5thPercent) .fragmentLength50thPercent(fragmentLength50thPercent) .fragmentLength95thPercent(fragmentLength95thPercent) - .enrichedGenePercent(enrichedGenePercent) .medianGCRatio(medianGCRatio) .forwardStrandPercent(forwardStrandPercent) .build(); diff --git a/hmf-common/src/main/java/com/hartwig/hmftools/common/bam/SamRecordUtils.java b/hmf-common/src/main/java/com/hartwig/hmftools/common/bam/SamRecordUtils.java index 941e3e7839b..f4acd83509d 100644 --- a/hmf-common/src/main/java/com/hartwig/hmftools/common/bam/SamRecordUtils.java +++ b/hmf-common/src/main/java/com/hartwig/hmftools/common/bam/SamRecordUtils.java @@ -40,6 +40,7 @@ public final class SamRecordUtils public static final String READ_GROUP_ATTRIBUTE = SAMTag.RG.name(); public static final String XS_ATTRIBUTE = "XS"; + public static final String XA_ATTRIBUTE = "XA"; // Redux tags public static final String CONSENSUS_READ_ATTRIBUTE = "CR"; diff --git a/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatisticFile.java b/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatisticFile.java index 2d89eb6d023..3ecf22e000c 100644 --- a/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatisticFile.java +++ b/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatisticFile.java @@ -50,7 +50,6 @@ public enum Column FragLength5th, FragLength50th, FragLength95th, - EnrichedGenePercent, MedianGCRatio, ForwardStrandPercent; } @@ -84,7 +83,6 @@ public static String writeLine(final String sampleId, final RnaStatistics statis sj.add(String.format("%.0f", statistics.fragmentLength5thPercent())); sj.add(String.format("%.0f", statistics.fragmentLength50thPercent())); sj.add(String.format("%.0f", statistics.fragmentLength95thPercent())); - sj.add(String.format("%.3f", statistics.enrichedGenePercent())); sj.add(String.format("%.3f", statistics.medianGCRatio())); sj.add(String.format("%.3f", statistics.forwardStrandPercent())); return sj.toString(); @@ -128,7 +126,6 @@ public static RnaStatistics fromLines(final List lines) .fragmentLength5thPercent(getDoubleValue(fieldsIndexMap, Column.FragLength5th.toString(), values)) .fragmentLength50thPercent(getDoubleValue(fieldsIndexMap, Column.FragLength50th.toString(), values)) .fragmentLength95thPercent(getDoubleValue(fieldsIndexMap, Column.FragLength95th.toString(), values)) - .enrichedGenePercent(getDoubleValue(fieldsIndexMap, Column.EnrichedGenePercent.toString(), values)) .medianGCRatio(getDoubleValue(fieldsIndexMap, Column.MedianGCRatio.toString(), values)) .forwardStrandPercent(getDoubleValue(fieldsIndexMap, Column.ForwardStrandPercent.toString(), values)) .build(); diff --git a/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatistics.java b/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatistics.java index 3068dc92618..c8df591b923 100644 --- a/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatistics.java +++ b/hmf-common/src/main/java/com/hartwig/hmftools/common/rna/RnaStatistics.java @@ -22,10 +22,6 @@ public abstract class RnaStatistics public abstract double fragmentLength50thPercent(); public abstract double fragmentLength95thPercent(); - // proportion of fragments in 7 highly expressed genes - public abstract double enrichedGenePercent(); - - // Median GC (excluding 7 highly expressed genes) public abstract double medianGCRatio(); public abstract double forwardStrandPercent(); diff --git a/isofox/README.md b/isofox/README.md index 994e7fb86c6..1afdaea08af 100644 --- a/isofox/README.md +++ b/isofox/README.md @@ -11,7 +11,7 @@ For transcript abundance, Isofox uses a similar methodology to several previous * Avoids overfitting of 'retained intron' transcripts which may simply be intronic reads * Individual or combinations of splice junctions which are unique to a transcript will be weighed strongly. Does not overfit variability of coverage within exons -The input for Isofox is mapped paired end reads (we use STAR for our aligner). +The input for Isofox is mapped paired end reads. We align with bwa-mem2 against a transcriptome-augmented reference and lift the alignments back to genomic coordinates with tars, then mark duplicates with redux; Isofox takes the resulting post-tars, post-redux BAM. ### A note on duplicates, highly expressed genes, raw and adjusted TPM @@ -22,19 +22,9 @@ We find that 6 genes in particular (RN7SL2, RN7SL1, RN7SL3, RN7SL4P, RN7SL5P & R In addition, any junction which maps in the Poly-G region of LINC00486 is filtered from all analyses (v38: chr2:32,916,190-32,916,630; v37: 2:33,141,260-33,141,700) as they are likely the result of Poly-G sequencer artefacts. ### A note on alignment and multi-mapping -We use STAR as our aligner. ISOFOX expects BAM output with chimeric reads in the BAM itself, so it is essential when using STAR to set the outSAMtype to 'BAM Unsorted' and the chimOutType to 'WithinBAM' +Reads are aligned with bwa-mem2 against a transcriptome-augmented reference and lifted back to genomic coordinates by tars, then duplicate-marked by redux. Chimeric and supplementary alignments are retained in the BAM. -The full list of non default parameters we use internally is: - -``` ---outSAMtype BAM Unsorted --outSAMunmapped Within --outBAMcompression 0 --outSAMattributes All --outFilterMultimapNmax 10 ---outFilterMismatchNmax 3 limitOutSJcollapsed 3000000 -chimSegmentMin 10 --chimOutType WithinBAM SoftClip ---chimJunctionOverhangMin 10 --chimSegmentReadGapMax 3 --chimScoreMin 1 --chimScoreDropMax 30 --chimScoreJunctionNonGTAG 0 ---chimScoreSeparation 1 --outFilterScoreMinOverLread 0.33 --outFilterMatchNminOverLread 0.33 --outFilterMatchNmin 35 ---alignSplicedMateMapLminOverLmate 0.33 --alignSplicedMateMapLmin 35 --alignSJstitchMismatchNmax 5 -1 5 5 -``` - -STAR allows setting of the `—outFilterMultimapNmax` parameter to specify the maximum number of multimaps to allow for an alignment, and we use the default value (10). STAR will mark one of the multi-mappable reads as primary and the remainder as secondary reads. By default, STAR sets MAPQ for mappable reads to 255, whereas multi-mappable reads will have qual scores of 3 or less. For transcript abundance only, Isofox counts both primary and secondary reads at all locations, but reduces the weight of the reads to reflect the multi-mapping (MAPQ 3 = 50%, MAPQ 2 = 33%, MAPQ 1 = 20%, MAPQ 0 => 10%). Reads with MAPQ of <10 are excluded from novel splice junction and chimeric analysis +Isofox supports both bwa-tars and STAR alignments, selected by `-aligner` (`bwa-tars` is the default, or `star`); the flag only affects how multi-mapped fragments are handled, which is the one place the two aligners differ. Under `bwa-tars` a multi-mapped read is a single primary alignment carrying its alternate loci in the bwa `XA` tag (no secondary records; map qualities 0 to 60, with a confident single-locus read at 60), and the fragment is counted once at its primary locus and flagged multi-mapped. Under `star` the alternate mappings are separate secondary records and ambiguity is encoded in the map quality (255 unique, 3 or lower multi-mapped); a multi-mapped fragment is down-weighted by map-quality tier so its mass is shared across the loci it maps to, reproducing pre-tars behaviour. Under either aligner, multi-mapped reads are excluded from novel splice junction and chimeric analysis. The optional `MULTI_MAP_LOCI` write type emits a tsv of each multi-mapped read's primary and XA alternate loci per gene collection for auditing (bwa-tars only). ## Configuration The functions of Isofox are controlled by the 'functions' argument: @@ -74,7 +64,7 @@ excluded_regions | Drop reads in regions of high multi-mappability excluded_gene_id_file | Exclude genes in file, format EnsemblGeneId,GeneName enriched_gene_ids | List of EnsemblGeneIds separated by ';', see Enriched Genes information below drop_dups | Default is false. By default duplicate fragments will be counted towards transcript expression. -write_type | Debug only: EXON, SPLICE_JUNC, FRAG_LENGTH, FRAG_LENGTH_BY_GENE, READ, CHIMERIC_READ, CHIMERIC_POSITION_DATA, SPLICE_SITE, TRANS_COMBO;GC_RATIO, separated by ';' +write_type | Debug only, separated by ';': EXON, SPLICE_JUNC, FRAG_LENGTH, FRAG_LENGTH_BY_GENE, READ, CHIMERIC_READ, CHIMERIC_POSITION_DATA, SPLICE_SITE, TRANS_COMBO, GC_RATIO, MULTI_MAP_LOCI ### Reference Files @@ -98,7 +88,8 @@ frag_length_min_count | Minimum number of fragments to observe for length distri exp_rate_frag_lengths | Discrete buckets for fragment lengths, either with frequency specified or left as zero if to be calculated (ie with -apply_calc_frag_lengths). eg '50-0;75-0;100-0;125-0;150-0;200-0;250-0;300-0;400-0;550-0' read_length | Expected RNA read length (eg 76 or 151), will be computed if not provided long_frag_limit | Default 550 bases, fragments longer than this without a splice junction are not considered to support a gene for the purposes of expression -single_map_qual | Default 255, discard reads with map quality below this unless using the config 'apply_map_qual_adjust' +single_map_qual | Default 60, or 255 when 'star_aligner' is set. Discard reads with map quality below this. +star_aligner | Flag, off by default. Off assumes the bwa-tars + REDUX pipeline; set it to apply STAR-aligner settings (raises the default single_map_qual to 255). enriched_gene_ids | By default includes: ENSG00000265150;ENSG00000258486;ENSG00000202198;ENSG00000266037;ENSG00000263740;ENSG00000265735 ### Optional output files: @@ -236,7 +227,7 @@ Each fragment is assigned to a 'category' based on the set of transcripts that i Any fragment which does not contain a splice junction, is wholly contained within the bounds of a gene, and with fragment size <= maximum insert size distribution is also allowed to map to an ‘UNSPLICED’ transcript of that gene. -Note that reads which are partially exonic, but marginally overhang an exon boundary or are soft clipped at or beyond an exon boundary have special treatment. This is particularly relevant for reads that have an overhang of 1 or 2 bases which will not be mapped by STAR with default parameters. If the overhanging section can be uniquely mapped either to the reference or to the other side of only a single known spliced junction, then the fragment is deemed to be supporting that splice junction or in the case of supporting just the reference is deemed to be supporting the UNSPLICED transcript. If multiple mappings are possible or the fragment length < unclipped read length (indicating likely adapter sequence) it is truncated at the exon boundary. If no mapping is possible then the fragment is treated as not supporting any known transcript. +Note that reads which are partially exonic, but marginally overhang an exon boundary or are soft clipped at or beyond an exon boundary have special treatment. This is particularly relevant for reads that have an overhang of 1 or 2 bases which may not be mapped across the exon boundary by the aligner. If the overhanging section can be uniquely mapped either to the reference or to the other side of only a single known spliced junction, then the fragment is deemed to be supporting that splice junction or in the case of supporting just the reference is deemed to be supporting the UNSPLICED transcript. If multiple mappings are possible or the fragment length < unclipped read length (indicating likely adapter sequence) it is truncated at the exon boundary. If no mapping is possible then the fragment is treated as not supporting any known transcript. ### 5. Fit abundance estimate per transcript diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/ChromosomeTaskExecutor.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/ChromosomeTaskExecutor.java index 5278258aa88..9bf50d04cb2 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/ChromosomeTaskExecutor.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/ChromosomeTaskExecutor.java @@ -74,7 +74,6 @@ public class ChromosomeTaskExecutor implements Callable // cache of results private final List mGeneCollectionSummaryData; - private long mEnrichedGenesFragmentCount; private final FragmentTypeCounts mCombinedFragmentCounts; private final GcRatioCounts mNonEnrichedGcRatioCounts; private int mTotalReadsProcessed; @@ -104,7 +103,7 @@ public ChromosomeTaskExecutor( mExpectedCountsCache = expectedCountsCache; - mBamFragmentAllocator = new FragmentAllocator(mConfig, altSjCohortCache, resultsWriter); + mBamFragmentAllocator = new FragmentAllocator(mConfig, mGeneTransCache, altSjCohortCache, resultsWriter); mBamFragmentAllocator.registerKnownFusionPairs(mGeneTransCache); mGcRatioCounts = mBamFragmentAllocator.getGcRatioCounts(); @@ -112,7 +111,6 @@ public ChromosomeTaskExecutor( mTranscriptGcRatios = transcriptGcCalcs; mGeneCollectionSummaryData = Lists.newArrayList(); - mEnrichedGenesFragmentCount = 0; mTotalReadsProcessed = 0; mCombinedFragmentCounts = new FragmentTypeCounts(); mNonEnrichedGcRatioCounts = new GcRatioCounts(); @@ -181,7 +179,6 @@ public void assignTranscriptCounts() List geneReadDataList = createGeneReadData(overlappingGenes, mGeneTransCache); GeneCollection geneCollection = new GeneCollection(mCollectionId++, geneReadDataList); - geneCollection.markEnrichedAndExcludedGenes(mConfig, mGeneTransCache); if(!genesFiltered) // reads will be taken from the previous gene collection's end { @@ -387,28 +384,8 @@ private void postBamReadTranscriptCounts(final GeneCollection geneCollection) mResultsWriter.writeSpliceJunctionData(geneCollection); } - if(!mConfig.Filters.EnrichedGeneIds.isEmpty()) - { - long enrichedGeneFragments = geneCollection.genes().stream() - .anyMatch(x -> mConfig.Filters.EnrichedGeneIds.contains(x.Gene.GeneId)) - ? geneCollection.fragmentTypeCounts().typeCount(TOTAL) : 0; - - if(enrichedGeneFragments > 0) - { - mEnrichedGenesFragmentCount += enrichedGeneFragments; - } - else - { - if(mBamFragmentAllocator.getGeneGcRatioCounts() != null) - mNonEnrichedGcRatioCounts.mergeRatioCounts(mBamFragmentAllocator.getGeneGcRatioCounts().getCounts()); - } - } - else - { - // take them all - if(mBamFragmentAllocator.getGeneGcRatioCounts() != null) - mNonEnrichedGcRatioCounts.mergeRatioCounts(mBamFragmentAllocator.getGeneGcRatioCounts().getCounts()); - } + if(mBamFragmentAllocator.getGeneGcRatioCounts() != null) + mNonEnrichedGcRatioCounts.mergeRatioCounts(mBamFragmentAllocator.getGeneGcRatioCounts().getCounts()); mCombinedFragmentCounts.combine(geneCollection.fragmentTypeCounts()); @@ -484,11 +461,10 @@ private void collectResults( geneCollectionSummary.GeneResults.add(geneResult); geneCollectionSummary.setFitAllocations(); - geneCollectionSummary.assignLowMapQualityFragments(); + geneCollectionSummary.assignMultiMappedFragments(); geneCollectionSummary.TranscriptResults.forEach(x -> x.setPreGcFitAllocation(x.getFitAllocation())); } - public long getEnrichedGenesFragmentCount() { return mEnrichedGenesFragmentCount; } public FragmentTypeCounts getCombinedCounts() { return mCombinedFragmentCounts; } public GcRatioCounts getNonEnrichedGcRatioCounts() { return mNonEnrichedGcRatioCounts; } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/FragmentAllocator.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/FragmentAllocator.java index b47ca8edc80..412a9550871 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/FragmentAllocator.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/FragmentAllocator.java @@ -3,23 +3,17 @@ import static java.lang.Math.max; import static java.lang.Math.min; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.CONSENSUS_READ_ATTRIBUTE; import static com.hartwig.hmftools.common.bam.SamRecordUtils.firstInPair; -import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_CHROMOSOME; -import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_GENE_ID; -import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_GENE_NAME; -import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_POS_END; -import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_POS_START; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.readToString; +import static com.hartwig.hmftools.common.utils.file.FileDelimiters.ITEM_DELIM; import static com.hartwig.hmftools.common.utils.file.FileDelimiters.TSV_DELIM; -import static com.hartwig.hmftools.common.utils.file.FileWriterUtils.createBufferedWriter; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_PAIR; import static com.hartwig.hmftools.common.region.BaseRegion.positionWithin; +import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; -import static com.hartwig.hmftools.isofox.IsofoxConstants.MULTI_MAP_QUALITY_THRESHOLD; -import static com.hartwig.hmftools.isofox.IsofoxConstants.SINGLE_MAP_QUALITY; import static com.hartwig.hmftools.isofox.IsofoxFunction.ALT_SPLICE_JUNCTIONS; -import static com.hartwig.hmftools.isofox.IsofoxFunction.STATISTICS; -import static com.hartwig.hmftools.isofox.IsofoxFunction.TRANSCRIPT_COUNTS; import static com.hartwig.hmftools.isofox.WriteType.SPLICE_SITE; import static com.hartwig.hmftools.isofox.common.FragmentMatchType.DISCORDANT; import static com.hartwig.hmftools.isofox.common.FragmentType.ALT; @@ -31,11 +25,12 @@ import static com.hartwig.hmftools.isofox.common.FragmentType.TRANS_SUPPORTING; import static com.hartwig.hmftools.isofox.common.FragmentType.UNSPLICED; import static com.hartwig.hmftools.isofox.IsofoxFunction.FUSIONS; -import static com.hartwig.hmftools.isofox.common.Read.MAX_SC_BASE_MATCH; import static com.hartwig.hmftools.isofox.common.Read.findOverlappingRegions; -import static com.hartwig.hmftools.isofox.common.Read.getUniqueValidRegion; -import static com.hartwig.hmftools.isofox.common.Read.markRegionBases; -import static com.hartwig.hmftools.isofox.common.Read.validTranscriptType; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.getUniqueValidRegion; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.markRegionBases; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.validTranscriptType; +import static com.hartwig.hmftools.isofox.common.ReadUtils.consensusDuplicateCount; +import static com.hartwig.hmftools.isofox.common.ReadUtils.trimAdapterBases; import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_INTRON; import static com.hartwig.hmftools.isofox.common.CommonUtils.deriveCommonRegions; import static com.hartwig.hmftools.isofox.common.TransMatchType.OTHER_TRANS; @@ -44,6 +39,7 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.isofox.fusion.ChimericUtils.isRealignedFragmentCandidate; +import static com.hartwig.hmftools.isofox.results.ResultsWriter.writeReadData; import java.io.BufferedWriter; import java.io.File; @@ -59,6 +55,7 @@ import com.google.common.collect.Sets; import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; import com.hartwig.hmftools.common.gene.ExonData; +import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.gene.TranscriptData; import com.hartwig.hmftools.common.bam.BamSlicer; import com.hartwig.hmftools.common.region.BaseRegion; @@ -70,7 +67,7 @@ import com.hartwig.hmftools.isofox.common.FragmentType; import com.hartwig.hmftools.isofox.common.GeneReadData; import com.hartwig.hmftools.isofox.common.Read; -import com.hartwig.hmftools.isofox.common.RegionMatchType; +import com.hartwig.hmftools.isofox.common.ReadTranscriptUtils; import com.hartwig.hmftools.isofox.common.RegionReadData; import com.hartwig.hmftools.isofox.common.TransExonRef; import com.hartwig.hmftools.isofox.common.TransMatchType; @@ -84,7 +81,6 @@ import com.hartwig.hmftools.isofox.novel.SpliceSiteCounter; import com.hartwig.hmftools.isofox.results.ResultsWriter; -import htsjdk.samtools.SAMFlag; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SamReader; import htsjdk.samtools.SamReaderFactory; @@ -116,14 +112,19 @@ public class FragmentAllocator private final boolean mStatsOnly; private final BufferedWriter mReadDataWriter; - private long mEnrichedGeneFragments; + private final BufferedWriter mMultiMapLociWriter; - private static final int GENE_LOG_COUNT = 5000000; + private final EnsemblDataCache mGeneTransCache; + + private static final int GENE_LOG_COUNT = 2000000; private static final int NON_GENIC_BASE_DEPTH_WIDTH = 250000; - public FragmentAllocator(final IsofoxConfig config, final AltSjCohortCache altSjCohortCache, final ResultsWriter resultsWriter) + public FragmentAllocator( + final IsofoxConfig config, final EnsemblDataCache geneTransCache, final AltSjCohortCache altSjCohortCache, + final ResultsWriter resultsWriter) { mConfig = config; + mGeneTransCache = geneTransCache; mCurrentGenes = null; mFragmentReads = new FragmentTracker(); @@ -135,28 +136,25 @@ public FragmentAllocator(final IsofoxConfig config, final AltSjCohortCache altSj mGeneReadCount = 0; mTotalBamReadCount = 0; mNextGeneCountLog = 0; - mEnrichedGeneFragments = 0; mValidReadStartRegion = new int[SE_PAIR]; mSamReader = mConfig.BamFile != null ? SamReaderFactory.makeDefault().referenceSequence(mConfig.RefGenomeFile).open(new File(mConfig.BamFile)) : null; - // duplicates aren't counted towards fusions so can be ignored if only running fusions - boolean keepDuplicates = (mConfig.runFunction(TRANSCRIPT_COUNTS) || mConfig.runFunction(STATISTICS)) && !mConfig.DropDuplicates; - // reads with supplementary alignment data are only used for chimeric read handling (eg fusions & alt-SJs) boolean keepSupplementaries = mRunFusions || mConfig.runFunction(ALT_SPLICE_JUNCTIONS); - boolean keepSecondaries = mConfig.runFunction(TRANSCRIPT_COUNTS); - int minMapQuality = keepSecondaries ? 0 : SINGLE_MAP_QUALITY; + // fusions typically aren't run without expression, but for STAR the existing logic was to drop reads with map qual less than the max + int minMapQuality = 0; - mBamSlicer = new BamSlicer(minMapQuality, keepDuplicates, keepSupplementaries, keepSecondaries); + // no need to process duplicates since their counts can be extracted from consensus reads for transcript counting + mBamSlicer = new BamSlicer(minMapQuality, false, keepSupplementaries, false); mReadDataWriter = resultsWriter.getReadDataWriter(); + mMultiMapLociWriter = resultsWriter.getMultiMapLociWriter(); mBaseDepth = new BaseDepth(); mChimericReads = new ChimericReadTracker(mConfig); - mChimericReads.setChimericReadWriter(resultsWriter.getChimericReadWriter()); mChimericReads.setChimericPosDataWriter(resultsWriter.getChimericPositionDataWriter()); mSpliceSiteCounter = new SpliceSiteCounter(resultsWriter.getSpliceSiteWriter()); @@ -176,7 +174,6 @@ public FragmentAllocator(final IsofoxConfig config, final AltSjCohortCache altSj public BaseDepth getBaseDepth() { return mBaseDepth; } public final ChimericReadTracker getChimericReadTracker() { return mChimericReads; } public final SpliceSiteCounter getSpliceSiteCounter() { return mSpliceSiteCounter; } - public final FragmentTracker getFragmentTracker() { return mFragmentReads; } public void clearCache() { @@ -199,7 +196,6 @@ public void processBam(final GeneCollection geneCollection, final ChrBaseRegion mGeneReadCount = 0; mNextGeneCountLog = GENE_LOG_COUNT; - mEnrichedGeneFragments = 0; // and width around the base depth region to pick up junctions outside the gene int[] baseDepthRange = new int[SE_PAIR]; @@ -221,55 +217,7 @@ public void processBam(final GeneCollection geneCollection, final ChrBaseRegion mValidReadStartRegion[SE_START] = geneRegion.start(); mValidReadStartRegion[SE_END] = geneRegion.end(); - List excludedRegions = mConfig.Filters.findExcludedRegions(geneRegion); - - if(!excludedRegions.isEmpty()) - { - // genic regions are wholly contained within an excluded region - BaseRegion geneBaseRegion = new BaseRegion(geneRegion.start(), geneRegion.end()); - if(excludedRegions.stream().anyMatch(x -> x.containsRegion(geneBaseRegion))) - return; - - // slice around any excluded regions - ISF_LOGGER.debug("gene collection({}) region({}) slicing around excluded regions({})", - mCurrentGenes, geneRegion, excludedRegions.stream().map(x -> x.toString()).collect(Collectors.joining(";"))); - - int regionStart = geneRegion.start(); - List sliceRegions = Lists.newArrayList(); - - for(int i = 0; i < excludedRegions.size(); ++i) - { - BaseRegion excludedRegion = excludedRegions.get(i); - - if(regionStart < excludedRegion.start()) - { - sliceRegions.add(new ChrBaseRegion(geneRegion.Chromosome, regionStart, excludedRegion.start() - 1)); - } - - regionStart = excludedRegion.end() + 1; - } - - if(regionStart < geneRegion.end()) - { - sliceRegions.add(new ChrBaseRegion(geneRegion.Chromosome, regionStart, geneRegion.end())); - } - - for(ChrBaseRegion sliceRegion : sliceRegions) - { - // no buffer for slicing around regions since they already have a buffer configured / set - if(sliceRegion.isValid()) - { - mBamSlicer.slice(mSamReader, sliceRegion, this::processSamRecord); - } - } - } - else - { - mBamSlicer.slice(mSamReader, geneRegion, this::processSamRecord); - } - - if(mEnrichedGeneFragments > 0) - mExpressionReadTracker.processEnrichedGeneFragments(mEnrichedGeneFragments); + mBamSlicer.slice(mSamReader, geneRegion, this::processSamRecord); if(mChimericReads.enabled()) { @@ -289,24 +237,20 @@ private void processSamRecord(final SAMRecord record) if(!positionWithin(record.getStart(), mValidReadStartRegion[SE_START], mValidReadStartRegion[SE_END])) return; + if(mConfig.LogReadIds.contains(record.getReadName())) + { + ISF_LOGGER.debug("specific read: {}", readToString(record)); + } + if(mConfig.Filters.skipRead(record, true)) { ++mChimericReads.getStats().Excluded; return; } - // if(mConfig.skipFilteredRead(record.getReadName())) - // return; - trackFragmentCounts(record); - if(mCurrentGenes.inEnrichedRegion(record.getStart(), record.getEnd())) - { - processEnrichedRegionRead(record); - return; - } - - Read read = Read.from(record); + Read read = new Read(record); processRead(read); } @@ -317,7 +261,7 @@ private void trackFragmentCounts(final SAMRecord record) ++mGeneReadCount; // count each fragment once by only taking the first read, and supplmentaries are ignored - if(record.getSupplementaryAlignmentFlag() || record.isSecondaryAlignment()) + if(record.getSupplementaryAlignmentFlag()) return; if(!firstInPair(record)) @@ -325,8 +269,12 @@ private void trackFragmentCounts(final SAMRecord record) mCurrentGenes.addCount(TOTAL, 1); - if(record.getDuplicateReadFlag()) - mCurrentGenes.addCount(DUPLICATE, 1); + if(record.hasAttribute(CONSENSUS_READ_ATTRIBUTE)) + { + int duplicateCount = consensusDuplicateCount(record); + mCurrentGenes.addCount(TOTAL, duplicateCount); + mCurrentGenes.addCount(DUPLICATE, duplicateCount); + } } private void processRead(final Read read) @@ -343,16 +291,16 @@ private void processRead(final Read read) if(reachedGeneReadLimit()) return; - final List overlappingRegions = findOverlappingRegions(mCurrentGenes.getExonRegions(), read); + mCurrentGenes.setReadGeneCollections(read, mValidReadStartRegion); - if(!overlappingRegions.isEmpty()) + if(read.isSupplementaryAlignment()) { - read.processOverlappingRegions(overlappingRegions); + handleSupplementaryRead(read); + } + else + { + checkFragmentRead(read); } - - mCurrentGenes.setReadGeneCollections(read, mValidReadStartRegion); - - checkFragmentRead(read); } private boolean checkFragmentRead(final Read read) @@ -369,6 +317,35 @@ private boolean checkFragmentRead(final Read read) return false; } + private void markGeneDataRegions(final Read read) + { + List overlappingRegions = findOverlappingRegions(mCurrentGenes.getExonRegions(), read); + + if(!overlappingRegions.isEmpty()) + { + ReadTranscriptUtils.processOverlappingRegions(read, overlappingRegions); + } + } + + private void handleSupplementaryRead(final Read read) + { + if(read.isDuplicate() || read.isMateUnmapped()) + return; + + mBaseDepth.processRead(read.getMappedRegionCoords()); + + markGeneDataRegions(read); + mChimericReads.addSupplementaryRead(read); + + if(mReadDataWriter != null && mConfig.writeType(WriteType.READ)) + { + List overlapGenes = mCurrentGenes.findGenesCoveringRange( + read.alignmentStart(), read.alignmentEnd(), true); + + writeReadData(mReadDataWriter, overlapGenes, read, CHIMERIC, 0); + } + } + private void processFragmentReads(final Read read1, final Read read2) { /* process the pair of reads from a fragment: @@ -382,42 +359,52 @@ private void processFragmentReads(final Read read1, final Read read2) - not supporting any transcript - eg alternative splice sites or unspliced reads */ - read1.trimAdapterSoftClipBases(read2); - read2.trimAdapterSoftClipBases(read1); + trimAdapterBases(read1, read2); + + markGeneDataRegions(read1); + markGeneDataRegions(read2); + + int fragmentCount = 1; + + boolean isConsensusRead = read1.isConsensusRead() || read2.isConsensusRead(); + + if(isConsensusRead) + { + // consensus reads from Redux - these are primaries where duplicate reads are also expected + // to avoid the additional count from the artificially created primary, skip these for any logic which is expression related, + // (note: only expression uses duplicates) + int duplicateCount = consensusDuplicateCount(read1.bamRecord()); + fragmentCount += duplicateCount; + } + + int numLoci = min(read1.numLoci(), read2.numLoci()); + boolean isMultiMapped = numLoci > 1; - boolean isDuplicate = read1.isDuplicate() || read2.isDuplicate(); - int minMapQuality = min(read1.mapQuality(), read2.mapQuality()); - boolean isMultiMapped = minMapQuality <= MULTI_MAP_QUALITY_THRESHOLD; + List altLoci = read1.numLoci() <= read2.numLoci() ? read1.altLoci() : read2.altLoci(); - boolean isChimeric = mChimericReads.isChimeric(read1, read2, isDuplicate, isMultiMapped); + boolean isChimeric = mChimericReads.isChimeric(read1, read2, isMultiMapped); if(mStatsOnly) { - if(!isDuplicate) - { - if(isChimeric) - mCurrentGenes.addCount(CHIMERIC, 1); - else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty()) - mCurrentGenes.addCount(UNSPLICED, 1); - else - mCurrentGenes.addCount(TRANS_SUPPORTING, 1); - } + if(isChimeric) + mCurrentGenes.addCount(CHIMERIC, 1); + else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty()) + mCurrentGenes.addCount(UNSPLICED, fragmentCount); + else + mCurrentGenes.addCount(TRANS_SUPPORTING, fragmentCount); return; } - final List commonMappings = deriveCommonRegions(read1.getMappedRegionCoords(), read2.getMappedRegionCoords()); + List commonMappings = deriveCommonRegions(read1.getMappedRegionCoords(), read2.getMappedRegionCoords()); - if(!isDuplicate) - { - mBaseDepth.processRead(commonMappings); - } + mBaseDepth.processRead(commonMappings); // if either read is chimeric (including one outside the genic region) then handle them both as such // some of these may be re-processed as alternative SJ candidates if they are within a single gene if(isChimeric) { - if(!isMultiMapped && !read1.isSecondaryAlignment() && !read2.isSecondaryAlignment()) + if(!isMultiMapped) { if(mChimericReads.enabled()) mChimericReads.addChimericReadPair(read1, read2); @@ -425,6 +412,16 @@ else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty() mCurrentGenes.addCount(CHIMERIC, 1); } + if(mReadDataWriter != null && mConfig.writeType(WriteType.READ)) + { + List overlapGenes = mCurrentGenes.findGenesCoveringRange( + min(read1.alignmentStart(), read2.alignmentStart()), + max(read1.alignmentEnd(), read2.alignmentEnd()), true); + + writeReadData(mReadDataWriter, overlapGenes, read1, CHIMERIC, 0); + writeReadData(mReadDataWriter, overlapGenes, read2, CHIMERIC, 0); + } + return; } @@ -440,26 +437,29 @@ else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty() return; } - int readPosMin = min(read1.PosStart, read2.PosStart); - int readPosMax = max(read1.PosEnd, read2.PosEnd); + if(numLoci > 1 && altLoci != null && mMultiMapLociWriter != null) + recordMultiMapLoci(read1, read2, altLoci); + + int readPosMin = min(read1.alignmentStart(), read2.alignmentStart()); + int readPosMax = max(read1.alignmentEnd(), read2.alignmentEnd()); - final List overlapGenes = mCurrentGenes.findGenesCoveringRange(readPosMin, readPosMax, true); + List overlapGenes = mCurrentGenes.findGenesCoveringRange(readPosMin, readPosMax, true); if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty()) { // fully intronic read in every transcript and gene - processIntronicReads(overlapGenes, read1, read2); + processIntronicReads(overlapGenes, read1, read2, fragmentCount, isMultiMapped); return; } - final Map firstReadTransTypes = read1.getTranscriptClassifications(); - final Map secondReadTransTypes = read2.getTranscriptClassifications(); + Map firstReadTransTypes = read1.getTranscriptClassifications(); + Map secondReadTransTypes = read2.getTranscriptClassifications(); // first find valid transcripts in both reads - final List validTranscripts = Lists.newArrayList(); - final Set invalidTranscripts = Sets.newHashSet(); + List validTranscripts = Lists.newArrayList(); + Set invalidTranscripts = Sets.newHashSet(); - final List validRegions = getUniqueValidRegion(read1, read2); + List validRegions = getUniqueValidRegion(read1, read2); if(mConfig.RunValidations) { @@ -475,14 +475,16 @@ else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty() // track splice site info if(mConfig.writeType(SPLICE_SITE)) { - mSpliceSiteCounter.registerSpliceSiteSupport(read1.getMappedRegionCoords(), read2.getMappedRegionCoords(), mCurrentGenes.getExonRegions()); + mSpliceSiteCounter.registerSpliceSiteSupport( + read1.getMappedRegionCoords(), read2.getMappedRegionCoords(), mCurrentGenes.getExonRegions()); } for(Map.Entry entry : firstReadTransTypes.entrySet()) { int transId = entry.getKey(); - if(validTranscriptType(entry.getValue()) && secondReadTransTypes.containsKey(transId) && validTranscriptType(secondReadTransTypes.get(transId))) + if(validTranscriptType(entry.getValue()) && secondReadTransTypes.containsKey(transId) + && validTranscriptType(secondReadTransTypes.get(transId))) { int calcFragmentLength = calcFragmentLength(transId, read1, read2); boolean validFragmentLength = calcFragmentLength > 0 && calcFragmentLength <= mConfig.MaxFragmentLength; @@ -538,14 +540,14 @@ else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty() .filter(x -> x.getValue() != EXON_INTRON) .map(x -> x.getKey()).collect(Collectors.toList());; - final List regions2 = read2.getMappedRegions().entrySet().stream() + List regions2 = read2.getMappedRegions().entrySet().stream() .filter(x -> x.getKey().hasTransId(transId)) .filter(x -> x.getValue() != EXON_INTRON) .map(x -> x.getKey()).collect(Collectors.toList()); for(RegionReadData region : regions2) { - if (!regions.contains(region)) + if(!regions.contains(region)) regions.add(region); } @@ -564,7 +566,7 @@ else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty() if(fragmentType == UNSPLICED) { - mExpressionReadTracker.processUnsplicedGenes(overlapGenes, validTranscripts, commonMappings, minMapQuality); + mExpressionReadTracker.processUnsplicedGenes(overlapGenes, validTranscripts, commonMappings, fragmentCount, isMultiMapped); } } else @@ -585,11 +587,11 @@ else if(read1.getMappedRegions().isEmpty() && read2.getMappedRegions().isEmpty() if(mConfig.RunValidations) { - for(int[] readRegion : commonMappings) + for(BaseRegion readRegion : commonMappings) { - if(commonMappings.stream().filter(x -> x[SE_START] == readRegion[SE_START] && x[SE_END] == readRegion[SE_END]).count() > 1) + if(commonMappings.stream().filter(x -> x.start() == readRegion.start() && x.end() == readRegion.end()).count() > 1) { - ISF_LOGGER.error("repeated read region({} -> {})", readRegion[SE_START], readRegion[SE_END]); + ISF_LOGGER.error("repeated read region({})", readRegion); } } } @@ -670,12 +672,13 @@ else if(regionCount > 1) } // keep track of which regions have been allocated from this fragment as a whole, so not counting each read separately - mExpressionReadTracker.processValidTranscript(transId, Lists.newArrayList(read1, read2), isUniqueTrans); + mExpressionReadTracker.processValidTranscript(transId, List.of(read1, read2), isUniqueTrans); } - mExpressionReadTracker.processUnsplicedGenes(comboTransMatchType, overlapGenes, validTranscripts, commonMappings, minMapQuality); + mExpressionReadTracker.processUnsplicedGenes( + comboTransMatchType, overlapGenes, validTranscripts, commonMappings, fragmentCount, isMultiMapped); - if(!read1.isSecondaryAlignment() && !read2.isSecondaryAlignment() && supportedGeneIsForward != null) + if(supportedGeneIsForward != null) { // track fragment strandedness boolean firstIsForward = read1.isFirstOfPair() ? !read1.isReadReversed() : !read2.isReadReversed(); @@ -684,25 +687,19 @@ else if(regionCount > 1) if(firstIsForward != secondIsForward) { if(firstIsForward == supportedGeneIsForward) - mCurrentGenes.addCount(FORWARD_STRAND, 1); + mCurrentGenes.addCount(FORWARD_STRAND, fragmentCount); else - mCurrentGenes.addCount(REVERSE_STRAND, 1); + mCurrentGenes.addCount(REVERSE_STRAND, fragmentCount); } } } - if(!read1.isSecondaryAlignment() && !read2.isSecondaryAlignment()) - { - mCurrentGenes.addCount(fragmentType, 1); - } + mCurrentGenes.addCount(fragmentType, fragmentCount); if(mReadDataWriter != null && mConfig.writeType(WriteType.READ)) { - for(GeneReadData geneReadData : overlapGenes) - { - writeReadData(mReadDataWriter, geneReadData, 0, read1, read2, fragmentType, validTranscripts.size()); - writeReadData(mReadDataWriter, geneReadData, 1, read2, read1, fragmentType, validTranscripts.size()); - } + writeReadData(mReadDataWriter, overlapGenes, read1, fragmentType, validTranscripts.size()); + writeReadData(mReadDataWriter, overlapGenes, read2, fragmentType, validTranscripts.size()); } } @@ -729,64 +726,11 @@ private Boolean findGeneStrand(final Read read, final List transcripts) private int calcFragmentLength(int transId, final Read read1, final Read read2) { - final TranscriptData transData = mCurrentGenes.getTranscripts().stream().filter(x -> x.TransId == transId).findFirst().orElse(null); + TranscriptData transData = mCurrentGenes.getTranscripts().stream().filter(x -> x.TransId == transId).findFirst().orElse(null); if(transData == null) return -1; - return calcFragmentLength(transData, read1, read2); - } - - public static int calcFragmentLength(final TranscriptData transData, final Read read1, final Read read2) - { - int minReadPos = min(read1.PosStart, read2.PosStart); - int maxReadPos = max(read1.PosEnd, read2.PosEnd); - return calcFragmentLength(transData, minReadPos, maxReadPos); - } - - public static int calcFragmentLength(final TranscriptData transData, final int minReadPos, final int maxReadPos) - { - // calculate fragment length within this transcript assuming it has been spliced - int transcriptBases = 0; - boolean startFound = false; - - for(final ExonData exon : transData.exons()) - { - if(!startFound) - { - if(minReadPos < exon.Start - MAX_SC_BASE_MATCH) - break; - - if(minReadPos > exon.End) - continue; - - if(maxReadPos <= exon.End) - { - // within same exon - return maxReadPos - minReadPos + 1; - } - - startFound = true; - transcriptBases = exon.End - max(exon.Start, minReadPos) + 1; - } - else - { - if(maxReadPos > exon.End) - { - transcriptBases += exon.baseLength(); - } - else if(maxReadPos < exon.Start) - { - break; - } - else - { - transcriptBases += maxReadPos - exon.Start + 1; - break; - } - } - } - - return transcriptBases; + return ReadTranscriptUtils.calcFragmentLength(transData, read1, read2); } private boolean reachedGeneReadLimit() @@ -800,45 +744,97 @@ private boolean reachedGeneReadLimit() return true; } - private void processEnrichedRegionRead(final SAMRecord record) + public List getTransComboData() { return mExpressionReadTracker.getTransComboData(); } + + private boolean altOverlapsExon(final GeneData gene, final ChrBaseRegion altRegion) { - if(mGeneReadCount >= mNextGeneCountLog) + List transcripts = mGeneTransCache.getTranscripts(gene.GeneId); + + if(transcripts == null) + return false; + + for(TranscriptData transData : transcripts) { - mNextGeneCountLog += GENE_LOG_COUNT; - ISF_LOGGER.info("chr({}) genes({}) enriched bamRecordCount({})", - mCurrentGenes.chromosome(), mCurrentGenes.geneNames(), mGeneReadCount); + for(ExonData exon : transData.exons()) + { + if(positionsOverlap(exon.Start, exon.End, altRegion.start(), altRegion.end())) + return true; + } } - if(reachedGeneReadLimit()) - return; + return false; + } - // check criteria for using the read for expression - if(!record.getReadPairedFlag() || record.isSecondaryAlignment() || record.getSupplementaryAlignmentFlag()) + private void recordMultiMapLoci(final Read read1, final Read read2, final List altLoci) + { + if(mMultiMapLociWriter == null) return; - if(record.getReadNegativeStrandFlag() == record.getMateNegativeStrandFlag()) - return; + // record a multi-mapped fragment's primary alignment plus each XA alternate locus (opt-in WriteType.MULTI_MAP_LOCI); + // InGeneCollection flags whether the locus falls within the gene collection currently being processed + int fragStart = min(read1.alignmentStart(), read2.alignmentStart()); + int fragEnd = max(read1.alignmentEnd(), read2.alignmentEnd()); + boolean primarySpliced = read1.containsSplit() || read2.containsSplit(); - if(!record.getReferenceName().equals(record.getMateReferenceName())) - return; + writeMultiMapLocus( + mMultiMapLociWriter, mCurrentGenes.id(), read1.id(), "PRIMARY", read1.chromosome(), fragStart, fragEnd, + primarySpliced, mCurrentGenes.geneNames(), true); - if(!record.getFirstOfPairFlag()) // only count 1 read per fragment - return; + int[] bounds = mCurrentGenes.regionBounds(); - // no further classification of fragment is performed - ie they are considered supporting - ++mEnrichedGeneFragments; + for(Read.AltAlignment locus : altLoci) + { + boolean inGeneCollection = locus.Region.Chromosome.equals(mCurrentGenes.chromosome()) + && positionsOverlap(locus.Region.start(), locus.Region.end(), bounds[SE_START], bounds[SE_END]); - // add to overall counts - since these are within a single exon, consider them supporting the transcript + unspliced - mCurrentGenes.addCount(TRANS_SUPPORTING, 1); + writeMultiMapLocus( + mMultiMapLociWriter, mCurrentGenes.id(), read1.id(), "XA", locus.Region.Chromosome, + locus.Region.start(), locus.Region.end(), locus.Spliced, altExonicGeneNames(locus), inGeneCollection); + } } - public List getTransComboData() { return mExpressionReadTracker.getTransComboData(); } + private String altExonicGeneNames(final Read.AltAlignment locus) + { + if(mGeneTransCache == null) + return ""; + + return mGeneTransCache.findGeneByRange(locus.Region.Chromosome, locus.Region.start(), locus.Region.end()).stream() + .filter(gene -> altOverlapsExon(gene, locus.Region)) + .map(gene -> gene.GeneName) + .collect(Collectors.joining(ITEM_DELIM)); + } + + private synchronized static void writeMultiMapLocus( + final BufferedWriter writer, int geneCollectionId, final String readId, final String recordType, + final String chromosome, int posStart, int posEnd, boolean spliced, final String genes, boolean inGeneCollection) + { + try + { + StringJoiner sj = new StringJoiner(TSV_DELIM); + sj.add(String.valueOf(geneCollectionId)); + sj.add(readId); + sj.add(recordType); + sj.add(chromosome); + sj.add(String.valueOf(posStart)); + sj.add(String.valueOf(posEnd)); + sj.add(String.valueOf(spliced)); + sj.add(genes); + sj.add(String.valueOf(inGeneCollection)); + writer.write(sj.toString()); + writer.newLine(); + } + catch(IOException e) + { + ISF_LOGGER.error("failed to write multi-map loci data: {}", e.toString()); + } + } - private void processIntronicReads(final List genes, final Read read1, final Read read2) + private void processIntronicReads( + final List genes, final Read read1, final Read read2, int fragmentCount, boolean multiMapped) { if(read1.containsSplit() || read2.containsSplit()) { - mCurrentGenes.addCount(ALT, 1); + mCurrentGenes.addCount(ALT, 1); // does not count duplicates since not expression related if(mAltSpliceJunctionFinder.enabled()) mAltSpliceJunctionFinder.evaluateFragmentReads(genes, read1, read2, Lists.newArrayList()); @@ -846,17 +842,13 @@ private void processIntronicReads(final List genes, final Read rea return; } - mExpressionReadTracker.processIntronicReads(genes, read1, read2); - - mCurrentGenes.addCount(UNSPLICED, 1); + mExpressionReadTracker.processIntronicReads(genes, read1, read2, fragmentCount, multiMapped); + mCurrentGenes.addCount(UNSPLICED, fragmentCount); if(mReadDataWriter != null && mConfig.writeType(WriteType.READ)) { - for(GeneReadData geneReadData : genes) - { - writeReadData(mReadDataWriter, geneReadData, 0, read1, read2, UNSPLICED, 0); - writeReadData(mReadDataWriter, geneReadData, 1, read2, read1, UNSPLICED, 0); - } + writeReadData(mReadDataWriter, genes, read1, UNSPLICED, 0); + writeReadData(mReadDataWriter, genes, read2, UNSPLICED, 0); } } @@ -866,9 +858,9 @@ private void processChimericNovelJunctions() if(!mAltSpliceJunctionFinder.enabled() || mChimericReads.getLocalChimericReads().isEmpty()) return; - final List invalidTrans = Lists.newArrayList(); + List invalidTrans = Lists.newArrayList(); - for(final List reads : mChimericReads.getLocalChimericReads()) + for(List reads : mChimericReads.getLocalChimericReads()) { Read read1 = null; Read read2 = null; @@ -900,10 +892,10 @@ else if(reads.size() == 3) if(read1 == null || read2 == null) continue; - int readPosMin = min(read1.PosStart, read2.PosStart); - int readPosMax = max(read1.PosEnd, read2.PosEnd); + int readPosMin = min(read1.alignmentStart(), read2.alignmentStart()); + int readPosMax = max(read1.alignmentEnd(), read2.alignmentEnd()); - final List overlapGenes = mCurrentGenes.findGenesCoveringRange(readPosMin, readPosMax, false); + List overlapGenes = mCurrentGenes.findGenesCoveringRange(readPosMin, readPosMax, false); mAltSpliceJunctionFinder.evaluateFragmentReads(overlapGenes, read1, read2, invalidTrans); } } @@ -940,112 +932,6 @@ public void registerKnownFusionPairs(final EnsemblDataCache geneTransCache) mChimericReads.registerKnownFusionPairs(geneTransCache); } - public static BufferedWriter createReadDataWriter(final IsofoxConfig config) - { - try - { - String outputFileName = config.formOutputFile("read_data.tsv"); - - BufferedWriter writer = createBufferedWriter(outputFileName, false); - - StringJoiner sj = new StringJoiner(TSV_DELIM); - sj.add(FLD_GENE_ID).add(FLD_GENE_NAME).add("ReadIndex").add("ReadId"); - sj.add(FLD_CHROMOSOME).add(FLD_POS_START).add(FLD_POS_END).add("Cigar").add("InsertSize").add("MateChr").add("MatePosStart"); - sj.add("Flags").add("FirstInPair").add("ReadReversed").add("SuppData").add("FragType"); - sj.add("TransId").add("TransClass").add("ValidTrans").add("ExonRank").add("ExonStart"); - sj.add("RegionStart").add("RegionEnd").add("RegionClass").add("ScRegionsStart").add("SvRegionsEnd"); - writer.write(sj.toString()); - writer.newLine(); - return writer; - } - catch (IOException e) - { - ISF_LOGGER.error("failed to create read data writer: {}", e.toString()); - return null; - } - } - - private synchronized static void writeReadData( - final BufferedWriter writer, final GeneReadData geneReadData, int readIndex, final Read read, final Read otherRead, - FragmentType geneReadType, int validTranscripts) - { - try - { - StringJoiner sj = new StringJoiner(TSV_DELIM); - - sj.add(geneReadData.Gene.GeneId); - sj.add(geneReadData.Gene.GeneName); - sj.add(String.valueOf(readIndex)); - sj.add(read.Id); - - sj.add(read.Chromosome); - sj.add(String.valueOf(read.PosStart)); - sj.add(String.valueOf(read.PosEnd)); - sj.add(read.cigarStr()); - sj.add(String.valueOf(read.fragmentInsertSize())); - sj.add(read.mateChromosome()); - sj.add(String.valueOf(read.mateStartPosition())); - - sj.add(String.valueOf(read.flags())); - sj.add(String.valueOf(read.isFirstOfPair())); - sj.add(String.valueOf(read.isReadReversed())); - sj.add(read.suppAlignmentAsStr()); - - sj.add(geneReadType.toString()); - - if(read.getTranscriptClassifications().isEmpty()) - { - sj.add("NONE").add("").add("").add("").add("").add("").add("").add("").add(""); - writer.write(sj.toString()); - writer.newLine(); - return; - } - - for(Map.Entry entry : read.getTranscriptClassifications().entrySet()) - { - int transId = entry.getKey(); - TransMatchType transType = entry.getValue(); - - for(Map.Entry rEntry : read.getMappedRegions().entrySet()) - { - RegionReadData region = rEntry.getKey(); - RegionMatchType matchType = rEntry.getValue(); - - if(!region.hasTransId(transId)) - continue; - - /* - if(validTranscriptType(read.getTranscriptClassification(transId))) - { - TranscriptData transData = geneReadData.getTranscripts().stream() - .filter(x -> x.TransId == transId).findFirst().orElse(null); - calcFragmentLength = transData != null ? calcFragmentLength(transData, read, otherRead) : -1; - } - */ - - StringJoiner transSj = new StringJoiner(TSV_DELIM); - transSj.add(sj.toString()); - - transSj.add(String.valueOf(transId)); - transSj.add(transType.toString()); - transSj.add(String.valueOf(validTranscripts)); - transSj.add(String.valueOf(region.getExonRank(transId))); - transSj.add(String.valueOf(region.start())); - transSj.add(String.valueOf(region.end())); - transSj.add(matchType.toString()); - transSj.add(String.valueOf(read.getSoftClipRegionsMatched()[SE_START])); - transSj.add(String.valueOf(read.getSoftClipRegionsMatched()[SE_END])); - - writer.write(transSj.toString()); - writer.newLine(); - } - } - } - catch(IOException e) - { - ISF_LOGGER.error("failed to write read data file: {}", e.toString()); - } - } @VisibleForTesting public void processReadRecords(final GeneCollection geneCollection, final List reads) @@ -1058,17 +944,15 @@ public void processReadRecords(final GeneCollection geneCollection, final List processRead(x)); } + @VisibleForTesting + public final FragmentTracker getFragmentTracker() { return mFragmentReads; } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/Isofox.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/Isofox.java index 29736eb848d..aaa448dbb60 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/Isofox.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/Isofox.java @@ -90,8 +90,6 @@ public Isofox(final IsofoxConfig config, final ConfigBuilder configBuilder) mGeneTransCache.setRequiredData(true, false, false, mConfig.CanonicalTranscriptOnly); mGeneTransCache.load(false); - mConfig.Filters.buildGeneRegions(mGeneTransCache); - mExpectedCountsCache = mConfig.ExpCountsFile != null || mConfig.applyGcBiasAdjust() ? new ExpectedCountsCache(mConfig) : null; mGcTranscriptCalcs = mConfig.applyGcBiasAdjust() ? new GcTranscriptCalculator(mConfig) : null; @@ -234,21 +232,18 @@ private void processBamFragments(final List chrTasks, fi { FragmentTypeCounts totalFragmentCounts = new FragmentTypeCounts(); - long enrichedGeneFragCount = 0; - GcRatioCounts nonEnrichedGcRatioCounts = new GcRatioCounts(); + GcRatioCounts gcRatioCounts = new GcRatioCounts(); for(ChromosomeTaskExecutor chrTask : chrTasks) { totalFragmentCounts.combine(chrTask.getCombinedCounts()); - enrichedGeneFragCount += chrTask.getEnrichedGenesFragmentCount(); - - nonEnrichedGcRatioCounts.mergeRatioCounts(chrTask.getNonEnrichedGcRatioCounts().getCounts()); + gcRatioCounts.mergeRatioCounts(chrTask.getNonEnrichedGcRatioCounts().getCounts()); } if(mConfig.applyGcBiasAdjust()) { - applyGcAdjustments(chrTasks, callableList, nonEnrichedGcRatioCounts); + applyGcAdjustments(chrTasks, callableList, gcRatioCounts); } if(mConfig.writeType(GC_RATIO)) @@ -263,9 +258,6 @@ private void processBamFragments(final List chrTasks, fi convertToPercentages(percentData); writeReadGcRatioCounts(mResultsWriter.getReadGcRatioWriter(), "ALL_PERC", percentData, true); - if(!mConfig.Filters.EnrichedGeneIds.isEmpty()) - writeReadGcRatioCounts(mResultsWriter.getReadGcRatioWriter(), "NON_ENRICHED", nonEnrichedGcRatioCounts.getCounts(), false); - if(mConfig.applyGcBiasAdjust()) { writeReadGcRatioCounts(mResultsWriter.getReadGcRatioWriter(), "TRANS_FIT_EXPECTED", @@ -281,10 +273,10 @@ private void processBamFragments(final List chrTasks, fi } // calculate a TPM for all transcripts before results are written - final List geneSummaryData = Lists.newArrayList(); + List geneSummaryData = Lists.newArrayList(); chrTasks.stream().forEach(x -> geneSummaryData.addAll(x.getGeneCollectionSummaryData())); - double[] tpmFactors = calcTpmFactors(geneSummaryData, mConfig.Filters.EnrichedGeneIds); + double[] tpmFactors = calcTpmFactors(geneSummaryData); PanelTpmNormaliser panelTpmNormaliser = new PanelTpmNormaliser(mConfig.PanelTpmNormFile); @@ -306,7 +298,7 @@ private void processBamFragments(final List chrTasks, fi // write summary statistics if(mConfig.runFunction(IsofoxFunction.TRANSCRIPT_COUNTS) || mConfig.runFunction(IsofoxFunction.STATISTICS)) { - double medianGCRatio = nonEnrichedGcRatioCounts.getPercentileRatio(0.5); + double medianGCRatio = gcRatioCounts.getPercentileRatio(0.5); int lowCoverageThreshold = LOW_COVERAGE_THRESHOLD; int splicedGeneThreshold = SPLICE_GENE_THRESHOLD; @@ -320,9 +312,9 @@ private void processBamFragments(final List chrTasks, fi splicedGeneThreshold = (int)(panelGeneCoverage * SPLICE_GENE_THRESHOLD); } - final RnaStatistics summaryStats = createSummaryStats( - totalFragmentCounts, enrichedGeneFragCount, spliceGeneCount, - medianGCRatio, mFragmentLengthDistribution, mMaxObservedReadLength > 0 ? mMaxObservedReadLength : mConfig.ReadLength, + RnaStatistics summaryStats = createSummaryStats( + totalFragmentCounts, spliceGeneCount, medianGCRatio, mFragmentLengthDistribution, + mMaxObservedReadLength > 0 ? mMaxObservedReadLength : mConfig.ReadLength, lowCoverageThreshold, splicedGeneThreshold); mResultsWriter.writeSummaryStats(summaryStats); @@ -355,7 +347,7 @@ private Map> getChromosomeGeneLists() if(!mConfig.Filters.SpecificChrRegions.hasFilters()) return mGeneTransCache.getChrGeneDataMap(); - final Map> chrGeneMap = Maps.newHashMap(); + Map> chrGeneMap = Maps.newHashMap(); if(mConfig.Filters.SpecificChrRegions.Regions.isEmpty()) { @@ -374,7 +366,22 @@ private Map> getChromosomeGeneLists() .filter(x -> positionsOverlap(region.start(), region.end(), x.GeneStart, x.GeneEnd)) .collect(Collectors.toList()); - chrGeneMap.put(region.Chromosome, regionGeneList); + if(!regionGeneList.isEmpty()) + { + List chrGeneDataList = chrGeneMap.get(region.Chromosome); + + if(chrGeneDataList == null) + { + chrGeneDataList = Lists.newArrayList(); + chrGeneMap.put(region.Chromosome, chrGeneDataList); + } + + for(GeneData geneData : regionGeneList) + { + if(!chrGeneDataList.contains(geneData)) + chrGeneDataList.add(geneData); + } + } } } @@ -385,7 +392,7 @@ private void calcFragmentLengths(final Map> chrGeneMap) { int requiredFragCount = mConfig.FragmentLengthSamplingCount / chrGeneMap.size(); // split evenly amongst chromosomes - final List fragSizeCalcs = Lists.newArrayList(); + List fragSizeCalcs = Lists.newArrayList(); for(Map.Entry> entry : chrGeneMap.entrySet()) { @@ -394,7 +401,7 @@ private void calcFragmentLengths(final Map> chrGeneMap) fragSizeCalcs.add(fragSizeCalc); } - final List> callableList = fragSizeCalcs.stream().collect(Collectors.toList()); + List> callableList = fragSizeCalcs.stream().collect(Collectors.toList()); boolean validExecution = TaskExecutor.executeTasks(callableList, mConfig.Threads); if(!validExecution) @@ -435,8 +442,8 @@ private boolean countBamReads(final Map> chrGeneMap) { ISF_LOGGER.info("basic BAM read counts"); - final List taskList = Lists.newArrayList(); - final List> callableList = Lists.newArrayList(); + List taskList = Lists.newArrayList(); + List> callableList = Lists.newArrayList(); for(Map.Entry> entry : chrGeneMap.entrySet()) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConfig.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConfig.java index 54bd90bc457..745eeb0d06e 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConfig.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConfig.java @@ -8,12 +8,15 @@ import static com.hartwig.hmftools.common.genome.refgenome.RefGenomeSource.loadRefGenome; import static com.hartwig.hmftools.common.genome.refgenome.RefGenomeVersion.V37; import static com.hartwig.hmftools.common.rna.RnaCommon.ISF_FILE_ID; +import static com.hartwig.hmftools.common.utils.config.CommonConfig.LOG_READ_IDS; +import static com.hartwig.hmftools.common.utils.config.CommonConfig.LOG_READ_IDS_DESC; import static com.hartwig.hmftools.common.utils.config.CommonConfig.NEO_DIR_CFG; import static com.hartwig.hmftools.common.utils.config.CommonConfig.NEO_DIR_DESC; import static com.hartwig.hmftools.common.utils.config.CommonConfig.PERF_DEBUG; import static com.hartwig.hmftools.common.utils.config.CommonConfig.PERF_DEBUG_DESC; import static com.hartwig.hmftools.common.utils.config.CommonConfig.SAMPLE; import static com.hartwig.hmftools.common.utils.config.CommonConfig.SAMPLE_DESC; +import static com.hartwig.hmftools.common.utils.config.CommonConfig.parseLogReadIds; import static com.hartwig.hmftools.common.utils.config.ConfigItem.enumValueSelectionAsStr; import static com.hartwig.hmftools.common.utils.config.ConfigUtils.addLoggingOptions; import static com.hartwig.hmftools.common.utils.config.ConfigUtils.loadDelimitedIdFile; @@ -44,17 +47,19 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.hartwig.hmftools.common.driver.panel.DriverGene; import com.hartwig.hmftools.common.driver.panel.DriverGeneFile; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeInterface; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeVersion; -import com.hartwig.hmftools.common.region.SpecificRegions; import com.hartwig.hmftools.common.utils.config.ConfigBuilder; import com.hartwig.hmftools.isofox.adjusts.FragmentSize; import com.hartwig.hmftools.isofox.common.GeneRegionFilters; @@ -141,7 +146,7 @@ public class IsofoxConfig public final boolean RunValidations; public final boolean RunPerfChecks; public final int Threads; - public final List FilteredReadIds; + public final Set LogReadIds; public static final Logger ISF_LOGGER = LogManager.getLogger(IsofoxConfig.class); @@ -155,7 +160,7 @@ public IsofoxConfig(final ConfigBuilder configBuilder) { final String[] functionsStr = configBuilder.getValue(FUNCTIONS).split(ITEM_DELIM); - for(final String functionStr : functionsStr) + for(String functionStr : functionsStr) { Functions.add(IsofoxFunction.valueOf(functionStr)); } @@ -207,6 +212,7 @@ public IsofoxConfig(final ConfigBuilder configBuilder) GeneReadLimit = Integer.parseInt(configBuilder.getValue(GENE_READ_LIMIT, "0")); MaxFragmentLength = configBuilder.getInteger(LONG_FRAGMENT_LIMIT); + IsofoxConstants.SINGLE_MAP_QUALITY = (short)configBuilder.getInteger(SINGLE_MAP_QUAL); DropDuplicates = configBuilder.hasValue(DROP_DUPLICATES); @@ -244,9 +250,7 @@ public IsofoxConfig(final ConfigBuilder configBuilder) RunValidations = configBuilder.hasValue(RUN_VALIDATIONS); RunPerfChecks = configBuilder.hasValue(PERF_CHECKS); - - FilteredReadIds = configBuilder.hasValue(FILTER_READS_FILE) ? - loadDelimitedIdFile(configBuilder.getValue(FILTER_READS_FILE), "FilteredReadIds", CSV_DELIM) : null; + LogReadIds = Sets.newHashSet(parseLogReadIds(configBuilder).stream().collect(Collectors.toSet())); } public boolean isValid() @@ -317,8 +321,6 @@ public String formOutputFile(final String fileId) return OutputDir + SampleId + ISF_FILE_ID + fileId; } - public boolean skipFilteredRead(final String readId) { return FilteredReadIds != null && !FilteredReadIds.contains(readId); } - public IsofoxConfig(final RefGenomeInterface refGenome) { SampleId = "TEST"; @@ -361,7 +363,7 @@ public IsofoxConfig(final RefGenomeInterface refGenome) RunValidations = true; RunPerfChecks = false; Threads = 0; - FilteredReadIds = null; + LogReadIds = Collections.emptySet(); } public static void registerConfig(final ConfigBuilder configBuilder) @@ -410,5 +412,6 @@ public static void registerConfig(final ConfigBuilder configBuilder) FusionConfig.registerConfig(configBuilder); addThreadOptions(configBuilder); configBuilder.addFlag(PERF_DEBUG, PERF_DEBUG_DESC); + configBuilder.addConfigItem(LOG_READ_IDS, LOG_READ_IDS_DESC); } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConstants.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConstants.java index d14c850ee1b..128770e4682 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConstants.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/IsofoxConstants.java @@ -19,11 +19,8 @@ public class IsofoxConstants public static final double GC_RATIO_BUCKET = 0.01; - public static final short DEFAULT_SINGLE_MAP_QUALITY = 255; + public static final short DEFAULT_SINGLE_MAP_QUALITY = 60; public static short SINGLE_MAP_QUALITY = DEFAULT_SINGLE_MAP_QUALITY; - public static short MULTI_MAP_QUALITY_THRESHOLD = 3; // multi-mapped fragments are given map quals of 3 or lower - - public static final int ENRICHED_GENE_BUFFER = 1000; public static final int MAX_NOVEL_SJ_DISTANCE = 500000; // beyond which a fragment will be considered chimeric @@ -35,28 +32,6 @@ public class IsofoxConstants public static final double HIGH_EXPRESSION_FOLD_CHANGE_HIGH = 2.3; public static final double HIGH_EXPRESSION_PERCENTILE_HIGH = 0.98; - public static void populateEnrichedGeneIds(final List geneIds, final RefGenomeVersion version) - { - if(version == RefGenomeVersion.V38) - { - geneIds.add("ENSG00000276168"); // RN7SL1 - geneIds.add("ENSG00000274012"); // RN7SL2 - geneIds.add("ENSG00000278771"); // RN7SL3 - geneIds.add("ENSG00000263740"); // RN7SL4P - geneIds.add("ENSG00000283293"); // RN7SK - geneIds.add("ENSG00000265735"); // RN7SL5P - } - else - { - geneIds.add("ENSG00000265150"); - geneIds.add("ENSG00000258486"); - geneIds.add("ENSG00000202198"); - geneIds.add("ENSG00000266037"); - geneIds.add("ENSG00000263740"); - geneIds.add("ENSG00000265735"); - } - } - // process genes with longer expected processing times first public static final List PRIORITISED_CHROMOSOMES = Lists.newArrayList("14", "22", "2", "3", "6", "9", "16", "17"); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/WriteType.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/WriteType.java index b18c816ec77..66ce1b259e1 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/WriteType.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/WriteType.java @@ -14,11 +14,12 @@ public enum WriteType FRAG_LENGTH, // intronic fragment lengths FRAG_LENGTH_BY_GENE, // intronic fragment lengths by gene READ, // all read attribution - CHIMERIC_READ, // chimeric reads + FUSION_FRAGMENT, CHIMERIC_POSITION_DATA, // chimeric fragments bucketed by position SPLICE_SITE, // splice site support TRANS_COMBO, // transcript group data for EM algo - GC_RATIO; // GC ratio counts from all genic reads + GC_RATIO, // GC ratio counts from all genic reads + MULTI_MAP_LOCI; // multi-mapped read primary + XA alternate loci public static List parseConfig(final String configStr) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/adjusts/FragmentSizeCalcs.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/adjusts/FragmentSizeCalcs.java index cf4fc218475..f2fd811a8e1 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/adjusts/FragmentSizeCalcs.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/adjusts/FragmentSizeCalcs.java @@ -145,13 +145,12 @@ private void calcSampleFragmentSize() int currentGeneIndex = 0; int nextLogCount = 100; + boolean hasSpecificRegions = mConfig.Filters.SpecificChrRegions.hasFilters(); + while(currentGeneIndex < mGeneDataList.size()) { currentGeneIndex = findNextOverlappingGenes(mGeneDataList, currentGeneIndex, overlappingGenes); - if(overlappingGenes.stream().anyMatch(x -> mConfig.Filters.EnrichedGeneIds.contains(x.GeneId))) - continue; - mCurrentTransDataList.clear(); mFragmentTracker.clear(); @@ -168,7 +167,7 @@ private void calcSampleFragmentSize() mCurrentTransDataList.addAll(mGeneTransCache.getTranscripts(geneData.GeneId)); } - if(mCurrentTransDataList.isEmpty() || mCurrentTransDataList.size() > MAX_GENE_TRANS) + if(!hasSpecificRegions && (mCurrentTransDataList.isEmpty() || mCurrentTransDataList.size() > MAX_GENE_TRANS)) continue; int geneLength = mCurrentGenesRange[SE_END] - mCurrentGenesRange[SE_START]; @@ -178,11 +177,6 @@ private void calcSampleFragmentSize() ChrBaseRegion sliceRegion = new ChrBaseRegion(mChromosome, mCurrentGenesRange); - List excludedRegions = mConfig.Filters.findExcludedRegions(sliceRegion); - - if(!excludedRegions.isEmpty()) - continue; - if(currentGeneIndex >= nextLogCount) { nextLogCount += 100; @@ -195,7 +189,6 @@ private void calcSampleFragmentSize() mCurrentFragmentCount = 0; mCurrentGenes = overlappingGenes.get(0).GeneName; - ISF_LOGGER.trace("chromosome({}) gene({} index={}) fragCount({}) nextRegion({})", mChromosome, mCurrentGenes, currentGeneIndex, mProcessedFragments, sliceRegion); @@ -311,7 +304,7 @@ private boolean isCandidateRecord(final SAMRecord record) int posStart = record.getStart(); int posEnd = record.getEnd(); - for(final TranscriptData transData : mCurrentTransDataList) + for(TranscriptData transData : mCurrentTransDataList) { if(transData.exons().stream().anyMatch(x -> positionsOverlap(posStart, posEnd, x.Start, x.End))) return false; @@ -394,7 +387,7 @@ public static void setConfigFragmentLengthData(final IsofoxConfig config, final int lengthCount = 0; - for(final FragmentSize fragLengthCount : fragmentLengths) + for(FragmentSize fragLengthCount : fragmentLengths) { if(fragLengthCount.Length >= currentRangeMin && fragLengthCount.Length <= currentRangeMax) { @@ -418,7 +411,7 @@ public static List calcPercentileData(final List fragmentL int currentTotal = 0; int prevLength = 0; - for(final FragmentSize fragLengthCount : fragmentLengths) + for(FragmentSize fragLengthCount : fragmentLengths) { double nextPercTotal = (currentTotal + fragLengthCount.Frequency) / totalFragments; @@ -533,7 +526,7 @@ public static void writeFragmentLengths(final IsofoxConfig config, final List readRegions) + final RefGenomeInterface refGenome, final String chromosome, final List readRegions) { double gcRatioTotal = 0; int basesTotal = 0; - for(final int[] region : readRegions) + for(BaseRegion region : readRegions) { - final String bases = refGenome.getBaseString(chromosome, region[SE_START], region[SE_END]); + final String bases = refGenome.getBaseString(chromosome, region.start(), region.end()); basesTotal += bases.length(); gcRatioTotal += calcGcRatio(bases) * bases.length(); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/cohort/CohortConfig.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/cohort/CohortConfig.java index 4efb11bd41d..29d94380ff1 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/cohort/CohortConfig.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/cohort/CohortConfig.java @@ -164,13 +164,13 @@ public static boolean formSampleFilenames(final CohortConfig config, final Analy { List missingSampleIds = Lists.newArrayList(); - for(final String sampleId : config.SampleData.SampleIds) + for(String sampleId : config.SampleData.SampleIds) { String filename = formSampleFilename(config, sampleId, dataType); final Path path = Paths.get(filename); - if (!Files.exists(path)) + if(!Files.exists(path)) { if(config.FailOnMissingSample) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BamReadCounter.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BamReadCounter.java index 4a53091c97d..2ea822deaf7 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BamReadCounter.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BamReadCounter.java @@ -28,7 +28,6 @@ import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.region.ChrBaseRegion; import com.hartwig.hmftools.isofox.IsofoxConfig; -import com.hartwig.hmftools.isofox.WriteType; import com.hartwig.hmftools.isofox.results.ResultsWriter; import htsjdk.samtools.SAMFlag; @@ -49,7 +48,6 @@ public class BamReadCounter implements Callable private int mTotalReadCount; private int mCurrentGeneReadCount; private final FragmentTypeCounts mFragmentTypeCounts; - private int mSecondaryReads; private String mChromosome; private final List mGeneDataList; private String mCurrentGenes; @@ -71,7 +69,6 @@ public BamReadCounter(final IsofoxConfig config, final ResultsWriter resultsWrit mTotalReadCount = 0; mCurrentGeneReadCount = 0; mCurrentGenes = ""; - mSecondaryReads = 0; mFragmentTypeCounts = new FragmentTypeCounts(); mMaqQualFrequencies = new int[4]; } @@ -135,8 +132,8 @@ private void processBam() mBamSlicer.slice(mSamReader, regions, this::processBamRead); } - ISF_LOGGER.info("chromosome({}) processing complete: total({}) duplicates({}) chimeric({}) secondaries({}) mapQuals(0={} 1={} 2={} 3={})", - mChromosome, mTotalReadCount, mFragmentTypeCounts.typeCount(DUPLICATE), mFragmentTypeCounts.typeCount(CHIMERIC), mSecondaryReads, + ISF_LOGGER.info("chromosome({}) processing complete: total({}) duplicates({}) chimeric({}) mapQuals(0={} 1={} 2={} 3={})", + mChromosome, mTotalReadCount, mFragmentTypeCounts.typeCount(DUPLICATE), mFragmentTypeCounts.typeCount(CHIMERIC), mMaqQualFrequencies[0], mMaqQualFrequencies[1], mMaqQualFrequencies[2], mMaqQualFrequencies[3]); } @@ -158,9 +155,6 @@ private void processBamRead(final SAMRecord record) if((record.getFlags() & SAMFlag.SUPPLEMENTARY_ALIGNMENT.intValue()) != 0) mFragmentTypeCounts.addCount(CHIMERIC); - if(record.isSecondaryAlignment()) - ++mSecondaryReads; - if(record.getMappingQuality() <= 3) { mMaqQualFrequencies[record.getMappingQuality()]++; @@ -184,7 +178,7 @@ public static BufferedWriter createReadDataWriter(final IsofoxConfig config) BufferedWriter writer = createBufferedWriter(outputFileName, false); writer.write("GeneId,ReadId,Chromosome,PosStart,PosEnd,Cigar,Flags,InsertSize"); - writer.write(",MateChr,MatePosStart,FirstInPair,ReadReversed,Duplicate,Secondary,Supplementary,SuppData"); + writer.write(",MateChr,MatePosStart,FirstInPair,ReadReversed,Duplicate,Supplementary,SuppData"); writer.newLine(); return writer; } @@ -208,7 +202,7 @@ private static synchronized void writeReadData(final BufferedWriter writer, fina writer.write(String.format(",%s,%s,%s,%s,%s,%s", record.getFirstOfPairFlag(), record.getReadNegativeStrandFlag(), record.getDuplicateReadFlag(), - record.getSecondOfPairFlag(), record.getSupplementaryAlignmentFlag(), suppData != null ? suppData.asDelimStr() : "N/A")); + record.getSupplementaryAlignmentFlag(), suppData != null ? suppData.asDelimStr() : "N/A")); writer.newLine(); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BaseDepth.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BaseDepth.java index da1f5fed314..2da7c17f928 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BaseDepth.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/BaseDepth.java @@ -13,8 +13,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import com.google.common.collect.Maps; +import com.hartwig.hmftools.common.region.BaseRegion; public class BaseDepth { @@ -63,15 +65,15 @@ public BaseDepth(final BaseDepth other, final Map depthMap) public int length() { return mBaseRange[SE_END] - mBaseRange[SE_START] + 1; } - public void processRead(final List readCoords) + public void processRead(final List readAlignments) { if(mDepth == null) return; - for(final int[] readSection : readCoords) + for(BaseRegion alignedSection : readAlignments) { - int readStartPos = readSection[SE_START]; - int readEndPos = readSection[SE_END]; + int readStartPos = alignedSection.start(); + int readEndPos = alignedSection.end(); if(readStartPos > mBaseRange[SE_END] || readEndPos < mBaseRange[SE_START]) continue; @@ -98,7 +100,7 @@ public Map createPositionMap(final Set candidateJuncti { final Map depthMap = Maps.newHashMap(); - for(final Integer position : candidateJunctions) + for(Integer position : candidateJunctions) { if(!positionWithin(position, mBaseRange[SE_START], mBaseRange[SE_END])) continue; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/CommonUtils.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/CommonUtils.java index 5791e19fb6f..d1014310757 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/CommonUtils.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/CommonUtils.java @@ -2,7 +2,6 @@ import static java.lang.Math.max; import static java.lang.Math.min; -import static java.lang.Math.round; import static com.hartwig.hmftools.common.genome.refgenome.RefGenomeVersion.V37; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; @@ -18,26 +17,28 @@ import com.google.common.collect.Lists; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeCoordinates; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeVersion; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.common.sv.StructuralVariantType; public class CommonUtils { - public static List deriveCommonRegions(final List regions1, final List regions2) + public static List deriveCommonRegions(final List regions1, final List regions2) { // merges any overlapping regoins to create a combined set without overlaps if(regions1.isEmpty() || regions2.isEmpty()) return regions1.isEmpty() ? regions2 : regions1; - List newRegions = Lists.newArrayList(); - // early exit for non-overlapping regions - if(regions1.get(regions1.size() - 1)[SE_END] < regions2.get(0)[SE_START]) + int totalRegions = regions1.size() + regions2.size(); + List newRegions = Lists.newArrayListWithCapacity(totalRegions); + + if(regions1.get(regions1.size() - 1).end() < regions2.get(0).start()) { newRegions.addAll(regions1); newRegions.addAll(regions2); return newRegions; } - else if(regions2.get(regions2.size() - 1)[SE_END] < regions1.get(0)[SE_START]) + else if(regions2.get(regions2.size() - 1).end() < regions1.get(0).start()) { newRegions.addAll(regions2); newRegions.addAll(regions1); @@ -49,19 +50,19 @@ else if(regions2.get(regions2.size() - 1)[SE_END] < regions1.get(0)[SE_START]) while(index1 < regions1.size() || index2 < regions2.size()) { - int[] region1 = index1 < regions1.size() ? regions1.get(index1) : null; - int[] region2 = index2 < regions2.size() ? regions2.get(index2) : null; + BaseRegion region1 = index1 < regions1.size() ? regions1.get(index1) : null; + BaseRegion region2 = index2 < regions2.size() ? regions2.get(index2) : null; if(region1 != null && region2 != null) { // add the earlier region if not overlapping - if(region1[SE_END] < region2[SE_START] - 1) + if(region1.end() < region2.start() - 1) { newRegions.add(region1); ++index1; continue; } - else if(region2[SE_END] < region1[SE_START] - 1) + else if(region2.end() < region1.start() - 1) { newRegions.add(region2); ++index2; @@ -69,7 +70,7 @@ else if(region2[SE_END] < region1[SE_START] - 1) } // merge the overlapping regions - int[] newRegion = new int[] { min(region1[SE_START], region2[SE_START]), max(region1[SE_END], region2[SE_END]) }; + BaseRegion newRegion = new BaseRegion(min(region1.start(), region2.start()), max(region1.end(), region2.end())); newRegions.add(newRegion); ++index1; @@ -83,16 +84,16 @@ else if(region2[SE_END] < region1[SE_START] - 1) boolean merged = false; - if(region1 != null && region1[SE_START] <= newRegion[SE_END] + 1) + if(region1 != null && region1.start() <= newRegion.end() + 1) { - newRegion[SE_END] = max(region1[SE_END], newRegion[SE_END]); + newRegion.setEnd(max(region1.end(), newRegion.end())); ++index1; merged = true; } - if(region2 != null && region2[SE_START] <= newRegion[SE_END] + 1) + if(region2 != null && region2.start() <= newRegion.end() + 1) { - newRegion[SE_END] = max(region2[SE_END], newRegion[SE_END]); + newRegion.setEnd(max(region2.end(), newRegion.end())); ++index2; merged = true; } @@ -131,7 +132,7 @@ public static int findStringOverlaps(final String str1, final String str2) // first compare bases at same indices, making note of the first difference if there is one while(i < str1.length() && j < str2.length()) { - if (str1.charAt(i) == str2.charAt(j)) + if(str1.charAt(i) == str2.charAt(j)) ++matched; else if(mismatchIndex == -1) mismatchIndex = i; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentTracker.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentTracker.java index f61cdba02ae..cbe7c549831 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentTracker.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentTracker.java @@ -21,12 +21,12 @@ public FragmentTracker() public Read checkRead(final Read read) { - Read otherRead = (Read)mReadMap.remove(read.Id); + Read otherRead = (Read)mReadMap.remove(read.id()); if(otherRead != null) return otherRead; - mReadMap.put(read.Id, read); + mReadMap.put(read.id(), read); return null; } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentType.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentType.java index c4b610e03f0..aa399dffc16 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentType.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/FragmentType.java @@ -8,7 +8,7 @@ public enum FragmentType UNSPLICED, CHIMERIC, DUPLICATE, - LOW_MAP_QUAL, + MULTI_MAPPED, FORWARD_STRAND, REVERSE_STRAND; } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneCollection.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneCollection.java index 134bc92b7fa..87d5bd9f4c4 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneCollection.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneCollection.java @@ -24,10 +24,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; import com.hartwig.hmftools.common.gene.ExonData; import com.hartwig.hmftools.common.gene.TranscriptData; -import com.hartwig.hmftools.isofox.IsofoxConfig; public class GeneCollection { @@ -45,9 +43,6 @@ public class GeneCollection private final List mCommonExonicRegions; // merge any overlapping exons, to form a set of exonic regions for the gene private final List mTranscripts; - private List mEnrichedTranscripts; - private int[] mEnrichedRegion; // special regions of high read density - // summary results private final Map mTranscriptReadCounts; // count of fragments support types for each transcript, and whether unique private final FragmentTypeCounts mFragmentCounts; // counts by various classifications @@ -76,9 +71,6 @@ public GeneCollection(int id, final List genes) mTranscriptReadCounts = Maps.newHashMap(); mFragmentCounts = new FragmentTypeCounts(); - - mEnrichedTranscripts = null; - mEnrichedRegion = null; } public int id() { return mId; } @@ -99,7 +91,7 @@ public GeneCollection(int id, final List genes) public int getStrand(int transId) { - final GeneReadData gene = mTransIdsGeneMap.get(transId); + GeneReadData gene = mTransIdsGeneMap.get(transId); return gene != null ? gene.Gene.Strand : 0; } @@ -125,64 +117,9 @@ public String geneNames(int maxCount) return geneNames.toString(); } - public boolean containsEnrichedRegion() { return mEnrichedRegion != null; } - public List getEnrichedTranscripts() { return mEnrichedTranscripts; } - public int[] getEnrichedRegion() { return mEnrichedRegion; } - - public boolean inEnrichedRegion(int posStart, int posEnd) - { - if(mEnrichedRegion == null) - return false; - - return positionsOverlap(posStart, posEnd, mEnrichedRegion[SE_START], mEnrichedRegion[SE_END]); - } - - public void markEnrichedAndExcludedGenes(final IsofoxConfig config, final EnsemblDataCache geneTransCache) - { - if(config.Filters.EnrichedGeneIds.isEmpty()) - return; - - for(GeneReadData geneReadData : mGenes) - { - if(config.Filters.EnrichedGeneIds.contains(geneReadData.Gene.GeneId)) - { - mEnrichedTranscripts = Lists.newArrayList(geneTransCache.getTranscripts(geneReadData.Gene.GeneId)); - mEnrichedRegion = new int[SE_PAIR]; - - for(TranscriptData transData : mEnrichedTranscripts) - { - for(ExonData exonData : transData.exons()) - { - mEnrichedRegion[SE_START] = mEnrichedRegion[SE_START] > 0 - ? min(mEnrichedRegion[SE_START], exonData.Start) : exonData.Start; - - mEnrichedRegion[SE_END] = max(mEnrichedRegion[SE_END], exonData.End); - } - } - } - } - } - - public void setEnrichedTranscripts(final List transDataList) - { - mEnrichedTranscripts = Lists.newArrayList(transDataList); - mEnrichedRegion = new int[SE_PAIR]; - - for(TranscriptData transData : mEnrichedTranscripts) - { - for(ExonData exonData : transData.exons()) - { - mEnrichedRegion[SE_START] = mEnrichedRegion[SE_START] > 0 - ? min(mEnrichedRegion[SE_START], exonData.Start) : exonData.Start; - - mEnrichedRegion[SE_END] = max(mEnrichedRegion[SE_END], exonData.End); - } - } - } - public void setReadGeneCollections(final Read read, final int[] nonGenicBounds) { - if(positionsWithin(read.PosStart, read.PosEnd, mRegionBounds[SE_START], mRegionBounds[SE_END])) + if(positionsWithin(read.alignmentStart(), read.alignmentEnd(), mRegionBounds[SE_START], mRegionBounds[SE_END])) { read.setGeneCollection(SE_START, mId, true); read.setGeneCollection(SE_END, mId, true); @@ -190,10 +127,9 @@ public void setReadGeneCollections(final Read read, final int[] nonGenicBounds) else { // mark any read extending beyond this gene collection's bounds in part or full - for (int se = SE_START; se <= SE_END; ++se) + for(int se = SE_START; se <= SE_END; ++se) { - if(positionWithin( - read.getCoordsBoundary(se), mRegionBounds[SE_START], mRegionBounds[SE_END])) + if(positionWithin(read.getCoordsBoundary(se), mRegionBounds[SE_START], mRegionBounds[SE_END])) { read.setGeneCollection(se, mId, true); } @@ -209,9 +145,9 @@ else if(positionWithin(read.getCoordsBoundary(se), nonGenicBounds[SE_START], non private void buildCache() { - for(final GeneReadData gene : mGenes) + for(GeneReadData gene : mGenes) { - for(final TranscriptData transData : gene.getTranscripts()) + for(TranscriptData transData : gene.getTranscripts()) { mTransIdsGeneMap.put(transData.TransId, gene); mTranscripts.add(transData); @@ -223,12 +159,12 @@ private void buildCache() generateExonicRegions(gene.Gene.GeneId, mChromosome, mExonRegions, gene.getTranscripts()); // cache the relevant set of exon regions back into the gene for convenience - for(final TranscriptData transData : gene.getTranscripts()) + for(TranscriptData transData : gene.getTranscripts()) { - for (final ExonData exon : transData.exons()) + for(ExonData exon : transData.exons()) { - final RegionReadData exonReadData = findExonRegion(mExonRegions, exon.Start, exon.End); - if (exonReadData == null) + RegionReadData exonReadData = findExonRegion(mExonRegions, exon.Start, exon.End); + if(exonReadData == null) { ISF_LOGGER.error("genes({}) failed to create exonic regions", geneNames()); return; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneReadData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneReadData.java index ba35a288ff8..90762117d14 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneReadData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneReadData.java @@ -13,6 +13,7 @@ import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.gene.TranscriptData; +import com.hartwig.hmftools.common.region.BaseRegion; public class GeneReadData { @@ -84,20 +85,20 @@ public static void generateCommonExonicRegions(final List region if(regions.isEmpty()) return; - List commonRegions = Lists.newArrayList(new int[] {regions.get(0).start(), regions.get(0).end()}); + List commonRegions = Lists.newArrayList(new BaseRegion(regions.get(0).Region.start(), regions.get(0).Region.end())); for(int i = 1; i < regions.size(); ++i) { - List nextRegion = Lists.newArrayList(new int[] {regions.get(i).start(), regions.get(i).end()}); + List nextRegion = Lists.newArrayList(new BaseRegion(regions.get(i).Region.start(), regions.get(i).Region.end())); commonRegions = deriveCommonRegions(commonRegions, nextRegion); } - allCommonRegions.addAll(commonRegions); + commonRegions.forEach(x -> allCommonRegions.add(new int[] {x.start(), x.end()} )); } public int calcExonicRegionLength() { - final List commonExonicRegions = Lists.newArrayList(); + List commonExonicRegions = Lists.newArrayList(); generateCommonExonicRegions(mExonRegions, commonExonicRegions); return commonExonicRegions.stream().mapToInt(x -> x[SE_END] - x[SE_START]).sum(); } @@ -115,13 +116,13 @@ public String toString() public static List createGeneReadData(final List geneDataList, final EnsemblDataCache geneTransCache) { - final List geneReadDataList = Lists.newArrayList(); + List geneReadDataList = Lists.newArrayList(); for(GeneData geneData : geneDataList) { GeneReadData geneReadData = new GeneReadData(geneData); - final List geneTranscripts = geneTransCache.getTranscripts(geneData.GeneId); + List geneTranscripts = geneTransCache.getTranscripts(geneData.GeneId); if(geneTranscripts == null || geneTranscripts.isEmpty()) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneRegionFilters.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneRegionFilters.java index 10e8931311f..9ab98bd9c0a 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneRegionFilters.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/GeneRegionFilters.java @@ -1,29 +1,17 @@ package com.hartwig.hmftools.isofox.common; -import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; -import static com.hartwig.hmftools.common.region.ChrBaseRegion.loadChrBaseRegions; import static com.hartwig.hmftools.common.region.SpecificRegions.addSpecificChromosomesRegionsConfig; import static com.hartwig.hmftools.common.utils.config.ConfigUtils.GENE_ID_FILE; import static com.hartwig.hmftools.common.utils.config.ConfigUtils.GENE_ID_FILE_DESC; import static com.hartwig.hmftools.common.utils.config.ConfigUtils.loadGeneIdsFile; -import static com.hartwig.hmftools.common.utils.file.FileDelimiters.ITEM_DELIM; import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; -import static com.hartwig.hmftools.isofox.IsofoxConstants.ENRICHED_GENE_BUFFER; -import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.hartwig.hmftools.common.bam.SupplementaryReadData; -import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; -import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.genome.chromosome.HumanChromosome; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeVersion; -import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.common.region.SpecificRegions; import com.hartwig.hmftools.common.utils.config.ConfigBuilder; import com.hartwig.hmftools.common.region.ChrBaseRegion; @@ -36,27 +24,19 @@ public class GeneRegionFilters public SpecificRegions SpecificChrRegions; public final List RestrictedGeneIds; // limit expression analysis to a set of panel genes - public final List EnrichedGeneIds; // genes to count by not fully process for any functional purpose - public final Map> ExcludedRegions; public final List ImmuneGeneRegions; private final RefGenomeVersion mRefGenomeVersion; private boolean mHasSpecificRegions; - // config - private static final String ENRICHED_GENE_IDS = "enriched_gene_ids"; - private static final String EXCLUDED_REGIONS = "excluded_regions"; - public GeneRegionFilters(final RefGenomeVersion refGenomeVersion) { RestrictedGeneIds = Lists.newArrayList(); - EnrichedGeneIds = Lists.newArrayList(); SpecificChrRegions = new SpecificRegions(); mHasSpecificRegions = false; ImmuneGeneRegions = Lists.newArrayList(); - ExcludedRegions = Maps.newHashMap(); mRefGenomeVersion = refGenomeVersion; } @@ -64,22 +44,11 @@ public GeneRegionFilters(final RefGenomeVersion refGenomeVersion) public static void registerConfig(final ConfigBuilder configBuilder) { configBuilder.addPath(GENE_ID_FILE, false, GENE_ID_FILE_DESC); - configBuilder.addConfigItem(ENRICHED_GENE_IDS, "List of geneIds to treat as enriched"); - configBuilder.addPath(EXCLUDED_REGIONS, false, "List of excluded regions"); addSpecificChromosomesRegionsConfig(configBuilder); } public void loadConfig(final ConfigBuilder configBuilder) { - if(configBuilder.hasValue(ENRICHED_GENE_IDS)) - { - Arrays.stream(configBuilder.getValue(ENRICHED_GENE_IDS).split(ITEM_DELIM)).forEach(x -> EnrichedGeneIds.add(x)); - } - else - { - IsofoxConstants.populateEnrichedGeneIds(EnrichedGeneIds, mRefGenomeVersion); - } - IsofoxConstants.populateImmuneRegions(ImmuneGeneRegions, mRefGenomeVersion); if(configBuilder.hasValue(GENE_ID_FILE)) @@ -93,13 +62,6 @@ public void loadConfig(final ConfigBuilder configBuilder) } } - if(configBuilder.hasValue(EXCLUDED_REGIONS)) - { - String excludedRegionsFile = configBuilder.getValue(EXCLUDED_REGIONS); - ExcludedRegions.putAll(loadChrBaseRegions(excludedRegionsFile, false)); - ISF_LOGGER.info("file({}) loaded {} excluded regions", excludedRegionsFile, ExcludedRegions.size()); - } - SpecificChrRegions = SpecificRegions.from(configBuilder); mHasSpecificRegions = SpecificChrRegions.hasFilters(); } @@ -109,11 +71,11 @@ public boolean excludeChromosome(final String chromosome) return SpecificChrRegions.excludeChromosome(chromosome); } - private static final int READ_END_BUFFER = 150; + private static final int READ_END_BUFFER = 150; // NOTE: could use BAM sampling read length public boolean skipRead(final SAMRecord read, boolean checkMateAndSupp) { - if(skipRead(read.getContig(), read.getAlignmentStart(), read.getAlignmentEnd())) + if(skipRead(read.getContig(), read.getAlignmentStart(), read.getAlignmentEnd(), false)) return true; if(checkMateAndSupp) @@ -122,7 +84,7 @@ public boolean skipRead(final SAMRecord read, boolean checkMateAndSupp) if(!read.getMateUnmappedFlag()) { int mateReadStart = read.getMateAlignmentStart(); - if(skipRead(read.getMateReferenceName(), mateReadStart, mateReadStart)) + if(skipRead(read.getMateReferenceName(), mateReadStart, mateReadStart, true)) { return true; } @@ -131,7 +93,7 @@ public boolean skipRead(final SAMRecord read, boolean checkMateAndSupp) SupplementaryReadData suppData = SupplementaryReadData.extractAlignment(read); if(suppData != null) { - if(skipRead(suppData.Chromosome, suppData.Position, suppData.Position)) + if(skipRead(suppData.Chromosome, suppData.Position, suppData.Position, true)) return true; } } @@ -141,16 +103,16 @@ public boolean skipRead(final SAMRecord read, boolean checkMateAndSupp) public boolean skipRead(final String chromosome, int readStart) { - return skipRead(chromosome, readStart, readStart + READ_END_BUFFER); + return skipRead(chromosome, readStart, readStart + READ_END_BUFFER, false); } - public boolean skipRead(final String chromosome, int readStart, int readEnd) + public boolean skipRead(final String chromosome, int readStart, int readEnd, boolean isMateOrSupp) { // currently only used to filter out chimeric reads if(!HumanChromosome.contains(chromosome)) return true; - if(mHasSpecificRegions) + if(!isMateOrSupp && mHasSpecificRegions) { if(SpecificChrRegions.excludeChromosome(chromosome)) return true; @@ -159,55 +121,6 @@ public boolean skipRead(final String chromosome, int readStart, int readEnd) return true; } - List excludedRegions = ExcludedRegions.get(chromosome); - - if(excludedRegions != null) - { - if(excludedRegions.stream().anyMatch(x -> positionsOverlap(x.start(), x.end(), readStart, readEnd))) - return true; - } - return false; } - - public List findExcludedRegions(final ChrBaseRegion region) - { - List excludedRegions = ExcludedRegions.get(region.Chromosome); - - if(excludedRegions == null) - return Collections.emptyList(); - - return excludedRegions.stream().filter(x -> x.overlaps(region)).collect(Collectors.toList()); - } - - public void buildGeneRegions(final EnsemblDataCache geneTransCache) - { - // add the regions from any enriched genes to the excluded regions set - for(String enrichedGeneId : EnrichedGeneIds) - { - GeneData geneData = geneTransCache.getGeneDataById(enrichedGeneId); - - if(geneData == null) - { - ISF_LOGGER.warn("enriched gene ID({}) missing from Ensembl cache", enrichedGeneId); - continue; - } - - List excludedRegions = ExcludedRegions.get(geneData.Chromosome); - - if(excludedRegions == null) - { - excludedRegions = Lists.newArrayList(); - ExcludedRegions.put(geneData.Chromosome, excludedRegions); - } - - excludedRegions.add(new BaseRegion( - geneData.GeneStart - ENRICHED_GENE_BUFFER, geneData.GeneEnd + ENRICHED_GENE_BUFFER)); - } - - for(List excludedRegions : ExcludedRegions.values()) - { - Collections.sort(excludedRegions); - } - } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/MappedCoords.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/MappedCoords.java new file mode 100644 index 00000000000..17c15f525e2 --- /dev/null +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/MappedCoords.java @@ -0,0 +1,241 @@ +package com.hartwig.hmftools.isofox.common; + +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.String.format; + +import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; +import static com.hartwig.hmftools.common.region.BaseRegion.positionsWithin; +import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; +import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; + +import static htsjdk.samtools.CigarOperator.D; +import static htsjdk.samtools.CigarOperator.I; +import static htsjdk.samtools.CigarOperator.S; + +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.Lists; +import com.hartwig.hmftools.common.region.BaseRegion; + +import htsjdk.samtools.CigarElement; +import htsjdk.samtools.CigarOperator; + +public class MappedCoords +{ + private final int mOriginalCount; + private final List mAlignments; + + private boolean[] mInferredAlignmentAdded; + private int[] mSoftClipRegionsMatched; + + public MappedCoords(final List alignments) + { + mOriginalCount = alignments.size(); + mAlignments = alignments; + mInferredAlignmentAdded = null; + mSoftClipRegionsMatched = null; + } + + public int alignmentCount() { return mAlignments.size(); } + public int originalAlignmentCount() { return mOriginalCount; } + + public List alignments() { return mAlignments; } + + public List alignmentsWithoutInferred() + { + if(mInferredAlignmentAdded == null) + return mAlignments; + + List regions = Lists.newArrayListWithCapacity(mOriginalCount); + + int startIndex = mInferredAlignmentAdded[SE_START] ? 1 : 0; + + int endIndex = startIndex + mOriginalCount - 1; + for(int i = startIndex; i <= endIndex; ++i) + { + regions.add(mAlignments.get(i)); + } + + return regions; + } + + public BaseRegion lowestAlignment(boolean includeInferred) + { + if(includeInferred || mInferredAlignmentAdded == null || !mInferredAlignmentAdded[SE_START]) + return mAlignments.get(0); + + return mAlignments.get(1); + } + + public BaseRegion highestAlignment(boolean includeInferred) + { + if(includeInferred || mInferredAlignmentAdded == null || !mInferredAlignmentAdded[SE_END]) + return mAlignments.get(mAlignments.size() - 1); + + return mAlignments.get(mAlignments.size() - 2); + } + + public BaseRegion regionByIndex(int index) { return mAlignments.get(index); } + + public static final int INVALID_INDEX = -1; + + public int findRegionIndex(final RegionReadData region) + { + for(int i = 0; i < mAlignments.size(); ++i) + { + BaseRegion readSection = mAlignments.get(i); + + if(positionsOverlap(readSection.start(), readSection.end(), region.start(), region.end())) + return i; + } + + return INVALID_INDEX; + } + + public boolean lowerInferredAlignmentAdded() { return mInferredAlignmentAdded != null ? mInferredAlignmentAdded[SE_START] : false; } + public boolean upperInferredAlignmentAdded() { return mInferredAlignmentAdded != null ? mInferredAlignmentAdded[SE_END] : false; } + public boolean inferredAlignmentAdded(int seIndex) { return mInferredAlignmentAdded != null ? mInferredAlignmentAdded[seIndex] : false; } + + public void addInferredRegion(boolean isLower, int posStart, int posEnd) + { + if(mInferredAlignmentAdded == null) + mInferredAlignmentAdded = new boolean[] {false, false}; + + if(isLower) + { + if(!mInferredAlignmentAdded[SE_START]) + { + mInferredAlignmentAdded[SE_START] = true; + mAlignments.add(0, new BaseRegion(posStart, posEnd)); + } + else + { + // lengthen the new region if required + BaseRegion existing = mAlignments.get(0); + existing.setStart(min(existing.start(), posStart)); + } + } + else + { + if(!mInferredAlignmentAdded[SE_END]) + { + mInferredAlignmentAdded[SE_END] = true; + mAlignments.add(new BaseRegion(posStart, posEnd)); + } + else + { + BaseRegion existing = mAlignments.get(mAlignments.size() - 1); + existing.setEnd(max(existing.end(), posEnd)); + } + } + } + + public boolean alignmentsOverlap(int posStart, int posEnd) + { + return mAlignments.stream().anyMatch(x -> positionsOverlap(posStart, posEnd, x.start(), x.end())); + } + + public boolean alignmentsWithin(int posStart, int posEnd) + { + return mAlignments.stream().anyMatch(x -> positionsWithin(x.start(), x.end(), posStart, posEnd)); + } + + public int getCoordsBoundary(int se) + { + return se == SE_START ? mAlignments.get(0).start() : mAlignments.get(mAlignments.size() - 1).end(); + } + + public void addSoftClipRegionMatched(boolean onStart, int count) + { + if(mSoftClipRegionsMatched == null) + { + mSoftClipRegionsMatched = new int[] {0, 0}; + } + + mSoftClipRegionsMatched[onStart ? SE_START : SE_END] += count; + } + + public boolean isSoftClipRegionMatched(int seIndex) + { + return mSoftClipRegionsMatched != null ? mSoftClipRegionsMatched[seIndex] > 0 : false; + } + + public int softClipRegionsMatched(int seIndex) + { + return mSoftClipRegionsMatched != null ? mSoftClipRegionsMatched[seIndex] : 0; + } + + public String toString() + { + if(mAlignments.size() == 1) + return mAlignments.get(0).toString(); + + String alignmentsStr = mAlignments.stream().map(x -> x.toString()).collect(Collectors.joining(";")); + + if(mSoftClipRegionsMatched == null && mInferredAlignmentAdded == null) + return format("%s", alignmentsStr); + + return format("%s inferred(lower=%s upper=%s) regionsMatched(lower=%d upper=%d)", + alignmentsStr, mInferredAlignmentAdded[SE_START], mInferredAlignmentAdded[SE_END], + mSoftClipRegionsMatched[SE_START], mSoftClipRegionsMatched[SE_END]); + } + + public static MappedCoords build(final List cigarElements, int posStart) + { + int splitCount = (int)cigarElements.stream().filter(x -> x.getOperator() == CigarOperator.N).count(); + + List alignments = Lists.newArrayListWithCapacity(1 + splitCount); + + BaseRegion currentRegion = null; + + int posOffset = 0; + boolean continueRegion = false; + + for(CigarElement element : cigarElements) + { + if(element.getOperator() == S) + continue; + + if(element.getOperator() == I) + { + continueRegion = true; + continue; + } + + if(element.getOperator() == D) + { + // don't break alignments for deletes + posOffset += element.getLength(); + continueRegion = true; + } + else if(element.getOperator() == CigarOperator.N) + { + // break on splits + posOffset += element.getLength(); + continueRegion = false; + } + else if(element.getOperator() == CigarOperator.M) + { + int readStartPos = posStart + posOffset; + int readEndPos = readStartPos + element.getLength() - 1; + + if(continueRegion && currentRegion != null) + { + currentRegion.setEnd(readEndPos); + } + else + { + currentRegion = new BaseRegion(readStartPos, readEndPos); + alignments.add(currentRegion); + } + + posOffset += element.getLength(); + continueRegion = false; + } + } + + return new MappedCoords(alignments); + } +} diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/PerformanceTracking.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/PerformanceTracking.java index 0c05d05f405..0ecb5c6e481 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/PerformanceTracking.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/PerformanceTracking.java @@ -73,7 +73,7 @@ public void logPerformanceStats(final List perfCounters) if(fitTimes.size() >= 10 && fitGenes.size() == fitTimes.size()) { - for (int i = fitTimes.size() - 1; i >= fitTimes.size() - 10; --i) + for(int i = fitTimes.size() - 1; i >= fitTimes.size() - 10; --i) { ISF_LOGGER.info(String.format("fit times: geneSet(%s) time(%.3f)", fitGenes.get(i), fitTimes.get(i))); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/Read.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/Read.java index 69ca44f4440..d2d84f4b722 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/Read.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/Read.java @@ -4,69 +4,55 @@ import static java.lang.Math.min; import static com.hartwig.hmftools.common.bam.CigarUtils.cigarElementsToStr; -import static com.hartwig.hmftools.common.bam.CigarUtils.leftSoftClipped; -import static com.hartwig.hmftools.common.bam.CigarUtils.rightSoftClipped; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.CONSENSUS_READ_ATTRIBUTE; import static com.hartwig.hmftools.common.bam.SamRecordUtils.SUPPLEMENTARY_ATTRIBUTE; -import static com.hartwig.hmftools.common.bam.SamRecordUtils.generateMappedCoords; -import static com.hartwig.hmftools.common.utils.file.FileDelimiters.ITEM_DELIM; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.XA_ATTRIBUTE; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.firstInPair; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.inferredInsertSize; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.mateNegativeStrand; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_PAIR; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; -import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; -import static com.hartwig.hmftools.common.region.BaseRegion.positionsWithin; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_REV; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; -import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; -import static com.hartwig.hmftools.isofox.IsofoxConstants.MULTI_MAP_QUALITY_THRESHOLD; -import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_BOUNDARY; -import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_INTRON; import static com.hartwig.hmftools.isofox.common.RegionMatchType.INTRON; -import static com.hartwig.hmftools.isofox.common.RegionMatchType.WITHIN_EXON; import static com.hartwig.hmftools.isofox.common.RegionMatchType.exonBoundary; -import static com.hartwig.hmftools.isofox.common.RegionMatchType.validExonMatch; -import static com.hartwig.hmftools.isofox.common.TransMatchType.ALT; -import static com.hartwig.hmftools.isofox.common.TransMatchType.EXONIC; -import static com.hartwig.hmftools.isofox.common.TransMatchType.SPLICE_JUNCTION; import static com.hartwig.hmftools.isofox.common.TransMatchType.UNKNOWN; import static htsjdk.samtools.CigarOperator.N; import static htsjdk.samtools.CigarOperator.S; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import com.hartwig.hmftools.common.bam.SupplementaryReadData; import com.hartwig.hmftools.common.gene.ExonData; import com.hartwig.hmftools.common.gene.TranscriptData; -import com.hartwig.hmftools.common.bam.ClippedSide; +import com.hartwig.hmftools.common.region.BaseRegion; +import com.hartwig.hmftools.common.region.ChrBaseRegion; import com.hartwig.hmftools.common.genome.region.Orientation; -import org.jetbrains.annotations.NotNull; - -import htsjdk.samtools.Cigar; import htsjdk.samtools.CigarElement; import htsjdk.samtools.SAMFlag; import htsjdk.samtools.SAMRecord; +import htsjdk.samtools.TextCigarCodec; public class Read { - public final String Id; - public final String Chromosome; - public final int PosStart; - public final int PosEnd; + private SAMRecord mRecord; + + // make private + private int mPosStart; + private int mPosEnd; - private String mReadBases; + private String mReadBases; // cached if trimmed private final String mOriginalCigarStr; private String mCigarStr; private final List mCigarElements; - private int mFlags; - private String mMateChromosome; - private int mMatePosStart; public int mUnclippedStart; public int mUnclippedEnd; @@ -74,60 +60,61 @@ public class Read private final int[] mGeneCollections; private final boolean[] mIsGenicRegion; - private final List mMappedCoords; - private boolean mLowerInferredAdded; - private boolean mUpperInferredAdded; - private final int[] mSoftClipRegionsMatched; - private int mFragmentInsertSize; - private String mSupplementaryAlignment; + + private final MappedCoords mMappedCoords; + + private SupplementaryReadData mSupplementaryData; private boolean mHasInterGeneSplit; - private short mMapQuality; + private List mAltLoci; // alternate genomic mapping loci from XA tag; null if uniquely mapped + private boolean mConsensusRead; private int[] mJunctionPositions; // chimeric junctions private final Map mMappedRegions; // regions related to this read and their match type private final Map mTranscriptClassification; private final Map> mTransExonRefs; - private final Map> mUpperTransExonRefs; // TE refs for upper coords if a spanning read public static final int NO_GENE_ID = -1; - public static Read from(final SAMRecord record) + public Read(final SAMRecord record) { - String readId = record.isSecondaryAlignment() ? String.format("%s_%s", - record.getReadName(), record.getAttribute("HI")) : record.getReadName(); + mRecord = record; + mPosStart = record.getAlignmentStart(); + mPosEnd = record.getAlignmentEnd(); - Read read = new Read( - readId, record.getReferenceName(), record.getStart(), record.getEnd(), - record.getReadString(), record.getCigar(), record.getInferredInsertSize(), record.getFlags(), - record.getMateReferenceName(), record.getMateAlignmentStart()); + mCigarElements = Lists.newArrayList(record.getCigar().getCigarElements()); + mOriginalCigarStr = record.getCigarString(); + mCigarStr = null; // set if trimmed - read.setSuppAlignment(record.getStringAttribute(SUPPLEMENTARY_ATTRIBUTE)); - read.setMapQuality((short)record.getMappingQuality()); - return read; - } + mSupplementaryData = SupplementaryReadData.extractAlignment(mRecord); - public Read( - final String id, final String chromosome, int posStart, int posEnd, final String readBases, @NotNull final Cigar cigar, - int insertSize, int flags, final String mateChromosome, int matePosStart) - { - Id = id; - Chromosome = chromosome; - PosStart = posStart; - PosEnd = posEnd; - mReadBases = readBases; + mHasSplit = mCigarElements.stream().anyMatch(x -> x.getOperator() == N); - mCigarElements = Lists.newArrayList(cigar.getCigarElements()); - mOriginalCigarStr = cigarElementsToStr(mCigarElements); - mCigarStr = null; + setBoundaries(); - mHasSplit = mCigarElements.stream().anyMatch(x -> x.getOperator() == N); + mGeneCollections = new int[] { NO_GENE_ID, NO_GENE_ID }; + mIsGenicRegion = new boolean[] { false, false }; + + mMappedCoords = MappedCoords.build(mCigarElements, mPosStart); + + mMappedRegions = Maps.newHashMap(); + mTransExonRefs = Maps.newHashMap(); + mTranscriptClassification = Maps.newHashMap(); + mHasInterGeneSplit = false; + mJunctionPositions = null; + + mConsensusRead = record.hasAttribute(CONSENSUS_READ_ATTRIBUTE); + mAltLoci = parseAltLoci(record.getStringAttribute(XA_ATTRIBUTE)); + } + + private void setBoundaries() + { + mUnclippedStart = mPosStart; - mUnclippedStart = PosStart; if(!mCigarElements.isEmpty()&& mCigarElements.get(0).getOperator() == S) mUnclippedStart -= mCigarElements.get(0).getLength(); - mUnclippedEnd = PosEnd; + mUnclippedEnd = mPosEnd; if(mCigarElements.size() >= 2) { @@ -135,120 +122,86 @@ public Read( if(mCigarElements.get(lastIndex).getOperator() == S) mUnclippedEnd += mCigarElements.get(lastIndex).getLength(); } - - mFlags = flags; - mMateChromosome = mateChromosome; - mMatePosStart = matePosStart; - - mGeneCollections = new int[] { NO_GENE_ID, NO_GENE_ID }; - mIsGenicRegion = new boolean[] { false, false }; - - List mappedCoords = generateMappedCoords(mCigarElements, PosStart); - mMappedCoords = Lists.newArrayListWithCapacity(mappedCoords.size()); - mMappedCoords.addAll(mappedCoords); - - mMappedRegions = Maps.newHashMap(); - mTransExonRefs = Maps.newHashMap(); - mUpperTransExonRefs = Maps.newHashMap(); - mTranscriptClassification = Maps.newHashMap(); - mLowerInferredAdded = false; - mUpperInferredAdded = false; - mSoftClipRegionsMatched = new int[] {0, 0}; - mFragmentInsertSize = insertSize; - mSupplementaryAlignment = null; - mHasInterGeneSplit = false; - mMapQuality = 0; - mJunctionPositions = null; } - public int range() { return PosEnd - PosStart; } + public String id() { return mRecord.getReadName(); } + public String chromosome() { return mRecord.getReferenceName(); } + public int alignmentStart() { return mPosStart; } + public int alignmentEnd() { return mPosEnd; } + public int flags() { return mRecord.getFlags(); } + + public String mateChromosome() { return mRecord.getMateReferenceName(); } + public int mateAlignmentStart() { return mRecord.getMateAlignmentStart(); } + public int fragmentInsertSize() { return inferredInsertSize(mRecord); } + public int mapQuality() { return mRecord.getMappingQuality(); } public byte orientByte() { return !isReadReversed() ? ORIENT_FWD : ORIENT_REV; } public Orientation orientation() { return !isReadReversed() ? Orientation.FORWARD : Orientation.REVERSE; } + public List cigarElements() { return mCigarElements; } - public String originalCigarStr() { return mCigarStr; } public String cigarStr() { return mCigarStr != null ? mCigarStr : mOriginalCigarStr; } - public String readBases() { return mReadBases; } - public int unclippedStart() { return mUnclippedStart; } - public int unclippedEnd() { return mUnclippedEnd; } - public boolean isLeftClipped() { return mUnclippedStart != PosStart; } - public boolean isRightClipped() { return mUnclippedEnd != PosEnd; } - public int leftClipLength() { return max(PosStart - mUnclippedStart, 0); } - public int rightClipLength() { return max(mUnclippedEnd - PosEnd, 0); } + public String readBases() { return mReadBases != null ? mReadBases : mRecord.getReadString(); } public boolean containsSplit() { return mHasSplit; } - public int flags() { return mFlags; } - public boolean isReadPaired() { return (mFlags & SAMFlag.READ_PAIRED.intValue()) != 0; } - public boolean isReadReversed() { return (mFlags & SAMFlag.READ_REVERSE_STRAND.intValue()) != 0; } - public boolean isFirstOfPair() { return (mFlags & SAMFlag.FIRST_OF_PAIR.intValue()) != 0; } - public boolean isDuplicate() { return (mFlags & SAMFlag.DUPLICATE_READ.intValue()) != 0; } - public boolean isTranslocation() { return !Chromosome.equals(mMateChromosome); } - public boolean isMateNegStrand() { return (mFlags & SAMFlag.MATE_REVERSE_STRAND.intValue()) != 0; } - public boolean isMateUnmapped() { return (mFlags & SAMFlag.MATE_UNMAPPED.intValue()) != 0; } + public boolean isReadPaired() { return mRecord.getReadPairedFlag(); } + public boolean isReadReversed() { return mRecord.getReadNegativeStrandFlag(); } + public boolean isFirstOfPair() { return firstInPair(mRecord); } + public boolean isDuplicate() { return mRecord.getDuplicateReadFlag(); } + public boolean isTranslocation() { return !chromosome().equals(mateChromosome()); } + public boolean isMateNegStrand() { return mateNegativeStrand(mRecord); } + public boolean isMateUnmapped() { return mRecord.getMateUnmappedFlag(); } public boolean isInversion() { return isReadReversed() == isMateNegStrand(); } - public boolean isProperPair() { return (mFlags & SAMFlag.PROPER_PAIR.intValue()) != 0; } - public boolean isSupplementaryAlignment() { return (mFlags & SAMFlag.SUPPLEMENTARY_ALIGNMENT.intValue()) != 0; } - public boolean isSecondaryAlignment() { return (mFlags & SAMFlag.SECONDARY_ALIGNMENT.intValue()) != 0; } + public boolean isSupplementaryAlignment() { return mRecord.getSupplementaryAlignmentFlag(); } - public void setFragmentInsertSize(int size) { mFragmentInsertSize = size; } - public void setSuppAlignment(final String suppAlign) { mSupplementaryAlignment = suppAlign; } - public String getSuppAlignment() { return mSupplementaryAlignment; } + public SupplementaryReadData supplementaryData() { return mSupplementaryData; } + public boolean hasSuppAlignment() { return mSupplementaryData != null; } - public String suppAlignmentAsStr() - { - return mSupplementaryAlignment != null ? mSupplementaryAlignment.replaceAll(",", ITEM_DELIM) : "NONE"; - } + public MappedCoords mappedCoords() { return mMappedCoords; } - public boolean hasSuppAlignment() { return mSupplementaryAlignment != null; } + // soft-clip methods which use the raw read's soft-clips, and not any exon-boundary inferred soft-clips + public int unclippedStart() { return mUnclippedStart; } + public int unclippedEnd() { return mUnclippedEnd; } + public boolean isLeftClipped() { return mUnclippedStart != mPosStart; } + public boolean isRightClipped() { return mUnclippedEnd != mPosEnd; } + public boolean containsSoftClipping() { return isLeftClipped() || isRightClipped(); } + public int leftClipLength() { return max(mPosStart - mUnclippedStart, 0); } + public int rightClipLength() { return max(mUnclippedEnd - mPosEnd, 0); } + public int longestSoftClip() { return max(leftClipLength(), rightClipLength()); } - public static ClippedSide clippedSide(final Read read) + public Boolean longestSoftClipIsLeft() { - int leftScLength = read.leftClipLength(); - int rightScLength = read.rightClipLength(); + int left = leftClipLength(); + int right = rightClipLength(); - if(leftScLength > 0 && rightScLength > 0) - { - return leftScLength >= rightScLength ? - new ClippedSide(SE_START, leftScLength, true) : new ClippedSide(SE_END, rightScLength, true); - } - else if(leftScLength > 0) - { - return new ClippedSide(SE_START, leftScLength, true); - } - else - { - return new ClippedSide(SE_END, rightScLength, rightScLength > 0); - } - } + if(left == 0 && right == 0) + return null; - public int[] getSoftClipRegionsMatched() { return mSoftClipRegionsMatched; } + return left >= right ? true : false; + } - public boolean isSoftClipped(int se) + public boolean isSoftClippedNoRegionMatch(int se) { - if(mSoftClipRegionsMatched[se] > 0) + if(mMappedCoords.isSoftClipRegionMatched(se)) return false; return se == SE_START ? isLeftClipped() : isRightClipped(); } - public boolean containsSoftClipping() { return isLeftClipped() || isRightClipped(); } - - public void setMapQuality(short mapQuality) { mMapQuality = mapQuality; } - public short mapQuality() { return mMapQuality; } + public List altLoci() { return mAltLoci; } + public int numLoci() { return mAltLoci != null ? 1 + mAltLoci.size() : 1; } - public int baseLength() { return mReadBases.length(); } + public boolean isMultiMapped() { return numLoci() > 1; } - public boolean isMultiMapped() { return mMapQuality <= MULTI_MAP_QUALITY_THRESHOLD; } + public boolean isConsensusRead() { return mConsensusRead; } - public int fragmentInsertSize() { return mFragmentInsertSize; } + public int baseLength() { return readBases().length(); } - public String mateChromosome() { return mMateChromosome; } - public int mateStartPosition() { return mMatePosStart; } + public SAMRecord bamRecord() { return mRecord; } public int[] getGeneCollectons() { return mGeneCollections; } - public final boolean[] getIsGenicRegion() { return mIsGenicRegion; } + public boolean[] getIsGenicRegion() { return mIsGenicRegion; } public void setGeneCollection(int seIndex, int gc, boolean isGeneic) { @@ -267,7 +220,7 @@ public boolean fullyNonGenic() public boolean matches(final Read other) { - return Id.equals(other.Id) && cigarStr().equals(other.cigarStr().toString()) && PosStart == other.PosStart && PosEnd == other.PosEnd; + return id().equals(other.id()) && flags() == other.flags(); } public boolean spansGeneCollections() @@ -275,538 +228,125 @@ public boolean spansGeneCollections() return mGeneCollections[SE_START] != mGeneCollections[SE_END]; } - public void setFlag(SAMFlag flag, boolean toggle) - { - if(toggle) - mFlags |= flag.intValue(); - else - mFlags &= ~flag.intValue(); - } - - public void setStrand(boolean readReversed, boolean mateReadReversed) - { - setFlag(SAMFlag.READ_REVERSE_STRAND, readReversed); - setFlag(SAMFlag.MATE_REVERSE_STRAND, mateReadReversed); - } - - public final Map> getReadTransExonRefs() { return mTransExonRefs; } + public Map> getReadTransExonRefs() { return mTransExonRefs; } public boolean isChimeric() { if(isTranslocation() || isInversion()) return true; - if(!isProperPair() || isSupplementaryAlignment() || mSupplementaryAlignment != null) + if(isSupplementaryAlignment() || mSupplementaryData != null) return true; return false; } - public List getMappedRegionCoords() { return mMappedCoords; } - - public List getMappedRegionCoords(boolean includeInferred) - { - if(includeInferred || (!mLowerInferredAdded && !mUpperInferredAdded)) - return mMappedCoords; + public List getMappedRegionCoords() { return mMappedCoords.alignments(); } + public List getMappedRegionCoordsWithoutInferred() { return mMappedCoords.alignmentsWithoutInferred(); } - final List regions = Lists.newArrayList(mMappedCoords); + public boolean overlapsMappedCoords(int posStart, int posEnd) { return mMappedCoords.alignmentsOverlap(posStart, posEnd); } + public int getCoordsBoundary(int se) { return mMappedCoords.getCoordsBoundary(se); } - if(mLowerInferredAdded) - regions.remove(0); - - if(mUpperInferredAdded) - regions.remove(regions.size() - 1); - - return regions; - } - - public boolean overlapsMappedReads(int posStart, int posEnd) + // an alternate mapping locus from the XA tag: its genomic span and whether that alignment is spliced + public static class AltAlignment { - return mMappedCoords.stream().anyMatch(x -> positionsOverlap(posStart, posEnd, x[SE_START], x[SE_END])); - } + public final ChrBaseRegion Region; + public final boolean Spliced; - public int getCoordsBoundary(int se) - { - return se == SE_START ? mMappedCoords.get(0)[SE_START] : mMappedCoords.get(mMappedCoords.size() - 1)[SE_END]; + public AltAlignment(final ChrBaseRegion region, final boolean spliced) + { + Region = region; + Spliced = spliced; + } } - public void processOverlappingRegions(final List regions) + private static List parseAltLoci(final String xaTag) { - // process all regions for each transcript as a group to look for inconsistencies with the transcript definition - Set transcripts = Sets.newHashSet(); - boolean hasSoftClipping = isLeftClipped() || isRightClipped(); + if(xaTag == null || xaTag.isEmpty()) + return null; - for(RegionReadData region : regions) - { - for(final TransExonRef ref : region.getTransExonRefs()) - { - transcripts.add(ref.TransId); - } - - RegionMatchType matchType = setRegionMatchType(region); - mMappedRegions.put(region, matchType); - - boolean checkMissedJunctions = matchType == EXON_INTRON || (hasSoftClipping && exonBoundary(matchType)); + List altLoci = Lists.newArrayList(); - if(checkMissedJunctions) - checkMissedJunctions(region); - } - - for(int transId : transcripts) + // XA entry format: chr,(+/-)pos,CIGAR,NM + for(String entry : xaTag.split(";")) { - // determine for each transcript whether the mapped regions support a spliced transcript, unspliced or alternate splicing - TransMatchType transMatchType = UNKNOWN; - - List transRegions = regions.stream() - .filter(x -> x.getTransExonRefs().stream().anyMatch(y -> y.TransId == transId)) - .collect(Collectors.toList()); + if(entry.isEmpty()) + continue; - // if any reads cross and exon-intron boundary, then mark the transcript as unspliced + String[] fields = entry.split(","); - if(transRegions.size() == 1 && mappedRegionCount() == 1) - { - // simple case of a single exon and read section - RegionReadData region = transRegions.get(0); - RegionMatchType matchType = mMappedRegions.get(region); + if(fields.length < 2) + continue; - if (matchType == RegionMatchType.NONE) - { - // should never happen since implies this read didn't hit the region at all - transMatchType = ALT; - } - else if (matchType == RegionMatchType.EXON_INTRON) - { - transMatchType = TransMatchType.UNSPLICED; - } - } - else if(mappedRegionCount() > transRegions.size()) - { - transMatchType = ALT; - } - else + try { - int minExonRank = 0; - int maxExonRank = 0; + int position = Math.abs(Integer.parseInt(fields[1])); - Collections.sort(transRegions); + // alt CIGAR gives the ref span and splice status; fall back to a single base when absent + int refLength = 1; + boolean spliced = false; + String cigarStr = fields.length > 2 ? fields[2] : null; - for (int regionIndex = 0; regionIndex < transRegions.size(); ++regionIndex) + if(cigarStr != null && !cigarStr.isEmpty()) { - RegionReadData region = transRegions.get(regionIndex); - - int exonRank = region.getExonRank(transId); - maxExonRank = max(maxExonRank, exonRank); - minExonRank = minExonRank == 0 ? exonRank : min(exonRank, minExonRank); - - int mappingIndex = getRegionMappingIndex(region); - int adjustedMappingIndex = mappingIndex; - - if(mLowerInferredAdded) - --adjustedMappingIndex; - - if (adjustedMappingIndex < 0 || adjustedMappingIndex != regionIndex) - { - transMatchType = ALT; - break; - } - - RegionMatchType matchType = mMappedRegions.get(region); - - if (matchType == RegionMatchType.EXON_INTRON) - { - transMatchType = TransMatchType.UNSPLICED; - break; - } - else - { - final int[] readSection = mMappedCoords.get(mappingIndex); - int readStartPos = readSection[SE_START]; - int readEndPos = readSection[SE_END]; - - boolean missStart = readStartPos > region.start(); - boolean missEnd = readEndPos < region.end(); - if(regionIndex == 0) - { - if(missEnd) - { - transMatchType = ALT; - break; - } - } - else if(regionIndex == transRegions.size() - 1) - { - if(missStart) - { - transMatchType = ALT; - break; - } - } - else if(missStart || missEnd) - { - transMatchType = ALT; - break; - } - } + refLength = max(TextCigarCodec.decode(cigarStr).getReferenceLength(), 1); + spliced = cigarStr.indexOf('N') >= 0; } - if (transMatchType == UNKNOWN) - { - int expectedRegions = maxExonRank - minExonRank + 1; - if (transRegions.size() < expectedRegions) - transMatchType = ALT; - } + ChrBaseRegion region = new ChrBaseRegion(fields[0], position, position + refLength - 1); + altLoci.add(new AltAlignment(region, spliced)); } - - if(transMatchType == UNKNOWN) + catch(NumberFormatException e) { - if(transRegions.size() > 1) - { - transMatchType = SPLICE_JUNCTION; - } - else - { - transMatchType = EXONIC; - } - } - - // any read with soft-clipping which cannot be mapped to the next exon, other than for short likely adapter sequence reads, - // is classified as alt - if(validTranscriptType(transMatchType) && containsSoftClipping() && !likelyAdaperSoftClipping()) - { - if(isLeftClipped() && mSoftClipRegionsMatched[SE_START] == 0) - transMatchType = ALT; - else if(isRightClipped() && mSoftClipRegionsMatched[SE_END] == 0) - transMatchType = ALT; } - - mTranscriptClassification.put(transId, transMatchType); - } - } - - public boolean likelyAdaperSoftClipping() - { - return mFragmentInsertSize < baseLength(); - } - - public static final List getUniqueValidRegion(final Read read1, final Read read2) - { - final List regions = read1.getMappedRegions().entrySet().stream() - .filter(x -> validExonMatch(x.getValue())) - .map(x -> x.getKey()).collect(Collectors.toList()); - - final List regions2 = read2.getMappedRegions().entrySet().stream() - .filter(x -> validExonMatch(x.getValue())) - .map(x -> x.getKey()).collect(Collectors.toList()); - - for(RegionReadData region : regions2) - { - if (!regions.contains(region)) - regions.add(region); } - return regions; + return altLoci.isEmpty() ? null : altLoci; } - public static boolean validTranscriptType(TransMatchType transType) - { - return transType == EXONIC || transType == SPLICE_JUNCTION; - } - - public int getRegionMappingIndex(final RegionReadData region) - { - for(int i = 0; i < mMappedCoords.size(); ++i) - { - final int[] readSection = mMappedCoords.get(i); - - if(positionsOverlap(readSection[SE_START], readSection[SE_END], region.start(), region.end())) - return i; - } - - return -1; - } - - private RegionMatchType setRegionMatchType(final RegionReadData region) + public boolean likelyAdaperSoftClipping() { - int mappingIndex = getRegionMappingIndex(region); - if (mappingIndex < 0) - return RegionMatchType.NONE; - - RegionMatchType matchType = getRegionMatchType(region, mappingIndex); - mMappedRegions.put(region, matchType); - return matchType; + return fragmentInsertSize() < baseLength(); } public RegionMatchType getRegionMatchType(final RegionReadData region) { - int mappingIndex = getRegionMappingIndex(region); - if (mappingIndex < 0) - return RegionMatchType.NONE; - - return getRegionMatchType(region, mappingIndex); - } - - private RegionMatchType getRegionMatchType(final RegionReadData region, int mappingIndex) - { - if(mappingIndex < 0 || mappingIndex >= mMappedCoords.size()) - return RegionMatchType.NONE; - - final int[] readSection = mMappedCoords.get(mappingIndex); - int readStartPos = readSection[SE_START]; - int readEndPos = readSection[SE_END]; - - if (readEndPos < region.start() || readStartPos > region.end()) + int mappingIndex = mMappedCoords.findRegionIndex(region); + if(mappingIndex == MappedCoords.INVALID_INDEX) return RegionMatchType.NONE; - if (readStartPos < region.start() || readEndPos > region.end()) - return RegionMatchType.EXON_INTRON; - - if (readStartPos > region.start() && readEndPos < region.end()) - return WITHIN_EXON; - - return EXON_BOUNDARY; - } - - public static void markRegionBases(final List readCoords, final RegionReadData region) - { - int[] regionBaseDepth = region.refBasesMatched(); - - if(regionBaseDepth == null) - return; - - for(final int[] readSection : readCoords) - { - int readStartPos = readSection[SE_START]; - int readEndPos = readSection[SE_END]; - - if (readStartPos > region.end() || readEndPos < region.start()) - continue; - - // process this overlap - int regionBaseIndex = readStartPos > region.start() ? readStartPos - region.start() : 0; - int overlap = min(readEndPos, region.end()) - max(readStartPos, region.start()) + 1; - - if(regionBaseIndex + overlap > regionBaseDepth.length) - { - ISF_LOGGER.error("region({}) read coords({} -> {}) regionBaseIndex({}) overlap({}) regionLength({})", - region, readStartPos, readEndPos, regionBaseIndex, overlap, regionBaseDepth.length); - return; - } - - for(int j = regionBaseIndex; j < regionBaseIndex + overlap; ++j) - { - ++regionBaseDepth[j]; - } - } + return ReadTranscriptUtils.getRegionMatchType(mMappedCoords, region, mappingIndex); } public boolean hasInterGeneSplit() { return mHasInterGeneSplit; } public void setHasInterGeneSplit() { mHasInterGeneSplit = true; } - private static final int MIN_SC_BASE_MATCH = 2; - public static final int MAX_SC_BASE_MATCH = 10; - - private void checkMissedJunctions(final RegionReadData region) - { - if(mSupplementaryAlignment != null) - return; - - // check for reads either soft-clipped or seemingly unspliced, where the extra bases can match with the next exon - - // check start of read - int[] readSection = mLowerInferredAdded ? mMappedCoords.get(1) : mMappedCoords.get(0); - int readStartPos = readSection[SE_START]; - int readEndPos = readSection[SE_END]; - - // don't understand why any deleted bases were taken into account, no longer appears correct or relevant - // int deletedLength = Cigar.getCigarElements().stream().filter(x -> x.getOperator() == D).mapToInt(x -> x.getLength()).sum(); - - int extraBaseLength = 0; - int scLength = 0; - - boolean hasRegionOverhang = region.start() > readStartPos && readEndPos > region.start() - && region.start() - readStartPos <= MAX_SC_BASE_MATCH; - - if(hasRegionOverhang) - { - extraBaseLength = region.start() - readStartPos; - } - - if(isLeftClipped() && readStartPos <= region.start()) - { - scLength = leftClipLength(); - extraBaseLength += scLength; - } - - // less any deleted bases - // extraBaseLength = max(extraBaseLength - deletedLength, 0); - - // allow a single base match if only 1 region matches - if(extraBaseLength >= 1 && extraBaseLength <= MAX_SC_BASE_MATCH && scLength <= MAX_SC_BASE_MATCH) - { - // first check for a match with the next exon on the lower side - final String extraBases = mReadBases.substring(0, extraBaseLength); - - final List matchedRegions = region.getPreRegions().stream() - .filter(x -> matchesOtherRegionBases(extraBases, x, false)).collect(Collectors.toList()); + public int mappedRegionCount() { return mMappedCoords.originalAlignmentCount(); } // does not include inferred regions - if(!matchedRegions.isEmpty()) - { - mSoftClipRegionsMatched[SE_START] = matchedRegions.size(); - mMappedRegions.put(region, EXON_BOUNDARY); - - if (matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength < MIN_SC_BASE_MATCH)) - { - // truncate the read positions back to match the exon boundary - if (!mLowerInferredAdded && hasRegionOverhang) - readSection[SE_START] += region.start() - readStartPos; - } - - // if only one region is matched or the min bases matched is satisfied, then create a mapping to the next region, - // otherwise treat the splice support as ambiguous (it not mapped to the next region) - if (matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength >= MIN_SC_BASE_MATCH)) - { - for (RegionReadData preRegion : matchedRegions) - { - // add matched coordinates for this exon and add it as a region - mMappedRegions.put(preRegion, EXON_BOUNDARY); - addInferredMappingRegion(true, preRegion.end() - extraBaseLength + 1, preRegion.end()); - } - } - } - } - - // check end of read - readSection = mUpperInferredAdded ? mMappedCoords.get(mMappedCoords.size() - 2) : mMappedCoords.get(mMappedCoords.size() - 1); - readStartPos = readSection[SE_START]; - readEndPos = readSection[SE_END]; - - extraBaseLength = 0; - scLength = 0; - - hasRegionOverhang = readEndPos > region.end() && readStartPos < region.end() && readEndPos - region.end() <= MAX_SC_BASE_MATCH; - - if(hasRegionOverhang) - { - extraBaseLength = readEndPos - region.end(); - } - - if(isRightClipped() && readEndPos >= region.end()) - { - scLength = rightClipLength(); - extraBaseLength += scLength; - } - - // extraBaseLength = max(extraBaseLength - deletedLength, 0); - - if(extraBaseLength >= 1 && extraBaseLength <= MAX_SC_BASE_MATCH && scLength <= MAX_SC_BASE_MATCH) - { - // now check for a match to the next exon up - int readLength = baseLength(); - final String extraBases = mReadBases.substring(readLength - extraBaseLength, readLength); - - final List matchedRegions = region.getPostRegions().stream() - .filter(x -> matchesOtherRegionBases(extraBases, x, true)).collect(Collectors.toList()); - - if(!matchedRegions.isEmpty()) - { - mSoftClipRegionsMatched[SE_END] = matchedRegions.size(); - - mMappedRegions.put(region, EXON_BOUNDARY); - - if (matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength < MIN_SC_BASE_MATCH)) - { - if (!mUpperInferredAdded && hasRegionOverhang) - readSection[SE_END] -= readEndPos - region.end(); - } - - if (matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength >= MIN_SC_BASE_MATCH)) - { - for (RegionReadData postRegion : matchedRegions) - { - mMappedRegions.put(postRegion, EXON_BOUNDARY); - addInferredMappingRegion(false, postRegion.start(), postRegion.start() + extraBaseLength - 1); - } - } - } - } - } - - private static boolean matchesOtherRegionBases(final String extraBases, final RegionReadData otherRegion, boolean matchToStart) - { - int otherRegionLength = otherRegion.length(); - - if(extraBases.length() > otherRegionLength) - return false; - - final String otherRegionBases = matchToStart ? otherRegion.refBases().substring(0, extraBases.length()) - : otherRegion.refBases().substring(otherRegionLength - extraBases.length(), otherRegionLength); - - return (otherRegionBases.equals(extraBases)); - } - - private int mappedRegionCount() - { - // discount any inferred regions - return mMappedCoords.size() - (mLowerInferredAdded ? 1 : 0) - (mUpperInferredAdded ? 1 : 0); - } - - public boolean inferredCoordAdded(boolean isLower) { return isLower ? mLowerInferredAdded : mUpperInferredAdded; } - - private void addInferredMappingRegion(boolean isLower, int posStart, int posEnd) - { - if(isLower) - { - if (!mLowerInferredAdded) - { - mLowerInferredAdded = true; - mMappedCoords.add(0, new int[] { posStart, posEnd }); - } - else - { - // lengthen the new region if required - int[] newSection = mMappedCoords.get(0); - newSection[SE_START] = min(newSection[SE_START], posStart); - } - } - else - { - if(!mUpperInferredAdded) - { - mUpperInferredAdded = true; - mMappedCoords.add(new int[] {posStart, posEnd}); - } - else - { - int[] newSection = mMappedCoords.get(mMappedCoords.size() - 1); - newSection[SE_END] = max(newSection[SE_END], posEnd); - } - } - } - - public final Map getMappedRegions() { return mMappedRegions; } + public Map getMappedRegions() { return mMappedRegions; } public static List findOverlappingRegions(final List regions, final Read read) { return regions.stream() - .filter(x -> read.overlapsMappedReads(x.start(), x.end())) + .filter(x -> read.overlapsMappedCoords(x.start(), x.end())) .collect(Collectors.toList()); } public void addIntronicTranscriptRefs(final List transDataList) { - final List transRefList = Lists.newArrayList(); + List transRefList = Lists.newArrayList(); - for(final TranscriptData transData : transDataList) + for(TranscriptData transData : transDataList) { - if(!mMappedCoords.stream().anyMatch(x -> positionsWithin(x[SE_START], x[SE_END], transData.TransStart, transData.TransEnd))) + if(!mMappedCoords.alignmentsWithin(transData.TransStart, transData.TransEnd)) continue; for(int i = 0; i < transData.exons().size() - 1; ++i) { - final ExonData exon = transData.exons().get(i); - final ExonData nextExon = transData.exons().get(i + 1); + ExonData exon = transData.exons().get(i); + ExonData nextExon = transData.exons().get(i + 1); - if(mMappedCoords.stream().anyMatch(x -> positionsWithin(x[SE_START], x[SE_END], exon.End, nextExon.Start))) + if(mMappedCoords.alignmentsWithin(exon.End, nextExon.Start)) { int minExonRank = min(exon.Rank, nextExon.Rank); transRefList.add(new TransExonRef( @@ -822,7 +362,7 @@ public void addIntronicTranscriptRefs(final List transDataList) public List getJunctionMatchingTransRefs(int junctionPosition, boolean isJunctionStart) { - final List matchedTransRefs = Lists.newArrayList(); + List matchedTransRefs = Lists.newArrayList(); mMappedRegions.entrySet().stream() .filter(x -> exonBoundary(x.getValue())) @@ -851,22 +391,18 @@ public void setJunctionPosition(int se, int junctionPosition) mJunctionPositions[se] = junctionPosition; } - public void trimAdapterSoftClipBases(final Read mateRead) + public void trimAdapterSoftClipBases(int trimLength) { - if(orientation() == mateRead.orientation()) + int softClipLength = orientation().isForward() ? rightClipLength() : leftClipLength(); + trimLength = min(trimLength, softClipLength); + + if(trimLength == 0) return; if(orientation().isForward()) { - if(mateRead.unclippedEnd() >= unclippedEnd()) - return; - - int trimLength = unclippedEnd() - mateRead.unclippedEnd(); - int softClipLength = rightClipLength(); - trimLength = min(trimLength, softClipLength); - // trim from upper end - mReadBases = mReadBases.substring(0, mReadBases.length() - trimLength); + mReadBases = mRecord.getReadString().substring(0, mRecord.getReadBases().length - trimLength); mUnclippedEnd -= trimLength; @@ -883,15 +419,8 @@ public void trimAdapterSoftClipBases(final Read mateRead) } else { - if(mateRead.unclippedStart() <= unclippedStart()) - return; - - int trimLength = mateRead.unclippedStart() - unclippedStart(); - int softClipLength = leftClipLength(); - trimLength = min(trimLength, softClipLength); - - // trim from upper end - mReadBases = mReadBases.substring(trimLength); + // trim from lower end + mReadBases = mRecord.getReadString().substring(trimLength); mUnclippedStart += trimLength; @@ -910,7 +439,37 @@ public void trimAdapterSoftClipBases(final Read mateRead) public String toString() { - return String.format("range(%s: %d -> %d, range=%d) length(%d) cigar(%s) id(%s)", - Chromosome, PosStart, PosEnd, range(), baseLength(), mCigarStr != null ? mCigarStr : "", Id); + return String.format("%s range(%s: %d -> %d) flags(%d) cigar(%s)", + id(), chromosome(), mPosStart, mPosEnd, flags(), cigarStr()); + } + + @VisibleForTesting + public void setFlag(SAMFlag flag, boolean toggle) + { + if(mRecord == null) + return; + + int newFlags = mRecord.getFlags(); + + if(toggle) + newFlags |= flag.intValue(); + else + newFlags &= ~flag.intValue(); + + mRecord.setFlags(newFlags); + } + + @VisibleForTesting + public void setStrand(boolean readReversed, boolean mateReadReversed) + { + setFlag(SAMFlag.READ_REVERSE_STRAND, readReversed); + setFlag(SAMFlag.MATE_REVERSE_STRAND, mateReadReversed); + } + + @VisibleForTesting + public void setSuppAlignment(final String suppAlign) + { + mRecord.setAttribute(SUPPLEMENTARY_ATTRIBUTE, suppAlign); + mSupplementaryData = SupplementaryReadData.extractAlignment(mRecord); } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/ReadTranscriptUtils.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/ReadTranscriptUtils.java new file mode 100644 index 00000000000..5484c8e0abc --- /dev/null +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/ReadTranscriptUtils.java @@ -0,0 +1,503 @@ +package com.hartwig.hmftools.isofox.common; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; +import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; +import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_BOUNDARY; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_INTRON; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.WITHIN_EXON; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.exonBoundary; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.validExonMatch; +import static com.hartwig.hmftools.isofox.common.TransMatchType.ALT; +import static com.hartwig.hmftools.isofox.common.TransMatchType.EXONIC; +import static com.hartwig.hmftools.isofox.common.TransMatchType.SPLICE_JUNCTION; +import static com.hartwig.hmftools.isofox.common.TransMatchType.UNKNOWN; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Sets; +import com.hartwig.hmftools.common.gene.ExonData; +import com.hartwig.hmftools.common.gene.TranscriptData; +import com.hartwig.hmftools.common.region.BaseRegion; + +public final class ReadTranscriptUtils +{ + private static final int MIN_SC_BASE_MATCH = 2; + private static final int MAX_SC_BASE_MATCH = 10; + private static final int MAX_SC_WITHIN_EXON_LENGTH = 2; // must stay below the realignment window (REALIGN_MIN_SOFT_CLIP_BASE_LENGTH) + + public static void processOverlappingRegions(final Read read, final List regions) + { + MappedCoords mappedCoords = read.mappedCoords(); + Map mappedRegions = read.getMappedRegions(); + Map transcriptClassification = read.getTranscriptClassifications(); + + // process all regions for each transcript as a group to look for inconsistencies with the transcript definition + Set transcripts = Sets.newHashSet(); + boolean hasSoftClipping = read.isLeftClipped() || read.isRightClipped(); + + for(RegionReadData region : regions) + { + for(TransExonRef ref : region.getTransExonRefs()) + { + transcripts.add(ref.TransId); + } + + RegionMatchType matchType = setRegionMatchType(mappedCoords, mappedRegions, region); + mappedRegions.put(region, matchType); + + boolean checkMissedJunctions = matchType == EXON_INTRON || (hasSoftClipping && exonBoundary(matchType)); + + if(checkMissedJunctions) + checkMissedJunctions(read, mappedCoords, mappedRegions, region); + } + + int mappedRegionCount = read.mappedRegionCount(); + + for(int transId : transcripts) + { + // determine for each transcript whether the mapped regions support a spliced transcript, unspliced or alternate splicing + TransMatchType transMatchType = UNKNOWN; + + List transRegions = regions.stream() + .filter(x -> x.getTransExonRefs().stream().anyMatch(y -> y.TransId == transId)) + .collect(Collectors.toList()); + + // if any reads cross and exon-intron boundary, then mark the transcript as unspliced + + if(transRegions.size() == 1 && mappedRegionCount == 1) + { + // simple case of a single exon and read section + RegionReadData region = transRegions.get(0); + RegionMatchType matchType = mappedRegions.get(region); + + if(matchType == RegionMatchType.NONE) + { + // should never happen since implies this read didn't hit the region at all + transMatchType = ALT; + } + else if(matchType == RegionMatchType.EXON_INTRON) + { + transMatchType = TransMatchType.UNSPLICED; + } + } + else if(mappedRegionCount > transRegions.size()) + { + transMatchType = ALT; + } + else + { + int minExonRank = 0; + int maxExonRank = 0; + + Collections.sort(transRegions); + + for(int regionIndex = 0; regionIndex < transRegions.size(); ++regionIndex) + { + RegionReadData region = transRegions.get(regionIndex); + + int exonRank = region.getExonRank(transId); + maxExonRank = max(maxExonRank, exonRank); + minExonRank = minExonRank == 0 ? exonRank : min(exonRank, minExonRank); + + int mappingIndex = mappedCoords.findRegionIndex(region); + int adjustedMappingIndex = mappingIndex; + + if(mappedCoords.inferredAlignmentAdded(SE_START)) + --adjustedMappingIndex; + + if(adjustedMappingIndex < 0 || adjustedMappingIndex != regionIndex) + { + transMatchType = ALT; + break; + } + + RegionMatchType matchType = mappedRegions.get(region); + + if(matchType == RegionMatchType.EXON_INTRON) + { + transMatchType = TransMatchType.UNSPLICED; + break; + } + else + { + BaseRegion readSection = mappedCoords.regionByIndex(mappingIndex); + int readStartPos = readSection.start(); + int readEndPos = readSection.end(); + + boolean missStart = readStartPos > region.start(); + boolean missEnd = readEndPos < region.end(); + if(regionIndex == 0) + { + if(missEnd) + { + transMatchType = ALT; + break; + } + } + else if(regionIndex == transRegions.size() - 1) + { + if(missStart) + { + transMatchType = ALT; + break; + } + } + else if(missStart || missEnd) + { + transMatchType = ALT; + break; + } + } + } + + if(transMatchType == UNKNOWN) + { + int expectedRegions = maxExonRank - minExonRank + 1; + if(transRegions.size() < expectedRegions) + transMatchType = ALT; + } + } + + if(transMatchType == UNKNOWN) + { + if(transRegions.size() > 1) + { + transMatchType = SPLICE_JUNCTION; + } + else + { + transMatchType = EXONIC; + } + } + + // any read with soft-clipping which cannot be mapped to the next exon, other than for short likely adapter sequence reads, + // is classified as alt, unless the clip is within an exon and a threshold + if(validTranscriptType(transMatchType) && read.containsSoftClipping() && !read.likelyAdaperSoftClipping()) + { + int softClipSide = read.isLeftClipped() ? SE_START : SE_END; + + if(!mappedCoords.isSoftClipRegionMatched(softClipSide) && !shortClipWithinExon(read, softClipSide, transRegions)) + { + transMatchType = ALT; + } + } + + transcriptClassification.put(transId, transMatchType); + } + } + + private static void checkMissedJunctions( + final Read read, final MappedCoords mappedCoords, final Map mappedRegions, + final RegionReadData region) + { + if(read.hasSuppAlignment()) + return; + + // check for reads either soft-clipped or seemingly unspliced, where the extra bases can match with the next exon + + // check start of read + BaseRegion readSection = mappedCoords.lowestAlignment(false); + int readStartPos = readSection.start(); + int readEndPos = readSection.end(); + + int extraBaseLength = 0; + int scLength = 0; + + boolean hasRegionOverhang = region.start() > readStartPos && readEndPos > region.start() + && region.start() - readStartPos <= MAX_SC_BASE_MATCH; + + if(hasRegionOverhang) + { + extraBaseLength = region.start() - readStartPos; + } + + if(read.isLeftClipped() && readStartPos <= region.start()) + { + scLength = read.leftClipLength(); + extraBaseLength += scLength; + } + + // less any deleted bases + // extraBaseLength = max(extraBaseLength - deletedLength, 0); + + // allow a single base match if only 1 region matches + if(extraBaseLength >= 1 && extraBaseLength <= MAX_SC_BASE_MATCH && scLength <= MAX_SC_BASE_MATCH) + { + // first check for a match with the next exon on the lower side + String extraBases = read.readBases().substring(0, extraBaseLength); + + List matchedRegions = region.getPreRegions().stream() + .filter(x -> matchesOtherRegionBases(extraBases, x, false)).collect(Collectors.toList()); + + if(!matchedRegions.isEmpty()) + { + mappedCoords.addSoftClipRegionMatched(true, matchedRegions.size()); + mappedRegions.put(region, EXON_BOUNDARY); + + if(matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength < MIN_SC_BASE_MATCH)) + { + // truncate the read positions back to match the exon boundary + if(!mappedCoords.lowerInferredAlignmentAdded() && hasRegionOverhang) + readSection.setStart(readSection.start() + region.start() - readStartPos); + } + + // if only one region is matched or the min bases matched is satisfied, then create a mapping to the next region, + // otherwise treat the splice support as ambiguous (it not mapped to the next region) + if(matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength >= MIN_SC_BASE_MATCH)) + { + for(RegionReadData preRegion : matchedRegions) + { + // add matched coordinates for this exon and add it as a region + mappedRegions.put(preRegion, EXON_BOUNDARY); + mappedCoords.addInferredRegion(true, preRegion.end() - extraBaseLength + 1, preRegion.end()); + } + } + } + } + + // check end of read + readSection = mappedCoords.highestAlignment(false); + readStartPos = readSection.start(); + readEndPos = readSection.end(); + + extraBaseLength = 0; + scLength = 0; + + hasRegionOverhang = readEndPos > region.end() && readStartPos < region.end() && readEndPos - region.end() <= MAX_SC_BASE_MATCH; + + if(hasRegionOverhang) + { + extraBaseLength = readEndPos - region.end(); + } + + if(read.isRightClipped() && readEndPos >= region.end()) + { + scLength = read.rightClipLength(); + extraBaseLength += scLength; + } + + if(extraBaseLength >= 1 && extraBaseLength <= MAX_SC_BASE_MATCH && scLength <= MAX_SC_BASE_MATCH) + { + // now check for a match to the next exon up + int readLength = read.baseLength(); + String extraBases = read.readBases().substring(readLength - extraBaseLength, readLength); + + List matchedRegions = region.getPostRegions().stream() + .filter(x -> matchesOtherRegionBases(extraBases, x, true)).collect(Collectors.toList()); + + if(!matchedRegions.isEmpty()) + { + mappedCoords.addSoftClipRegionMatched(false, matchedRegions.size()); + + mappedRegions.put(region, EXON_BOUNDARY); + + if(matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength < MIN_SC_BASE_MATCH)) + { + if(!mappedCoords.upperInferredAlignmentAdded() && hasRegionOverhang) + readSection.setEnd(readSection.end() - (readEndPos - region.end())); + } + + if(matchedRegions.size() == 1 || (matchedRegions.size() > 1 && extraBaseLength >= MIN_SC_BASE_MATCH)) + { + for(RegionReadData postRegion : matchedRegions) + { + mappedRegions.put(postRegion, EXON_BOUNDARY); + mappedCoords.addInferredRegion(false, postRegion.start(), postRegion.start() + extraBaseLength - 1); + } + } + } + } + } + + private static boolean matchesOtherRegionBases(final String extraBases, final RegionReadData otherRegion, boolean matchToStart) + { + int otherRegionLength = otherRegion.length(); + + if(extraBases.length() > otherRegionLength) + return false; + + String otherRegionBases = matchToStart ? otherRegion.refBases().substring(0, extraBases.length()) + : otherRegion.refBases().substring(otherRegionLength - extraBases.length(), otherRegionLength); + + return (otherRegionBases.equals(extraBases)); + } + + private static boolean shortClipWithinExon(final Read read, int se, final List transRegions) + { + int clipLength = se == SE_START ? read.leftClipLength() : read.rightClipLength(); + + if(clipLength > MAX_SC_WITHIN_EXON_LENGTH) + return false; + + int readPositionBoundary = read.getCoordsBoundary(se); + + int transcriptBoundary = se == SE_START ? + transRegions.stream().mapToInt(RegionReadData::start).min().orElse(0) : + transRegions.stream().mapToInt(RegionReadData::end).max().orElse(0); + + return se == SE_START ? + readPositionBoundary - clipLength >= transcriptBoundary : readPositionBoundary + clipLength <= transcriptBoundary; + } + + public static List getUniqueValidRegion(final Read read1, final Read read2) + { + List regions = read1.getMappedRegions().entrySet().stream() + .filter(x -> validExonMatch(x.getValue())) + .map(x -> x.getKey()).collect(Collectors.toList()); + + List regions2 = read2.getMappedRegions().entrySet().stream() + .filter(x -> validExonMatch(x.getValue())) + .map(x -> x.getKey()).collect(Collectors.toList()); + + for(RegionReadData region : regions2) + { + if(!regions.contains(region)) + regions.add(region); + } + + return regions; + } + + public static boolean validTranscriptType(TransMatchType transType) + { + return transType == EXONIC || transType == SPLICE_JUNCTION; + } + + private static RegionMatchType setRegionMatchType( + final MappedCoords mappedCoords, final Map mappedRegions, final RegionReadData region) + { + int mappingIndex = mappedCoords.findRegionIndex(region); + if(mappingIndex == MappedCoords.INVALID_INDEX) + return RegionMatchType.NONE; + + RegionMatchType matchType = getRegionMatchType(mappedCoords, region, mappingIndex); + mappedRegions.put(region, matchType); + return matchType; + } + + /* + public static RegionMatchType getRegionMatchType(final MappedCoords mappedCoords, final RegionReadData region) + { + int mappingIndex = mappedCoords.findRegionIndex(region); + if(mappingIndex == MappedCoords.INVALID_INDEX) + return RegionMatchType.NONE; + + return getRegionMatchType(mappedCoords, region, mappingIndex); + } + */ + + public static RegionMatchType getRegionMatchType(final MappedCoords mappedCoords, final RegionReadData region, int mappingIndex) + { + if(mappingIndex == MappedCoords.INVALID_INDEX || mappingIndex >= mappedCoords.alignmentCount()) + return RegionMatchType.NONE; + + BaseRegion readSection = mappedCoords.regionByIndex(mappingIndex); + int readStartPos = readSection.start(); + int readEndPos = readSection.end(); + + if(readEndPos < region.start() || readStartPos > region.end()) + return RegionMatchType.NONE; + + if(readStartPos < region.start() || readEndPos > region.end()) + return RegionMatchType.EXON_INTRON; + + if(readStartPos > region.start() && readEndPos < region.end()) + return WITHIN_EXON; + + return EXON_BOUNDARY; + } + + public static void markRegionBases(final List readCoords, final RegionReadData region) + { + int[] regionBaseDepth = region.refBasesMatched(); + + if(regionBaseDepth == null) + return; + + for(BaseRegion readSection : readCoords) + { + int readStartPos = readSection.start(); + int readEndPos = readSection.end(); + + if(readStartPos > region.end() || readEndPos < region.start()) + continue; + + // process this overlap + int regionBaseIndex = readStartPos > region.start() ? readStartPos - region.start() : 0; + int overlap = min(readEndPos, region.end()) - max(readStartPos, region.start()) + 1; + + if(regionBaseIndex + overlap > regionBaseDepth.length) + { + ISF_LOGGER.error("region({}) read coords({} -> {}) regionBaseIndex({}) overlap({}) regionLength({})", + region, readStartPos, readEndPos, regionBaseIndex, overlap, regionBaseDepth.length); + return; + } + + for(int j = regionBaseIndex; j < regionBaseIndex + overlap; ++j) + { + ++regionBaseDepth[j]; + } + } + } + + public static int calcFragmentLength(final TranscriptData transData, final Read read1, final Read read2) + { + int minReadPos = min(read1.alignmentStart(), read2.alignmentStart()); + int maxReadPos = max(read1.alignmentEnd(), read2.alignmentEnd()); + return calcFragmentLength(transData, minReadPos, maxReadPos); + } + + public static int calcFragmentLength(final TranscriptData transData, final int minReadPos, final int maxReadPos) + { + // calculate fragment length within this transcript assuming it has been spliced + int transcriptBases = 0; + boolean startFound = false; + + for(ExonData exon : transData.exons()) + { + if(!startFound) + { + if(minReadPos < exon.Start - MAX_SC_BASE_MATCH) + break; + + if(minReadPos > exon.End) + continue; + + if(maxReadPos <= exon.End) + { + // within same exon + return maxReadPos - minReadPos + 1; + } + + startFound = true; + transcriptBases = exon.End - max(exon.Start, minReadPos) + 1; + } + else + { + if(maxReadPos > exon.End) + { + transcriptBases += exon.baseLength(); + } + else if(maxReadPos < exon.Start) + { + break; + } + else + { + transcriptBases += maxReadPos - exon.Start + 1; + break; + } + } + } + + return transcriptBases; + } +} diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/ReadUtils.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/ReadUtils.java new file mode 100644 index 00000000000..f097f7a7a61 --- /dev/null +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/ReadUtils.java @@ -0,0 +1,108 @@ +package com.hartwig.hmftools.isofox.common; + +import static java.lang.Math.max; + +import static com.hartwig.hmftools.common.bam.CigarUtils.getReadIndexFromPosition; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.CONSENSUS_INFO_DELIM; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.CONSENSUS_READ_ATTRIBUTE; +import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; +import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; +import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; +import static com.hartwig.hmftools.isofox.common.FragmentType.DUPLICATE; + +import com.hartwig.hmftools.common.bam.ClippedSide; +import com.hartwig.hmftools.common.region.BaseRegion; + +import htsjdk.samtools.SAMRecord; + +public final class ReadUtils +{ + public static void trimAdapterBases(final Read read1, final Read read2) + { + if(read1.orientation() == read2.orientation()) + return; + + // no overlap + if(!positionsOverlap(read1.alignmentStart(), read1.alignmentEnd(), read2.alignmentStart(), read2.alignmentEnd())) + return; + + // determine the soft-clip bases to trim from each end + // note that differing splicing can mean that alignments are no the true indication of 5' base boundaries + int readRefPositionStart1 = 0; + int readRefPositionStart2 = 0; + for(BaseRegion mappedCoords1 : read1.getMappedRegionCoords()) + { + for(BaseRegion mappedCoords2 : read2.getMappedRegionCoords()) + { + if(positionsOverlap(mappedCoords1.start(), mappedCoords1.end(), mappedCoords2.start(), mappedCoords2.end())) + { + readRefPositionStart1 = readRefPositionStart2 = max(mappedCoords1.start(), mappedCoords2.start()); + break; + } + } + + if(readRefPositionStart1 > 0) + break; + } + + if(readRefPositionStart1 == 0) + return; + + int readIndexStart1 = getReadIndexFromPosition(read1.alignmentStart(), read1.cigarElements(), readRefPositionStart1); + int readIndexStart2 = getReadIndexFromPosition(read2.alignmentStart(), read2.cigarElements(), readRefPositionStart2); + int readUpperBaseLength1 = read1.baseLength() - readIndexStart1 - 1; + int readUpperBaseLength2 = read2.baseLength() - readIndexStart2 - 1; + + int trimLength1 = 0; + int trimLength2 = 0; + + if(read1.orientation().isForward()) + { + // trim first read on the upper 3' side and vice versa + trimLength2 = max(readIndexStart2 - readIndexStart1, 0); + trimLength1 = max(readUpperBaseLength1 - readUpperBaseLength2, 0); + } + else + { + trimLength1 = max(readIndexStart1 - readIndexStart2, 0); + trimLength2 = max(readUpperBaseLength2 - readUpperBaseLength1, 0); + } + + read1.trimAdapterSoftClipBases(trimLength1); + read2.trimAdapterSoftClipBases(trimLength2); + } + + public static ClippedSide clippedSide(final Read read) + { + int leftScLength = read.leftClipLength(); + int rightScLength = read.rightClipLength(); + + if(leftScLength > 0 && rightScLength > 0) + { + return leftScLength >= rightScLength ? + new ClippedSide(SE_START, leftScLength, true) : new ClippedSide(SE_END, rightScLength, true); + } + else if(leftScLength > 0) + { + return new ClippedSide(SE_START, leftScLength, true); + } + else + { + return new ClippedSide(SE_END, rightScLength, rightScLength > 0); + } + } + + public static int consensusDuplicateCount(final SAMRecord record) + { + String consensusInfo = record.getStringAttribute(CONSENSUS_READ_ATTRIBUTE); + + if(consensusInfo == null) + return 0; + + String[] consensusComponents = consensusInfo.split(CONSENSUS_INFO_DELIM, 3); + int duplicateCount = Integer.parseInt(consensusComponents[0]); + + // return 1 less than the cached count since 2 duplicates means 1 primary and 1 duplicate + return max(duplicateCount - 1, 1); + } +} diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/RegionReadData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/RegionReadData.java index fd7d245c796..a6fc1321134 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/RegionReadData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/RegionReadData.java @@ -65,7 +65,7 @@ public RegionReadData(final String chromosome, int posStart, int posEnd) public int getExonRank(final int transId) { - for(final TransExonRef transRef : mTransExonRefs) + for(TransExonRef transRef : mTransExonRefs) { if(transRef.TransId == transId) return transRef.ExonRank; @@ -190,11 +190,11 @@ public static void findUniqueBases(final List regions) { for(int i = 0; i < regions.size() - 1; ++i) { - final RegionReadData region1 = regions.get(i); + RegionReadData region1 = regions.get(i); for(int j = i + 1; j < regions.size(); ++j) { - final RegionReadData region2 = regions.get(j); + RegionReadData region2 = regions.get(j); region1.markNonUniqueBases(region2); } } @@ -276,7 +276,7 @@ public void clearState() { if(mRefBasesMatched != null) { - for (int i = 0; i < mRefBasesMatched.length; ++i) + for(int i = 0; i < mRefBasesMatched.length; ++i) mRefBasesMatched[i] = 0; } } @@ -305,7 +305,7 @@ public static void generateExonicRegions( final String geneId, final String chromosome, final List regions, final List transcripts) { // form a genomic region for each unique exon amongst the transcripts - for(final TranscriptData transData : transcripts) + for(TranscriptData transData : transcripts) { RegionReadData prevRegionReadData = null; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/TransExonRef.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/TransExonRef.java index 97e94c42249..2c087583816 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/common/TransExonRef.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/common/TransExonRef.java @@ -8,7 +8,7 @@ public class TransExonRef public final int TransId; public final String TransName; public final int ExonRank; - private final Boolean mIsCanonical; + public final Boolean IsCanonical; public TransExonRef(final String geneId, final int transId, final String transName, final int exonRank) { @@ -21,7 +21,7 @@ public TransExonRef(final String geneId, final int transId, final String transNa TransId = transId; TransName = transName; ExonRank = exonRank; - mIsCanonical = isCanonical; + IsCanonical = isCanonical; } public boolean matches(final TransExonRef other) @@ -41,7 +41,7 @@ private boolean matches(final TransExonRef other, int maxDiffVsOther) return other.ExonRank >= ExonRank + maxDiffVsOther && other.ExonRank <= ExonRank; } - public boolean isCanonical() { return mIsCanonical != null && mIsCanonical; } + public boolean isCanonical() { return IsCanonical != null && IsCanonical; } public static boolean hasMatch(final List list1, final List list2) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/CategoryCountsData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/CategoryCountsData.java index fb5fb856c5e..2b1bbde8494 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/CategoryCountsData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/CategoryCountsData.java @@ -14,7 +14,7 @@ public class CategoryCountsData private final List mTranscripts; private final List mUnsplicedGenes; private double mFragmentCount; - private int mLowMapQualFragments; + private int mMultiMappedFragments; private double[] mFragmentCountsByGcRatio; // counts by length is only used for expected not actual counts, and is then adjusted by the observed fragment length distribution @@ -27,7 +27,7 @@ public CategoryCountsData(final List transcripts, final List un mTranscripts = transcripts; mUnsplicedGenes = unsplicedGenes; mFragmentCount = 0; - mLowMapQualFragments = 0; + mMultiMappedFragments = 0; mCombinedKey = formTranscriptIds(); @@ -76,15 +76,15 @@ public boolean matches(final List transcripts, final List unspl } public final double fragmentCount() { return mFragmentCount; } - public final int lowMapQualFragments() { return mLowMapQualFragments; } + public final int multiMappedFragments() { return mMultiMappedFragments; } public final double[] fragmentCountsByGcRatio() { return mFragmentCountsByGcRatio; } - public void addCounts(double count) + public void addCounts(double count, boolean multiMapped) { mFragmentCount += count; - if(count < 1) - ++mLowMapQualFragments; + if(multiMapped) + ++mMultiMappedFragments; } public void adjustCounts(double factor) @@ -92,18 +92,18 @@ public void adjustCounts(double factor) mFragmentCount *= factor; } - public void addGcRatioCounts(double count, final int[] gcRatioIndex, final double[] counts) + public void addGcRatioCounts(double count, boolean multiMapped, final int[] gcRatioIndex, final double[] counts) { mFragmentCount += count; - if(count < 1) - ++mLowMapQualFragments; + if(multiMapped) + ++mMultiMappedFragments; if(gcRatioIndex != null && counts != null) { - for (int i = 0; i < gcRatioIndex.length; ++i) + for(int i = 0; i < gcRatioIndex.length; ++i) { - if (gcRatioIndex[i] >= 0) + if(gcRatioIndex[i] >= 0) mFragmentCountsByGcRatio[gcRatioIndex[i]] += counts[i]; } } @@ -180,12 +180,12 @@ private String formTranscriptIds() // convert into an order list of ints List transIds = Lists.newArrayList(); - for (Integer transId : mTranscripts) + for(Integer transId : mTranscripts) { int index = 0; while (index < transIds.size()) { - if (transId < transIds.get(index)) + if(transId < transIds.get(index)) break; ++index; @@ -196,7 +196,7 @@ private String formTranscriptIds() List items = Lists.newArrayList(); - for (Integer transId : transIds) + for(Integer transId : transIds) { items.add(String.valueOf(transId)); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedCountsCache.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedCountsCache.java index ef629c2e07d..4400c5dff46 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedCountsCache.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedCountsCache.java @@ -49,7 +49,7 @@ public List getGeneExpectedRatesData(final String chrId, fin { List geneSetCountsData = mGeneSetCategoryDataMap.get(chrId); - if (geneSetCountsData == null || !geneSetCountsDataMatches(geneIds, geneSetCountsData)) + if(geneSetCountsData == null || !geneSetCountsDataMatches(geneIds, geneSetCountsData)) { geneSetCountsData = findGeneSetCountsData(geneIds); } @@ -59,7 +59,7 @@ public List getGeneExpectedRatesData(final String chrId, fin private boolean geneSetCountsDataMatches(final List geneIds, final List geneSetCountsData) { - final Set geneTransSet = Sets.newHashSet(); + Set geneTransSet = Sets.newHashSet(); geneSetCountsData.forEach(x -> x.unsplicedGeneIds().forEach(y -> geneTransSet.add(y))); // confirm that the genes in the collection match @@ -69,7 +69,7 @@ private boolean geneSetCountsDataMatches(final List geneIds, final List< private final List findGeneSetCountsData(final List geneIds) { // manually find the gene set by looking for a match of all geneIds - for(final List geneCounts : mGeneSetCategoryDataMap.values()) + for(List geneCounts : mGeneSetCategoryDataMap.values()) { if(geneSetCountsDataMatches(geneIds, geneCounts)) return geneCounts; @@ -81,7 +81,7 @@ private final List findGeneSetCountsData(final List // GeneSetId,TransId,Category,Counts for each fragment length private boolean loadExpCountsFile() { - if (!Files.exists(Paths.get(mConfig.ExpCountsFile))) + if(!Files.exists(Paths.get(mConfig.ExpCountsFile))) { ISF_LOGGER.warn("invalid gene ID file({})", mConfig.ExpCountsFile); return false; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedRatesCommon.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedRatesCommon.java index e489f69d411..69ef73d01c8 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedRatesCommon.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpectedRatesCommon.java @@ -51,11 +51,11 @@ public static List loadFragmentSizeConfig(final ConfigBuilder conf public static Map> createTransComboDataMap(final List categoryCountsData) { - final Map> transGeneCountsMap = Maps.newHashMap(); + Map> transGeneCountsMap = Maps.newHashMap(); - for(final CategoryCountsData tcData : categoryCountsData) + for(CategoryCountsData tcData : categoryCountsData) { - final Set geneTransNames = Sets.newHashSet(); + Set geneTransNames = Sets.newHashSet(); tcData.unsplicedGeneIds().forEach(x -> geneTransNames.add(x)); tcData.transcriptIds().forEach(x -> geneTransNames.add(String.valueOf(x))); @@ -81,7 +81,7 @@ public static void formTranscriptDefinitions(final List cate // convert fragment counts in each category per transcript into the equivalent of a signature per transcript collectCategories(categoryCountsData, expRatesData); - final Map> transGeneCountsMap = createTransComboDataMap(categoryCountsData); + Map> transGeneCountsMap = createTransComboDataMap(categoryCountsData); int categoryCount = expRatesData.Categories.size(); @@ -91,15 +91,15 @@ public static void formTranscriptDefinitions(final List cate for(int transIndex = 0; transIndex < expRatesData.TranscriptIds.size(); ++transIndex) { - final String transId = expRatesData.TranscriptIds.get(transIndex); + String transId = expRatesData.TranscriptIds.get(transIndex); double[] categoryCounts = new double[categoryCount]; - final List transCounts = transGeneCountsMap.get(transId); + List transCounts = transGeneCountsMap.get(transId); for(CategoryCountsData tcData : transCounts) { - final String transKey = tcData.combinedKey(); + String transKey = tcData.combinedKey(); double fragmentCount = tcData.fragmentCount(); if(fragmentCount > 0) @@ -125,9 +125,9 @@ public static void formTranscriptDefinitions(final List cate private static void collectCategories( final List categoryCountsData, ExpectedRatesData expRatesData) { - for(final CategoryCountsData tcData : categoryCountsData) + for(CategoryCountsData tcData : categoryCountsData) { - final String transKey = tcData.combinedKey(); + String transKey = tcData.combinedKey(); if(tcData.fragmentCount() > 0 || tcData.transcriptIds().isEmpty()) // force inclusion of unspliced gene categories { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpressionReadTracker.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpressionReadTracker.java index d9e951ffb66..578e0a05e3f 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpressionReadTracker.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/ExpressionReadTracker.java @@ -1,13 +1,10 @@ package com.hartwig.hmftools.isofox.expression; -import static java.lang.Math.min; - import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; -import static com.hartwig.hmftools.isofox.IsofoxConstants.MULTI_MAP_QUALITY_THRESHOLD; import static com.hartwig.hmftools.isofox.IsofoxFunction.TRANSCRIPT_COUNTS; import static com.hartwig.hmftools.isofox.adjusts.GcRatioCounts.calcGcRatioFromReadRegions; -import static com.hartwig.hmftools.isofox.common.FragmentType.LOW_MAP_QUAL; +import static com.hartwig.hmftools.isofox.common.FragmentType.MULTI_MAPPED; import static com.hartwig.hmftools.isofox.common.RegionMatchType.validExonMatch; import static com.hartwig.hmftools.isofox.common.CommonUtils.deriveCommonRegions; import static com.hartwig.hmftools.isofox.common.TransMatchType.SPLICE_JUNCTION; @@ -16,6 +13,7 @@ import java.util.stream.Collectors; import com.google.common.collect.Lists; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.IsofoxConfig; import com.hartwig.hmftools.isofox.adjusts.GcRatioCounts; import com.hartwig.hmftools.isofox.common.FragmentMatchType; @@ -61,7 +59,8 @@ public void setGeneData(final GeneCollection genes) } public void processUnsplicedGenes( - final List overlapGenes, final List validTranscripts, final List commonMappings, int minMapQuality) + final List overlapGenes, final List validTranscripts, final List commonMappings, + int fragmentCount, boolean multiMapped) { if(!mEnabled) return; @@ -71,13 +70,13 @@ public void processUnsplicedGenes( if(!unsplicedGeneIds.isEmpty()) { CategoryCountsData catCounts = getCategoryCountsData(validTranscripts, unsplicedGeneIds); - addGcCounts(catCounts, commonMappings, minMapQuality); + addGcCounts(catCounts, commonMappings, fragmentCount, multiMapped); } } public void processUnsplicedGenes( final FragmentMatchType comboTransMatchType, final List overlapGenes, final List validTranscripts, - final List commonMappings, int minMapQuality) + final List commonMappings, int fragmentCount, boolean multiMapped) { if(!mEnabled) return; @@ -86,10 +85,11 @@ public void processUnsplicedGenes( overlapGenes.stream().map(x -> x.Gene.GeneId).collect(Collectors.toList()) : Lists.newArrayList(); CategoryCountsData catCounts = getCategoryCountsData(validTranscripts, unsplicedGeneIds); - addGcCounts(catCounts, commonMappings, minMapQuality); + addGcCounts(catCounts, commonMappings, fragmentCount, multiMapped); } - public void processIntronicReads(final List genes, final Read read1, final Read read2) + public void processIntronicReads( + final List genes, final Read read1, final Read read2, int fragmentCount, boolean multiMapped) { if(!mEnabled) return; @@ -100,39 +100,9 @@ public void processIntronicReads(final List genes, final Read read { CategoryCountsData catCounts = getCategoryCountsData(Lists.newArrayList(), unsplicedGeneIds); - List readRegions = deriveCommonRegions(read1.getMappedRegionCoords(), read2.getMappedRegionCoords()); - addGcCounts(catCounts, readRegions, min(read1.mapQuality(), read2.mapQuality())); - } - } - - public void processEnrichedGeneFragments(long enrichedGeneFragments) - { - if(!mEnabled) - return; - - // add to category counts - final int[] enrichedRegion = mGenes.getEnrichedRegion(); - final List unsplicedGeneIds = mGenes.findGenesCoveringRange(enrichedRegion[SE_START], enrichedRegion[SE_END], true) - .stream().map(x -> x.Gene.GeneId).collect(Collectors.toList()); - - final List transIds = mGenes.getEnrichedTranscripts().stream().map(x -> Integer.valueOf(x.TransId)).collect(Collectors.toList()); - CategoryCountsData catCounts = getCategoryCountsData(transIds, unsplicedGeneIds); - - // compute and cache GC data - double gcRatio = calcGcRatioFromReadRegions(mConfig.RefGenome, mGenes.chromosome(), Lists.newArrayList(mGenes.getEnrichedRegion())); - - int[] gcRatioIndices = { -1, -1 }; - double[] gcRatioCounts = { 0, 0 }; - - if(mGcRatioCounts != null) - { - mGcRatioCounts.determineRatioData(gcRatio, gcRatioIndices, gcRatioCounts); - gcRatioCounts[0] *= enrichedGeneFragments; - gcRatioCounts[1] *= enrichedGeneFragments; + List readRegions = deriveCommonRegions(read1.getMappedRegionCoords(), read2.getMappedRegionCoords()); + addGcCounts(catCounts, readRegions, fragmentCount, multiMapped); } - - addGcCounts(catCounts, gcRatioIndices, gcRatioCounts, enrichedGeneFragments); - } private CategoryCountsData getCategoryCountsData(final List transcripts, final List geneIds) @@ -156,7 +126,7 @@ private CategoryCountsData getCategoryCountsData(final List transcripts public void processValidTranscript(int transId, final List reads, boolean isUniqueTrans) { - final List processedRegions = Lists.newArrayList(); + List processedRegions = Lists.newArrayList(); for(Read read : reads) { @@ -206,43 +176,32 @@ else if(region1.getPreRegions().contains(region2)) } } - public void addGcCounts(final CategoryCountsData catCounts, final List readRegions, int minMapQuality) + public void addGcCounts( + final CategoryCountsData catCounts, final List readRegions, int fragmentCount, boolean multiMapped) { int[] gcRatioIndices = { -1, -1 }; double[] gcRatioCounts = { 0, 0 }; - if (mGcRatioCounts != null) + if(mGcRatioCounts != null) { double gcRatio = calcGcRatioFromReadRegions(mConfig.RefGenome, mGenes.chromosome(), readRegions); mGcRatioCounts.determineRatioData(gcRatio, gcRatioIndices, gcRatioCounts); } - double fragmentCount = 1; + if(multiMapped) + mGenes.addCount(MULTI_MAPPED, 1); - if(minMapQuality <= MULTI_MAP_QUALITY_THRESHOLD) - { - if(minMapQuality == 3) - fragmentCount = 0.5; - else if(minMapQuality == 2) - fragmentCount = 0.33; - else if(minMapQuality == 1) - fragmentCount = 0.2; - else - fragmentCount = 0.1; - - mGenes.addCount(LOW_MAP_QUAL, 1); - } - - addGcCounts(catCounts, gcRatioIndices, gcRatioCounts, fragmentCount); + addGcCounts(catCounts, gcRatioIndices, gcRatioCounts, fragmentCount, multiMapped); } - public void addGcCounts(final CategoryCountsData catCounts, final int[] gcRatioIndices, double[] gcRatioCounts, double count) + public void addGcCounts( + final CategoryCountsData catCounts, final int[] gcRatioIndices, double[] gcRatioCounts, int count, boolean multiMapped) { if(mGcRatioCounts != null) { for(int i = 0; i < gcRatioIndices.length; ++i) { - if (gcRatioIndices[i] >= 0) + if(gcRatioIndices[i] >= 0) { mGcRatioCounts.addGcRatioCount(gcRatioIndices[i], gcRatioCounts[i]); mGeneGcRatioCounts.addGcRatioCount(gcRatioIndices[i], gcRatioCounts[i]); @@ -250,14 +209,13 @@ public void addGcCounts(final CategoryCountsData catCounts, final int[] gcRatioI } if(mConfig.applyGcBiasAdjust()) - catCounts.addGcRatioCounts(count, gcRatioIndices, gcRatioCounts); + catCounts.addGcRatioCounts(count, multiMapped, gcRatioIndices, gcRatioCounts); else - catCounts.addCounts(count); + catCounts.addCounts(count, multiMapped); } else { - catCounts.addCounts(count); + catCounts.addCounts(count, multiMapped); } } - } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/GeneCollectionSummary.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/GeneCollectionSummary.java index c1924e70276..9baf7fb8c1e 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/GeneCollectionSummary.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/GeneCollectionSummary.java @@ -18,7 +18,7 @@ public class GeneCollectionSummary public final List GeneResults; public final List TranscriptResults; - private final int mTotalLowMqFragments; + private final int mTotalMultiMappedFragments; private final Map mFitAllocations; // results from the expected rate vs counts fit routine, stored per transcript private double mFitResiduals; @@ -32,7 +32,7 @@ public GeneCollectionSummary( GeneResults = Lists.newArrayList(); TranscriptResults = Lists.newArrayList(); - mTotalLowMqFragments = TransCategoryCounts.stream().mapToInt(x -> x.lowMapQualFragments()).sum(); + mTotalMultiMappedFragments = TransCategoryCounts.stream().mapToInt(x -> x.multiMappedFragments()).sum(); mFitAllocations = Maps.newHashMap(); mFitResiduals = 0; @@ -55,9 +55,9 @@ public void setFitAllocations() { Map geneSpliceTotals = Maps.newHashMap(); - for (final TranscriptResult transResult : TranscriptResults) + for(TranscriptResult transResult : TranscriptResults) { - final String transName = transResult.Trans.TransName; + String transName = transResult.Trans.TransName; double fitAllocation = getFitAllocation(transName); transResult.setFitAllocation(fitAllocation); @@ -70,7 +70,7 @@ public void setFitAllocations() } - for(final GeneResult geneResult : GeneResults) + for(GeneResult geneResult : GeneResults) { Double geneFitAllocation = geneSpliceTotals.get(geneResult.Gene.GeneId); geneResult.setFitAllocation( @@ -89,7 +89,7 @@ public void allocateResidualsToGenes() // divvy up residuals between the genes according to their length long totalGeneLength = GeneResults.stream().mapToLong(x -> x.Gene.length()).sum(); - for (final GeneResult geneResult : GeneResults) + for(GeneResult geneResult : GeneResults) { double residualsFraction = geneResult.Gene.length() / (double) totalGeneLength * mFitResiduals; geneResult.setFitResiduals(residualsFraction); @@ -102,7 +102,7 @@ public void applyGcAdjustments(final double[] gcAdjustments) double originalTotal = 0; double newTotal = 0; - for(final CategoryCountsData catCounts : TransCategoryCounts) + for(CategoryCountsData catCounts : TransCategoryCounts) { originalTotal += catCounts.fragmentCount(); catCounts.applyGcAdjustments(gcAdjustments); @@ -115,9 +115,9 @@ public void applyGcAdjustments(final double[] gcAdjustments) TransCategoryCounts.forEach(x -> x.adjustCounts(adjustFactor)); } - public void assignLowMapQualityFragments() + public void assignMultiMappedFragments() { - if(mTotalLowMqFragments == 0) + if(mTotalMultiMappedFragments == 0) return; double totalTranscriptAlloc = TranscriptResults.stream().mapToDouble(x -> x.getFitAllocation()).sum(); @@ -127,22 +127,22 @@ public void assignLowMapQualityFragments() if(totalAlloc == 0) return; - double splicedLowMqFrags = mTotalLowMqFragments * totalTranscriptAlloc / totalAlloc; - double unsplicedLowMqFrags = mTotalLowMqFragments * totalUnsplicedAlloc / totalAlloc; + double splicedMultiMappedFrags = mTotalMultiMappedFragments * totalTranscriptAlloc / totalAlloc; + double unsplicedMultiMappedFrags = mTotalMultiMappedFragments * totalUnsplicedAlloc / totalAlloc; // divide amongst transcripts - for(final TranscriptResult transResult : TranscriptResults) + for(TranscriptResult transResult : TranscriptResults) { - double transAlloc = totalTranscriptAlloc > 0 ? transResult.getFitAllocation() / totalTranscriptAlloc * splicedLowMqFrags : 0; - transResult.setLowMapQualsAllocation(transAlloc); + double transAlloc = totalTranscriptAlloc > 0 ? transResult.getFitAllocation() / totalTranscriptAlloc * splicedMultiMappedFrags : 0; + transResult.setMultiMappedAllocation(transAlloc); } // split amongst genes as per fragment allocation - for(final GeneResult geneResult : GeneResults) + for(GeneResult geneResult : GeneResults) { - double splicedAlloc = totalTranscriptAlloc > 0 ? geneResult.getSplicedAlloc() / totalTranscriptAlloc * splicedLowMqFrags : 0; - double unsplicedAlloc = totalUnsplicedAlloc > 0 ? geneResult.getUnsplicedAlloc() / totalUnsplicedAlloc * unsplicedLowMqFrags : 0; - geneResult.setLowMapQualsAllocation(splicedAlloc + unsplicedAlloc); + double splicedAlloc = totalTranscriptAlloc > 0 ? geneResult.getSplicedAlloc() / totalTranscriptAlloc * splicedMultiMappedFrags : 0; + double unsplicedAlloc = totalUnsplicedAlloc > 0 ? geneResult.getUnsplicedAlloc() / totalUnsplicedAlloc * unsplicedMultiMappedFrags : 0; + geneResult.setMultiMappedAllocation(splicedAlloc + unsplicedAlloc); } } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/PanelTpmNormaliser.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/PanelTpmNormaliser.java index 82b0969707b..d3d8e6bf979 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/PanelTpmNormaliser.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/PanelTpmNormaliser.java @@ -79,7 +79,7 @@ private void loadNormalisations(final String filename) int geneIdIndex = fieldsMap.get(FLD_GENE_ID); int adjustIndex = fieldsMap.get(FLD_TPM_ADJUST_FACTOR); - for(final String data : lines) + for(String data : lines) { String[] values = data.split(fileDelim); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/TranscriptExpression.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/TranscriptExpression.java index 547c228b17d..8fbd6145a74 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/TranscriptExpression.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/TranscriptExpression.java @@ -66,7 +66,7 @@ public TranscriptExpression(final IsofoxConfig config, final ExpectedCountsCache if(totalFrequencyFrags > 0) { - for(final FragmentSize fragFrequency : mConfig.FragmentSizeData) + for(FragmentSize fragFrequency : mConfig.FragmentSizeData) { mFragmentFrequencyRates.add(fragFrequency.Frequency / totalFrequencyFrags); } @@ -162,9 +162,9 @@ public void runTranscriptEstimation( private static final int ADJUSTED_TPM = 1; private static final double TPM_MILLION = 1000000; - public static double[] calcTpmFactors(final List geneSummaryData, final List enrichedGeneIds) + public static double[] calcTpmFactors(final List geneSummaryData) { - // exclude enriched genes and cap the contribution of any one gene to 1% + // cap the contribution of any one gene to 1% double[] results = {0, 0}; List fragsPerKbSet = Lists.newArrayListWithExpectedSize(200000); @@ -173,17 +173,14 @@ public static double[] calcTpmFactors(final List geneSumm double fragsPerKbTotal = 0; double rawFragsPerKbTotal = 0; - for(final GeneCollectionSummary summaryData : geneSummaryData) + for(GeneCollectionSummary summaryData : geneSummaryData) { - for(final TranscriptResult transResult : summaryData.TranscriptResults) + for(TranscriptResult transResult : summaryData.TranscriptResults) { double fragsPerKb = transResult.fragmentsPerKb(); rawFragsPerKbTotal += fragsPerKb; - if(enrichedGeneIds.contains(transResult.Trans.GeneId)) - continue; - if(fragsPerKb < 0.5) { fragsPerKbTotal += fragsPerKb; @@ -230,7 +227,7 @@ public static double[] calcTpmFactors(final List geneSumm if(abs(maxGeneContribPerc - MAX_GENE_PERC_CONTRIBUTION) < 0.001) break; - if (maxGeneContribPerc > MAX_GENE_PERC_CONTRIBUTION) + if(maxGeneContribPerc > MAX_GENE_PERC_CONTRIBUTION) { maxFragsPerKbTotal = fragsPerKbTotal; nextFragsPerKbTotal = max(fragsPerKbTotal * 0.5, minFragsPerKbTotal); @@ -278,7 +275,7 @@ public static void setTranscriptsPerMillion(final List al for(GeneResult geneResult : geneSummary.GeneResults) { - final double[] geneTpm = geneTPMs.get(geneResult.Gene.GeneId); + double[] geneTpm = geneTPMs.get(geneResult.Gene.GeneId); geneResult.setTPM(geneTpm[RAW_TPM], geneTpm[ADJUSTED_TPM]); } } @@ -355,7 +352,7 @@ private void loadGeneExpectedRatesData(final String chrId, final List ge { mCurrentExpRatesData = null; - final List geneSetCountsData = mCache.getGeneExpectedRatesData(chrId, geneIds); + List geneSetCountsData = mCache.getGeneExpectedRatesData(chrId, geneIds); if(geneSetCountsData == null) { @@ -383,7 +380,7 @@ private double[] generateReadCounts(final GeneCollectionSummary geneSummaryData) for(CategoryCountsData tcData : geneSummaryData.TransCategoryCounts) { - final String categoryKey = tcData.combinedKey(); + String categoryKey = tcData.combinedKey(); double fragmentCount = tcData.fragmentCount(); if(fragmentCount > 0) @@ -427,7 +424,7 @@ public static BufferedWriter createWriter(final IsofoxConfig config) try { - final String outputFileName = config.formOutputFile("category_counts.csv"); + String outputFileName = config.formOutputFile("category_counts.csv"); BufferedWriter writer = createBufferedWriter(outputFileName, false); writer.write("GenesId,Category,Count,FitCount"); @@ -458,22 +455,22 @@ private synchronized static void writeCategoryCounts(final BufferedWriter writer { try { - final GcRatioCounts tmp = new GcRatioCounts(); + GcRatioCounts tmp = new GcRatioCounts(); for(int i = 0; i < categories.size(); ++i) { double count = counts[i]; - final String category = categories.get(i); + String category = categories.get(i); writer.write(format("%s,%s,%.0f,%.1f", genesId, category, count, fittedCounts[i])); if(writeGcData) { - final CategoryCountsData catCounts = categoryCountsData.stream() + CategoryCountsData catCounts = categoryCountsData.stream() .filter(x -> x.combinedKey().equals(category)).findFirst().orElse(null); - final double[] gcCounts = catCounts != null ? catCounts.fragmentCountsByGcRatio() : tmp.getCounts(); + double[] gcCounts = catCounts != null ? catCounts.fragmentCountsByGcRatio() : tmp.getCounts(); for(int j = 0; j < gcCounts.length; ++j) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortCompare.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortCompare.java index 10b3721465b..07c409d5368 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortCompare.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortCompare.java @@ -123,7 +123,7 @@ private void analyseCohorts(final String cancerType) final List cohortASampleIndices = Lists.newArrayList(); final List cohortBSampleIndices = Lists.newArrayList(); - for(final String sampleId : mConfig.SampleData.SampleIds) + for(String sampleId : mConfig.SampleData.SampleIds) { if(!cancerType.equals(CANCER_TYPE_ALL) && !cancerType.equals(mConfig.SampleData.SampleCancerType.get(sampleId))) continue; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortDistribution.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortDistribution.java index d333baee591..2bea7e1886e 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortDistribution.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionCohortDistribution.java @@ -137,7 +137,7 @@ private void produceCohortData(int index) final List cancerValues = Lists.newArrayListWithExpectedSize(samples.size()); - for(final String sampleId : samples) + for(String sampleId : samples) { Integer sampleIndex = mSampleIndexMap.get(sampleId); @@ -263,7 +263,7 @@ private void writeCancerValues(final String geneTransId, final String geneTransN mWriter.write(String.format(",%6.3e", medianValue)); - for (int i = 0; i < PERCENTILE_COUNT; ++i) + for(int i = 0; i < PERCENTILE_COUNT; ++i) { double value = percentileValues[i]; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionData.java index 4c589a05430..7dcd0987139 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionData.java @@ -24,12 +24,12 @@ public class ExpressionData public final int SplicedFragments; public final int UnsplicedFragments; - public final double LowMapQualFrags; + public final double MultiMappedFragments; public ExpressionData( final String source, final String geneId, final String geneName, final String transName, double fittedFrags, double rawFrags, int readCount, double tpm, int effectiveLength, - int splicedFragments, int unsplicedFragments, double lowMapQualFrags) + int splicedFragments, int unsplicedFragments, double multiMappedFragments) { Source = source; GeneId = geneId; @@ -38,7 +38,7 @@ public ExpressionData( EffectiveLength = effectiveLength; SplicedFragments = splicedFragments; UnsplicedFragments = unsplicedFragments; - LowMapQualFrags = lowMapQualFrags; + MultiMappedFragments = multiMappedFragments; mFittedFragmentCount = fittedFrags; mRawFragmentCount = rawFrags; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionMatrix.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionMatrix.java index 7ecb3950b8d..3f569b4bb9c 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionMatrix.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/ExpressionMatrix.java @@ -127,7 +127,7 @@ private void processSampleFile(int sampleIndex, final Path filename, final Map loadIsofoxFile(final Path filename) Integer effectiveLengthIndex = fieldsMap.get(FLD_EFFECTIVE_LENGTH); Integer splicedIndex = fieldsMap.get(FLD_SPLICED_FRAGS); Integer unsplicedIndex = fieldsMap.get(FLD_UNSPLICED_FRAGS); - Integer lowQualIndex = fieldsMap.get("LowMapQualFrags"); + Integer lowQualIndex = fieldsMap.get("MultiMappedFragments"); - for(final String data : lines) + for(String data : lines) { ExpressionData expData = mTransScope ? fromIsofoxTranscript( @@ -265,7 +265,7 @@ private Map loadFile(final String sampleId, final String final List lines = Files.readAllLines(Paths.get(filename)); lines.remove(0); - for(final String data : lines) + for(String data : lines) { ExpressionData expData = null; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/GeneratePanelNormalisation.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/GeneratePanelNormalisation.java index ade157adcd2..4b6d7720076 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/GeneratePanelNormalisation.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/expression/cohort/GeneratePanelNormalisation.java @@ -136,7 +136,7 @@ private void processSampleFile(final Path filename, final Map chime { // use the read with the longest soft-clip if there is one Read primaryRead = null; - ClippedSide maxClippedSide = null; + int maxSoftClip = 0; String suppChromosome = ""; int suppPosition = 0; for(Read read : readGroup.reads()) { - ClippedSide clippedSide = clippedSide(read); + int softClip = read.longestSoftClip(); if(primaryRead == null) { primaryRead = read; - maxClippedSide = clippedSide; + maxSoftClip = softClip; } else if(primaryRead.isSupplementaryAlignment() && !read.isSupplementaryAlignment()) { primaryRead = read; - maxClippedSide = clippedSide; + maxSoftClip = softClip; } else { - if(clippedSide.Length > maxClippedSide.Length) + if(softClip > maxSoftClip) { primaryRead = read; - maxClippedSide = clippedSide; + maxSoftClip = softClip; } } if(suppChromosome.isEmpty() && read.hasSuppAlignment()) { - String[] suppDataItems = read.getSuppAlignment().split(CSV_DELIM, -1); - - if(suppDataItems.length > 2) - { - suppChromosome = suppDataItems[0]; - suppPosition = Integer.parseInt(suppDataItems[1]); - } + suppChromosome = read.supplementaryData().Chromosome; + suppPosition = read.supplementaryData().Position; } } - int positionBucket = ChimericPosData.positionBucket(primaryRead.PosStart); - String key = ChimericPosData.key(primaryRead.Chromosome, positionBucket); + int positionBucket = ChimericPosData.positionBucket(primaryRead.alignmentStart()); + String key = ChimericPosData.key(primaryRead.chromosome(), positionBucket); ChimericPosData posData = chimericPosDataMap.get(key); @@ -136,7 +128,7 @@ else if(primaryRead.isSupplementaryAlignment() && !read.isSupplementaryAlignment } posData.addReadCounts(primaryRead); - posData.addRemoteRegion(primaryRead.mateChromosome(), primaryRead.mateStartPosition()); + posData.addRemoteRegion(primaryRead.mateChromosome(), primaryRead.mateAlignmentStart()); if(!suppChromosome.isEmpty()) posData.addRemoteRegion(suppChromosome, suppPosition); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadCache.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadCache.java deleted file mode 100644 index 6f26d05418d..00000000000 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadCache.java +++ /dev/null @@ -1,318 +0,0 @@ -package com.hartwig.hmftools.isofox.fusion; - -import static com.hartwig.hmftools.common.bam.CigarUtils.cigarFromStr; -import static com.hartwig.hmftools.common.utils.file.FileDelimiters.ITEM_DELIM; -import static com.hartwig.hmftools.common.utils.file.FileWriterUtils.closeBufferedWriter; -import static com.hartwig.hmftools.common.utils.file.FileWriterUtils.createBufferedWriter; -import static com.hartwig.hmftools.common.utils.file.FileReaderUtils.createFieldsIndexMap; -import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; -import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; -import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; -import static com.hartwig.hmftools.isofox.common.RegionMatchType.NONE; -import static com.hartwig.hmftools.isofox.results.ResultsWriter.OLD_FILE_DELIM; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; - -import com.hartwig.hmftools.isofox.IsofoxConfig; -import com.hartwig.hmftools.isofox.common.Read; -import com.hartwig.hmftools.isofox.common.RegionMatchType; -import com.hartwig.hmftools.isofox.common.TransExonRef; - -import com.google.common.collect.Lists; - -public class ChimericReadCache -{ - private final IsofoxConfig mConfig; - private final BufferedWriter mReadWriter; - - public ChimericReadCache(final IsofoxConfig config) - { - mConfig = config; - mReadWriter = config.Fusions.WriteChimericReads || config.RunPerfChecks ? initialiseReadWriter() : null; - } - - public void close() - { - closeBufferedWriter(mReadWriter); - } - - private BufferedWriter initialiseReadWriter() - { - try - { - final String outputFileName = mConfig.formOutputFile("chimeric_reads.csv"); - - BufferedWriter writer = createBufferedWriter(outputFileName, false); - writer.write("ReadGroupCount,ReadId,FusionGroup,Chromosome,PosStart,PosEnd,Orientation,Cigar"); - writer.write(",Flags,HasSupplAlign,SuppData,BasesStart,BasesEnd,MateChr,MatePosStart"); - writer.write(",GeneSetStart,GeneSetEnd,GenicStart,GenicEnd,InterGeneSplit"); - writer.write(",MappedCoords,ScRegionsMatchedStart,ScRegionsMatchedEnd"); - writer.write(",TopTransMatch,TransExonData,UpperTopTransMatch,UpperTransExonData"); - writer.newLine(); - return writer; - } - catch (IOException e) - { - ISF_LOGGER.error("failed to write chimeric read data: {}", e.toString()); - return null; - } - } - - public synchronized void writeReadData(final String readId, final List reads, final String groupStatus) - { - if(mReadWriter == null) - return; - - try - { - for(final FusionRead read : reads) - { - mReadWriter.write(String.format("%d,%s,%s,%s,%d,%d,%d,%s", - reads.size(), readId, groupStatus, read.Chromosome, - read.posStart(), read.posEnd(), read.Orientation, read.Cigar)); - - /* - mReadWriter.write(String.format(",%s,%s,%s,%s,%s,%s,%d,%s,%d", - read.isFirstOfPair(), read.HasSuppAlignment, read.isReadReversed(), read.isProperPair(), - read.SuppData != null, read.ReadBases, read.flags(), read.MateChromosome, read.MatePosStart)); - */ - - mReadWriter.write(String.format(",%d,%s,%s,%s,%s,%s,%d", - read.Flags, read.HasSuppAlignment, read.SuppData != null ? read.SuppData.asDelimStr() : "NONE", - read.BoundaryBases[SE_START], read.BoundaryBases[SE_END], read.MateChromosome, read.MatePosStart)); - - mReadWriter.write(String.format(",%d,%d,%s,%s,%s", - read.GeneCollections[SE_START], read.GeneCollections[SE_END], - read.IsGenicRegion[SE_START], read.IsGenicRegion[SE_END], read.HasInterGeneSplit)); - - StringJoiner coordsStr = new StringJoiner(ITEM_DELIM); - - for(int[] coord : read.MappedCoords) - { - coordsStr.add(String.format("%d:%d", coord[SE_START], coord[SE_END])); - } - - mReadWriter.write(String.format(",%s,%d,%d", - coordsStr.toString(), read.SoftClipLengths[SE_START], read.SoftClipLengths[SE_END])); - - // log the transcript exons affected, and the highest matching transcript - StringJoiner transExonData = new StringJoiner(ITEM_DELIM); - RegionMatchType topTransMatchType = read.getRegionMatchType(SE_START); - - if(topTransMatchType != NONE) - { - for(final FusionTransExon transExonRef : read.getTransExonRefs(SE_START)) - { - transExonData.add(String.format("%d:%d", transExonRef.TransId, transExonRef.ExonRank)); - } - } - - StringJoiner upperTransExonData = new StringJoiner(ITEM_DELIM); - RegionMatchType upperTopTransMatchType = NONE; - - if(read.spansGeneCollections() && read.getTransExonRefs(SE_END) != null) - { - upperTopTransMatchType = read.getRegionMatchType(SE_END); - - for(FusionTransExon transExonRef : read.getTransExonRefs(SE_END)) - { - upperTransExonData.add(String.format("%d:%d", transExonRef.TransId, transExonRef.ExonRank)); - } - } - - mReadWriter.write(String.format(",%s,%s,%s,%s", - topTransMatchType, transExonData.toString().isEmpty() ? "NONE" : transExonData.toString(), - upperTopTransMatchType, upperTransExonData.toString().isEmpty() ? "NONE" : upperTransExonData.toString())); - - mReadWriter.newLine(); - } - - } - catch (IOException e) - { - ISF_LOGGER.error("failed to write chimeric read data: {}", e.toString()); - return; - } - } - - public static List loadChimericReads(final String inputFile) - { - final List readGroupList = Lists.newArrayList(); - - // TODO - use FusionRead instead - ISF_LOGGER.error("current unsupported"); - - if(!Files.exists(Paths.get(inputFile))) - { - ISF_LOGGER.error("invalid chimeric reads file: {}", inputFile); - return readGroupList; - } - - try - { - BufferedReader fileReader = new BufferedReader(new FileReader(inputFile)); - - // skip field names - String line = fileReader.readLine(); - - if (line == null) - { - ISF_LOGGER.error("empty chimeric reads file: {}", inputFile); - return readGroupList; - } - - final Map fieldsMap = createFieldsIndexMap(line, OLD_FILE_DELIM); - - int readId = fieldsMap.get("ReadId"); - int fusionGroup = fieldsMap.get("FusionGroup"); - int chr = fieldsMap.get("Chromosome"); - int posStart = fieldsMap.get("PosStart"); - int posEnd = fieldsMap.get("PosEnd"); - int cigar = fieldsMap.get("Cigar"); - int insertSize = fieldsMap.get("InsertSize"); - int flags = fieldsMap.get("Flags"); - int suppAlgn = fieldsMap.get("SuppAlign"); - int bases = fieldsMap.get("Bases"); - int mateChr = fieldsMap.get("MateChr"); - int matePosStart = fieldsMap.get("MatePosStart"); - int geneSetStart = fieldsMap.get("GeneSetStart"); - int geneSetEnd = fieldsMap.get("GeneSetEnd"); - int genicStart = fieldsMap.get("GenicStart"); - int genicEnd = fieldsMap.get("GenicEnd"); - int interGeneSplit = fieldsMap.get("InterGeneSplit"); - int mappedCoords = fieldsMap.get("MappedCoords"); - int scrmStart = fieldsMap.get("ScRegionsMatchedStart"); - int scrmEnd = fieldsMap.get("ScRegionsMatchedEnd"); - int topTransMatch = fieldsMap.get("TopTransMatch"); - int upperTopTransMatch = fieldsMap.get("UpperTopTransMatch"); - int transExonData = fieldsMap.get("TransExonData"); - int upperTransExonData = fieldsMap.get("UpperTransExonData"); - - FusionReadGroup readGroup = null; - - while ((line = fileReader.readLine()) != null) - { - String[] items = line.split(OLD_FILE_DELIM, -1); - - try - { - if(items[fusionGroup].contains("INVALID")) - continue; - - Read read = new Read( - items[readId], - items[chr], - Integer.parseInt(items[posStart]), - Integer.parseInt(items[posEnd]), - items[bases], - cigarFromStr((items[cigar])), - Integer.parseInt(items[insertSize]), - Integer.parseInt(items[flags]), - items[mateChr], - Integer.parseInt(items[matePosStart])); - - String saData = items[suppAlgn]; - - if(!saData.equals("NONE")) - read.setSuppAlignment(saData); - - read.setGeneCollection(SE_START, Integer.parseInt(items[geneSetStart]), Boolean.parseBoolean(items[genicStart])); - read.setGeneCollection(SE_END, Integer.parseInt(items[geneSetEnd]), Boolean.parseBoolean(items[genicEnd])); - - if(Boolean.parseBoolean(items[interGeneSplit])) - read.setHasInterGeneSplit(); - - read.getMappedRegionCoords().clear(); - read.getMappedRegionCoords().addAll(parseMappedCoords(items[mappedCoords])); - - read.getSoftClipRegionsMatched()[SE_START] = Integer.parseInt(items[scrmStart]); - read.getSoftClipRegionsMatched()[SE_END] = Integer.parseInt(items[scrmEnd]); - - RegionMatchType matchType = RegionMatchType.valueOf(items[topTransMatch]); - - if(matchType != NONE) - { - List transExonRefs = parseTransExonRefs(items[transExonData]); - // TODO - set into the map instead - // read.getReadTransExonRefs().put(matchType, transExonRefs); - } - - matchType = RegionMatchType.valueOf(items[upperTopTransMatch]); - - if(matchType != NONE) - { - List transExonRefs = parseTransExonRefs(items[upperTransExonData]); - // read.getReadTransExonRefs(SE_END).put(matchType, transExonRefs); - } - - // reads are written by fragment so all reads will be sequential - if(readGroup == null || !readGroup.ReadId.equals(read.Id)) - { - // readGroup = new FusionReadGroup(read); - } - else - { - // readGroup.Reads.add(read); - - if(readGroup.isComplete()) - readGroupList.add(readGroup); - } - } - catch (Exception e) - { - ISF_LOGGER.error("failed to parse chimeric read data: {}", line); - return Lists.newArrayList(); - } - } - - ISF_LOGGER.info("loaded {} chimeric fragment reads from file({})", readGroupList.size(), inputFile); - } - catch (IOException e) - { - ISF_LOGGER.warn("failed to load chimeric reads file({}): {}", inputFile, e.toString()); - } - - return readGroupList; - } - - private static List parseMappedCoords(final String data) - { - List mappedCoords = Lists.newArrayList(); - - for(String ref : data.split(ITEM_DELIM, -1)) - { - String[] items = ref.split(":"); - if(items.length != 2) - continue; - - mappedCoords.add(new int[] { Integer.parseInt(items[0]), Integer.parseInt(items[1]) }); - } - - return mappedCoords; - } - - private static List parseTransExonRefs(final String data) - { - List transExonRefs = Lists.newArrayList(); - - for(String ref : data.split(ITEM_DELIM, -1)) - { - String[] items = ref.split(":"); - if(items.length != 4) - continue; - - transExonRefs.add(new TransExonRef(items[0], Integer.parseInt(items[1]), items[2], Integer.parseInt(items[3]))); - } - - return transExonRefs; - } - -} diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadGroup.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadGroup.java index 754c816a98a..828a54404b6 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadGroup.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadGroup.java @@ -15,7 +15,7 @@ public ChimericReadGroup(final Read read) { mReads = Lists.newArrayListWithCapacity(2); mReads.add(read); - mComplete = readGroupComplete(mReads); + mComplete = readGroupComplete(); } public ChimericReadGroup(final Read read1, final Read read2) @@ -23,10 +23,10 @@ public ChimericReadGroup(final Read read1, final Read read2) mReads = Lists.newArrayListWithCapacity(2); mReads.add(read1); mReads.add(read2); - mComplete = readGroupComplete(mReads); + mComplete = readGroupComplete(); } - public final String id() { return mReads.get(0).Id; } + public final String id() { return mReads.get(0).id(); } public int size() { return mReads.size(); } @@ -37,7 +37,7 @@ public ChimericReadGroup(final Read read1, final Read read2) public void addRead(final Read read) { mReads.add(read); - mComplete = readGroupComplete(mReads); + mComplete = readGroupComplete(); } public boolean hasSuppAlignment() { return mReads.stream().anyMatch(x -> x.hasSuppAlignment()); } @@ -47,14 +47,14 @@ public String toString() return String.format("%s reads(%d) complete(%s)", id(), mReads.size(), isComplete()); } - public static boolean readGroupComplete(final List reads) + private boolean readGroupComplete() { int suppCount = 0; int nonSuppCount = 0; int expectedSuppCount = 0; int expectedNonSuppCount = 1; - for(Read read : reads) + for(Read read : mReads) { if(read.isReadPaired() && !read.isMateUnmapped()) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTracker.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTracker.java index 2bddf76807f..4b2f7f67c32 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTracker.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTracker.java @@ -14,6 +14,7 @@ import static com.hartwig.hmftools.isofox.IsofoxFunction.ALT_SPLICE_JUNCTIONS; import static com.hartwig.hmftools.isofox.IsofoxFunction.FUSIONS; import static com.hartwig.hmftools.isofox.common.FragmentType.CHIMERIC; +import static com.hartwig.hmftools.isofox.common.ReadUtils.clippedSide; import static com.hartwig.hmftools.isofox.fusion.ChimericPosData.addChimericPosData; import static com.hartwig.hmftools.isofox.fusion.ChimericUtils.findSplitReadJunction; import static com.hartwig.hmftools.isofox.fusion.ChimericUtils.isInversion; @@ -21,7 +22,6 @@ import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MIN_SOFT_CLIP_BASE_LENGTH; import static com.hartwig.hmftools.isofox.fusion.FusionRead.convertReads; import static com.hartwig.hmftools.isofox.results.ResultsWriter.writeChimericPositionData; -import static com.hartwig.hmftools.isofox.results.ResultsWriter.writeChimericReadData; import java.io.BufferedWriter; import java.util.List; @@ -53,7 +53,6 @@ public class ChimericReadTracker private GeneCollection mGeneCollection; // the current collection being processed private final Map mChimericReadMap; private final Map mFusionReadGroupMap; - private final List mLocalCompleteGroups; // 2-read same-gene-collection groups with a split junction // junction position from fusion junction candidate reads are cached to identify candidate realignable reads private JunctionRacFragments mJunctionRacGroups; @@ -75,7 +74,6 @@ public class ChimericReadTracker private final Map> mPreviousPostGeneReadMap; private final ChimericStats mChimericStats; - private BufferedWriter mChimericReadWriter; private BufferedWriter mChimericPosDataWriter; private final Map mChimericPosDataMap; @@ -93,7 +91,6 @@ public ChimericReadTracker(final IsofoxConfig config) mFusionReadGroupMap = Maps.newHashMap(); mJunctionRacGroups = null; mLocalChimericReads = Lists.newArrayList(); - mLocalCompleteGroups = Lists.newArrayList(); mCandidateRealignedGroups = Lists.newArrayList(); mPostGeneReadMap = Maps.newHashMap(); mPreviousPostGeneReadMap = Maps.newHashMap(); @@ -101,7 +98,6 @@ public ChimericReadTracker(final IsofoxConfig config) mHardFilteredReadIds = Maps.newHashMap(); mGeneCollection = null; mKnownSpliteSites = null; - mChimericReadWriter = null; mChimericPosDataWriter = null; mChimericPosDataMap = Maps.newHashMap(); } @@ -125,15 +121,14 @@ public JunctionRacFragments extractJunctionRacFragments() public ChimericStats getStats() { return mChimericStats; } public Map> getHardFilteredReadIds() { return mHardFilteredReadIds; } - public void setChimericReadWriter(final BufferedWriter writer) { mChimericReadWriter = writer; } public void setChimericPosDataWriter(final BufferedWriter writer) { mChimericPosDataWriter = writer; } - public boolean isChimeric(final Read read1, final Read read2, boolean isDuplicate, boolean isMultiMapped) + public boolean isChimeric(final Read read1, final Read read2, boolean isMultiMapped) { if(read1.isChimeric() || read2.isChimeric() || !read1.withinGeneCollection() || !read2.withinGeneCollection()) return true; - if(!isDuplicate && !isMultiMapped && enabled() && (read1.containsSplit() || read2.containsSplit())) + if(!isMultiMapped && enabled() && (read1.containsSplit() || read2.containsSplit())) { return setHasMultipleKnownSpliceGenes(Lists.newArrayList(read1, read2), mKnownPairGeneIds); } @@ -152,10 +147,10 @@ public void registerKnownFusionPairs(final EnsemblDataCache geneTransCache) if(typeData == null) continue; - for(final KnownFusionData knownData : typeData) + for(KnownFusionData knownData : typeData) { - final GeneData upGene = !knownData.FiveGene.isEmpty() ? geneTransCache.getGeneDataByName(knownData.FiveGene) : null; - final GeneData downGene = !knownData.ThreeGene.isEmpty() ? geneTransCache.getGeneDataByName(knownData.ThreeGene) : null; + GeneData upGene = !knownData.FiveGene.isEmpty() ? geneTransCache.getGeneDataByName(knownData.FiveGene) : null; + GeneData downGene = !knownData.ThreeGene.isEmpty() ? geneTransCache.getGeneDataByName(knownData.ThreeGene) : null; if(type == KNOWN_PAIR && upGene != null && downGene != null) { @@ -196,7 +191,6 @@ private void clear(boolean full) mChimericReadMap.clear(); mFusionReadGroupMap.clear(); - mLocalCompleteGroups.clear(); mCandidateRealignedGroups.clear(); mChimericStats.clear(); mLocalChimericReads.clear(); @@ -214,7 +208,7 @@ private void clear(boolean full) public void addRealignmentCandidates(final Read read1, final Read read2) { - if(read1.isDuplicate() || read2.isDuplicate()) // group complete so drop these + if(read1.isDuplicate() || read2.isDuplicate()) return; mCandidateRealignedGroups.add(new ChimericReadGroup(read1, read2)); @@ -222,23 +216,20 @@ public void addRealignmentCandidates(final Read read1, final Read read2) public void addChimericReadPair(final Read read1, final Read read2) { - if(inImmuneRegion(read1) || inImmuneRegion(read2)) + if(read1.isDuplicate() || read2.isDuplicate() || inImmuneRegion(read1) || inImmuneRegion(read2)) return; - if(!read1.isDuplicate() && !read2.isDuplicate()) - { - // populate transcript info for intronic reads since it will be used in fusion matching - addIntronicTranscriptData(read1); - addIntronicTranscriptData(read2); - } + // populate transcript info for intronic reads since it will be used in fusion matching + addIntronicTranscriptData(read1); + addIntronicTranscriptData(read2); // add the pair when it's clear there aren't others with the same ID in the map - if(mConfig.RunValidations && mChimericReadMap.containsKey(read1.Id)) + if(mConfig.RunValidations && mChimericReadMap.containsKey(read1.id())) { // shouldn't occur - ISF_LOGGER.error("overriding chimeric read({})", read1.Id); + ISF_LOGGER.error("overriding chimeric read({})", read1.id()); - final ChimericReadGroup existingGroup = mChimericReadMap.get(read1.Id); + ChimericReadGroup existingGroup = mChimericReadMap.get(read1.id()); for(Read read : existingGroup.reads()) { @@ -262,7 +253,7 @@ public void addChimericReadPair(final Read read1, final Read read2) if(!mConfig.Fusions.WriteChimericOnly) { - mChimericReadMap.put(read1.Id, readGroup); + mChimericReadMap.put(read1.id(), readGroup); } } } @@ -273,6 +264,20 @@ private void addIntronicTranscriptData(final Read read) read.addIntronicTranscriptRefs(mGeneCollection.getTranscripts()); } + public void addSupplementaryRead(final Read read) + { + if(inImmuneRegion(read)) + return; + + ChimericReadGroup chimericReads = mChimericReadMap.get(read.id()); + if(chimericReads == null) + mChimericReadMap.put(read.id(), new ChimericReadGroup(read)); + else + chimericReads.addRead(read); + + addIntronicTranscriptData(read); + } + public void postProcessChimericReads(final BaseDepth baseDepth, final FragmentTracker fragmentTracker) { // check any lone reads - this cannot be one of a pair of non-genic reads since they will have already been dismissed @@ -281,18 +286,15 @@ public void postProcessChimericReads(final BaseDepth baseDepth, final FragmentTr { Read read = (Read)object; - if(read.isMateUnmapped() || inImmuneRegion(read) || read.isSecondaryAlignment()) + if(read.isDuplicate() || read.isMateUnmapped() || inImmuneRegion(read)) continue; - if(!read.isDuplicate()) - { - baseDepth.processRead(read.getMappedRegionCoords()); - addIntronicTranscriptData(read); - } + baseDepth.processRead(read.getMappedRegionCoords()); + addIntronicTranscriptData(read); - ChimericReadGroup chimericReads = mChimericReadMap.get(read.Id); - if (chimericReads == null) - mChimericReadMap.put(read.Id, new ChimericReadGroup(read)); + ChimericReadGroup chimericReads = mChimericReadMap.get(read.id()); + if(chimericReads == null) + mChimericReadMap.put(read.id(), new ChimericReadGroup(read)); else chimericReads.addRead(read); } @@ -303,8 +305,8 @@ public void postProcessChimericReads(final BaseDepth baseDepth, final FragmentTr for(ChimericReadGroup readGroup : mChimericReadMap.values()) { // skip reads if all will be processed later or have been already - final List reads = readGroup.reads(); - final String readId = reads.get(0).Id; + List reads = readGroup.reads(); + String readId = reads.get(0).id(); int readCount = reads.size(); boolean readGroupComplete = readGroup.isComplete(); @@ -355,9 +357,6 @@ public void postProcessChimericReads(final BaseDepth baseDepth, final FragmentTr continue; } - if(mChimericReadWriter != null) - writeChimericReadData(mChimericReadWriter, readGroup, baseDepth); - if(mChimericPosDataWriter != null) addChimericPosData(mChimericPosDataMap, readGroup); @@ -390,7 +389,6 @@ public void postProcessChimericReads(final BaseDepth baseDepth, final FragmentTr { // clear other chimeric state except for local junction information mChimericReadMap.clear(); - mLocalCompleteGroups.clear(); mCandidateRealignedGroups.clear(); mChimericStats.clear(); } @@ -400,17 +398,18 @@ private boolean inImmuneRegion(final Read read) { // only skip fragments in immune regions if both junction positions are in one boolean inImmuneRegion = mConfig.Filters.ImmuneGeneRegions.stream() - .anyMatch(x -> x.Chromosome.equals(read.Chromosome) && positionsOverlap(read.PosStart, read.PosEnd, x.start(), x.end())); + .anyMatch(x -> x.Chromosome.equals(read.chromosome()) + && positionsOverlap(read.alignmentStart(), read.alignmentEnd(), x.start(), x.end())); if(inImmuneRegion - && mConfig.Filters.ImmuneGeneRegions.stream().anyMatch(x -> x.containsPosition(read.mateChromosome(), read.mateStartPosition()))) + && mConfig.Filters.ImmuneGeneRegions.stream().anyMatch(x -> x.containsPosition(read.mateChromosome(), read.mateAlignmentStart()))) { return true; } if(read.hasSuppAlignment()) { - SupplementaryReadData suppData = SupplementaryReadData.extractAlignment(read.getSuppAlignment()); + SupplementaryReadData suppData = read.supplementaryData(); if(inImmuneRegion && suppData != null && mConfig.Filters.ImmuneGeneRegions.stream().anyMatch(x -> x.containsPosition(suppData.Chromosome, suppData.Position))) @@ -487,20 +486,20 @@ private boolean skipNonGenicReads(final List reads) // any set of entirely post-gene read(s) will be skipped and then picked up by the next gene collection's processing // otherwise record that they were processed to avoid double-processing them in the next gene collection List postGeneReads = !mGeneCollection.isEndOfChromosome() ? reads.stream() - .filter(x -> x.PosStart > mGeneCollection.regionBounds()[SE_END]) + .filter(x -> x.alignmentStart() > mGeneCollection.regionBounds()[SE_END]) .collect(Collectors.toList()) : Lists.newArrayList(); if(postGeneReads.size() == reads.size()) return true; List preGeneReads = reads.stream() - .filter(x -> x.PosStart < mGeneCollection.regionBounds()[SE_START]) + .filter(x -> x.alignmentStart() < mGeneCollection.regionBounds()[SE_START]) .collect(Collectors.toList()); if(!preGeneReads.isEmpty()) { // remove any previously processed reads - final String readId = preGeneReads.get(0).Id; + String readId = preGeneReads.get(0).id(); List prevPostGeneReads = mPreviousPostGeneReadMap.get(readId); if(prevPostGeneReads != null) @@ -514,7 +513,7 @@ private boolean skipNonGenicReads(final List reads) // cache and stop processing this group if(!postGeneReads.isEmpty()) - mPostGeneReadMap.put(reads.get(0).Id, postGeneReads); + mPostGeneReadMap.put(reads.get(0).id(), postGeneReads); return false; } @@ -529,7 +528,7 @@ private void addRealignCandidates() private void collectCandidateJunctions(final ChimericReadGroup readGroup) { // type 1: split reads - final Read splitRead = readGroup.reads().stream() + Read splitRead = readGroup.reads().stream() .filter(x -> x.containsSplit()) .filter(x -> x.spansGeneCollections() || x.hasInterGeneSplit()) .findFirst().orElse(null); @@ -538,19 +537,23 @@ private void collectCandidateJunctions(final ChimericReadGroup readGroup) { int[] splitJunction = findSplitReadJunction(splitRead); - addJunction(splitRead, SE_START, splitJunction[SE_START], ORIENT_FWD); - addJunction(splitRead, SE_END, splitJunction[SE_END], ORIENT_REV); + if(splitJunction != null) + { + addJunction(splitRead, SE_START, splitJunction[SE_START], ORIENT_FWD); + addJunction(splitRead, SE_END, splitJunction[SE_END], ORIENT_REV); + } + return; } - final int[] junctionPositions = new int[SE_PAIR]; + int[] junctionPositions = new int[SE_PAIR]; // type 2: supplementary with clipping - final Read suppRead = readGroup.reads().stream().filter(x -> x.hasSuppAlignment()).findFirst().orElse(null); + Read suppRead = readGroup.reads().stream().filter(x -> x.hasSuppAlignment()).findFirst().orElse(null); if(suppRead != null) { - ClippedSide scSide = Read.clippedSide(suppRead); + ClippedSide scSide = clippedSide(suppRead); if(scSide != null && scSide.Length >= REALIGN_MIN_SOFT_CLIP_BASE_LENGTH) { @@ -569,7 +572,7 @@ private void collectCandidateJunctions(final ChimericReadGroup readGroup) // select the side with the longest soft-clipping Read read = readGroup.reads().get(0); - ClippedSide scSide = Read.clippedSide(read); + ClippedSide scSide = clippedSide(read); if(scSide != null) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericUtils.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericUtils.java index 190a832bbad..c6b848ae976 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericUtils.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/ChimericUtils.java @@ -6,8 +6,9 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_REV; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; -import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MAX_SOFT_CLIP_BASE_LENGTH; -import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MIN_SOFT_CLIP_BASE_LENGTH; +import static com.hartwig.hmftools.isofox.fusion.FusionUtils.aboveJunctionSoftClipThreshold; + +import static htsjdk.samtools.CigarOperator.N; import java.util.List; @@ -15,7 +16,7 @@ import com.hartwig.hmftools.isofox.common.Read; import com.hartwig.hmftools.isofox.common.TransExonRef; -import htsjdk.samtools.CigarOperator; +import htsjdk.samtools.CigarElement; public final class ChimericUtils { @@ -34,12 +35,12 @@ public static boolean isInversion(final List reads) continue; if(existingChromosome.equals("")) - existingChromosome = read.Chromosome; - else if(!existingChromosome.equals(read.Chromosome)) + existingChromosome = read.chromosome(); + else if(!existingChromosome.equals(read.chromosome())) return false; - int scLeft = read.isSoftClipped(SE_START) ? read.leftClipLength() : 0; - int scRight = read.isSoftClipped(SE_END) ? read.rightClipLength() : 0; + int scLeft = read.isSoftClippedNoRegionMatch(SE_START) ? read.leftClipLength() : 0; + int scRight = read.isSoftClippedNoRegionMatch(SE_END) ? read.rightClipLength() : 0; if(scLeft == 0 && scRight == 0) return false; @@ -60,38 +61,47 @@ public static int[] findSplitReadJunction(final Read read) if(!read.containsSplit()) return null; - int maxSplitLength = read.cigarElements().stream() - .filter(x -> x.getOperator() == CigarOperator.N) - .mapToInt(x -> x.getLength()).max().orElse(0); + int readPosition = read.alignmentStart(); + CigarElement maxSplitElement = null; + int maxSplitPosStart = 0; - List mappedCoords = read.getMappedRegionCoords(); - for(int i = 0; i < mappedCoords.size() - 1; ++i) + for(int i = 0; i < read.cigarElements().size() - 1; ++i) { - final int[] lowerCoords = mappedCoords.get(i); - final int[] upperCoords = mappedCoords.get(i + 1); + CigarElement element = read.cigarElements().get(i); - if(upperCoords[SE_START] - lowerCoords[SE_END] - 1 == maxSplitLength) + if(element.getOperator() == N) { - return new int[] { lowerCoords[SE_END], upperCoords[SE_START] }; + if(maxSplitElement == null || element.getLength() > maxSplitElement.getLength()) + { + maxSplitElement = element; + maxSplitPosStart = readPosition - 1; // last base of the prior aligned section + } } + + if(element.getOperator().consumesReferenceBases()) + readPosition += element.getLength(); } + if(maxSplitElement != null) + return new int[] { maxSplitPosStart, maxSplitPosStart + maxSplitElement.getLength() + 1 }; + + // ISF_LOGGER.error("read({}) has split but cannot find split coords", read); return null; } - public static boolean hasRealignableSoftClip(final Read read, int se, boolean checkMax) + public static boolean hasCandidateJunctionSoftClips(final Read read, int se) { - if(!read.isSoftClipped(se)) + if(!read.isSoftClippedNoRegionMatch(se)) return false; int scLength = se == SE_START ? read.leftClipLength() : read.rightClipLength(); - return (scLength >= REALIGN_MIN_SOFT_CLIP_BASE_LENGTH && (!checkMax || scLength <= REALIGN_MAX_SOFT_CLIP_BASE_LENGTH)); + return aboveJunctionSoftClipThreshold(scLength); } public static boolean isRealignedFragmentCandidate(final Read read) { - return hasRealignableSoftClip(read, SE_START, true) || hasRealignableSoftClip(read, SE_END, true); + return hasCandidateJunctionSoftClips(read, SE_START) || hasCandidateJunctionSoftClips(read, SE_END); } public static boolean setHasMultipleKnownSpliceGenes(final List reads, final List knownPairGeneIds) @@ -118,10 +128,10 @@ public static boolean setHasMultipleKnownSpliceGenes(final List reads, fin break; } - if(hasRealignableSoftClip(read, SE_START, false)) + if(hasCandidateJunctionSoftClips(read, SE_START)) junctionTransRefs[SE_START].addAll(read.getJunctionMatchingTransRefs(read.getCoordsBoundary(SE_START), false)); - if(hasRealignableSoftClip(read, SE_END, false)) + if(hasCandidateJunctionSoftClips(read, SE_END)) junctionTransRefs[SE_END].addAll(read.getJunctionMatchingTransRefs(read.getCoordsBoundary(SE_END), true)); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConfig.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConfig.java index ab00e18f5b4..2798569e2b1 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConfig.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConfig.java @@ -8,8 +8,6 @@ public class FusionConfig { - public final boolean WriteChimericReads; - public final boolean WriteChimericFragments; public final boolean PerformanceStats; public final String CohortFile; public final boolean CacheFragments; @@ -20,8 +18,6 @@ public class FusionConfig public final KnownFusionCache KnownFusions; - private static final String WRITE_CHIMERIC_READS = "write_chimeric_reads"; - private static final String WRITE_CHIMERIC_FRAGS = "write_chimeric_frags"; private static final String MIN_FRAGS_HARD_FILTER = "fusion_min_frags_filter"; private static final String SKIP_NON_GENIC_FUSIONS = "fusion_skip_non_genic"; private static final String WRITE_CHIMERIC_ONLY = "fusion_write_chimeric_only"; @@ -31,8 +27,6 @@ public class FusionConfig public FusionConfig(final ConfigBuilder configBuilder) { - WriteChimericReads = configBuilder.hasFlag(WRITE_CHIMERIC_READS); - WriteChimericFragments = configBuilder.hasFlag(WRITE_CHIMERIC_FRAGS); RunPerfChecks = configBuilder.hasFlag(RUN_FUSION_PERF); SkipNonGenic = configBuilder.hasFlag(SKIP_NON_GENIC_FUSIONS); WriteChimericOnly = configBuilder.hasFlag(WRITE_CHIMERIC_ONLY); @@ -42,16 +36,13 @@ public FusionConfig(final ConfigBuilder configBuilder) KnownFusions = new KnownFusionCache(); KnownFusions.loadFromFile(configBuilder); - - CacheFragments = WriteChimericFragments || WriteChimericReads; + CacheFragments = false; PerformanceStats = true; } public FusionConfig() { - WriteChimericReads = false; - WriteChimericFragments = false; CacheFragments = true; KnownFusions = new KnownFusionCache(); SkipNonGenic = false; @@ -64,8 +55,6 @@ public FusionConfig() public static void registerConfig(final ConfigBuilder configBuilder) { - configBuilder.addFlag(WRITE_CHIMERIC_READS, "Write chimeric read data"); - configBuilder.addFlag(WRITE_CHIMERIC_FRAGS, "Write chimeric fragment data"); configBuilder.addFlag(SKIP_NON_GENIC_FUSIONS, "Skip non-genic fusion fragments"); configBuilder.addFlag(WRITE_CHIMERIC_ONLY, "Write chimeric reads but no other fusion processing"); addKnownFusionFileOption(configBuilder); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConstants.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConstants.java index a1f0076e9ef..d383a235758 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConstants.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionConstants.java @@ -3,16 +3,16 @@ public class FusionConstants { public static final int REALIGN_MIN_SOFT_CLIP_BASE_LENGTH = 3; - public static final int REALIGN_MAX_SOFT_CLIP_BASE_LENGTH = 10; + public static final int SOFT_CLIP_JUNC_BUFFER = 3; // max that a realigned fragment's position can overhang the fusion junction public static final int JUNCTION_BASE_LENGTH = 10; // bases to record from the ref genome around the fusion junction - public static final int SOFT_CLIP_JUNC_BUFFER = 3; // max that a realigned fragment's position can overhang the fusion junction - public static final int DEFAULT_HARD_FILTER_MIN_FRAGS = 2; public static final int HIGH_LOG_COUNT = 10000; public static final int FILTER_COHORT_LIMIT_KNOWN = 5; public static final int FILTER_COHORT_LIMIT_NOT_KNOWN = 2; + + public static final int FILTER_MIN_MAP_QUAL = 10; } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionData.java index c17357e5c6a..841d01c26d7 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionData.java @@ -170,7 +170,7 @@ public double alleleFrequency() } public int totalFragments() { return SplitFrags + RealignedFrags + DiscordantFrags; } - public int supportingFragments() { return SplitFrags + RealignedFrags; } + public int supportingFragments() { return SplitFrags + DiscordantFrags; } public String toString() { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFinder.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFinder.java index 893ceae185f..432322e35c5 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFinder.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFinder.java @@ -2,6 +2,7 @@ import static java.lang.Math.abs; +import static com.hartwig.hmftools.common.bam.SamRecordUtils.readToString; import static com.hartwig.hmftools.common.fusion.FusionCommon.FS_DOWN; import static com.hartwig.hmftools.common.fusion.FusionCommon.FS_UP; import static com.hartwig.hmftools.common.region.BaseRegion.positionWithin; @@ -9,8 +10,10 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.common.sv.StartEndIterator.switchIndex; import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; +import static com.hartwig.hmftools.isofox.WriteType.FUSION_FRAGMENT; import static com.hartwig.hmftools.isofox.common.Read.NO_GENE_ID; import static com.hartwig.hmftools.isofox.common.TransExonRef.hasMatchWithinRange; +import static com.hartwig.hmftools.isofox.fusion.FusionConstants.FILTER_MIN_MAP_QUAL; import static com.hartwig.hmftools.isofox.fusion.FusionConstants.HIGH_LOG_COUNT; import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.DISCORDANT; import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.DISCORDANT_JUNCTION; @@ -19,6 +22,7 @@ import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.REALIGN_CANDIDATE; import static com.hartwig.hmftools.isofox.fusion.FusionJunctionType.KNOWN; import static com.hartwig.hmftools.isofox.fusion.FusionReadGroup.mergeChimericReadMaps; +import static com.hartwig.hmftools.isofox.fusion.FusionUtils.checkFusionPositionAdjustmentsVsCanonicalSpliceSites; import static com.hartwig.hmftools.isofox.fusion.FusionUtils.checkMissingGeneData; import static com.hartwig.hmftools.isofox.fusion.FusionUtils.formChromosomePair; import static com.hartwig.hmftools.isofox.fusion.HardFilteredCache.removePartialGroupsWithHardFilteredMatch; @@ -66,8 +70,8 @@ public class FusionFinder implements Callable private final FusionWriter mFusionWriter; private int mHardFilteredCount; - private int mDuplicateGroupFilteredCount; private int mExcludedFilteredCount; + private final boolean mCacheFragments; private final PerformanceCounter[] mPerfCounters; @@ -97,9 +101,10 @@ public FusionFinder( mFusionWriter = fusionWriter; mHardFilteredCount = 0; - mDuplicateGroupFilteredCount = 0; mExcludedFilteredCount = 0; + mCacheFragments = mConfig.Fusions.CacheFragments || mConfig.WriteTypes.contains(FUSION_FRAGMENT); + if(mConfig.Fusions.RunPerfChecks) { mPerfCounters = new PerformanceCounter[PERF_CREATE_LOCAL + 1]; @@ -123,8 +128,6 @@ public FusionFinder( public final RacFragmentCache racFragmentCache() { return mRacFragmentCache; } public int hardFilteredCount() { return mHardFilteredCount; } - public int duplicateFilteredCount() { return mDuplicateGroupFilteredCount; } - public int excludedReadCount() { return mExcludedFilteredCount; } public void clearState(boolean isFinal) { @@ -151,7 +154,7 @@ public List processNewChimericReadGroups( // identify any read groups with reads spanning into a future gene collection // and fill in any missing gene info for reads (partial or complete) which link to this gene collections List spanningGroups = newReadGroups.values().stream() - .filter(x -> x.Reads.stream().anyMatch(y -> y.GeneCollections[SE_END] == NO_GENE_ID)) + .filter(x -> x.reads().stream().anyMatch(y -> y.GeneCollections[SE_END] == NO_GENE_ID)) .collect(Collectors.toList()); List geneCompletedGroups = reconcileSpanningReadGroups(geneCollection, spanningGroups, baseDepth); @@ -174,7 +177,7 @@ private List reconcileSpanningReadGroups( FusionReadGroup readGroup = mSpanningReadGroups.get(index); boolean missingGeneInfo = false; - for(FusionRead read : readGroup.Reads) + for(FusionRead read : readGroup.reads()) { if(read.GeneCollections[SE_END] != NO_GENE_ID) continue; @@ -270,8 +273,8 @@ public void processInterChromosomalReadGroups(final List readGr if(mHardFilteredCount > 0) { - ISF_LOGGER.info("chr({}) fusion processing complete, filtered(hard={} excluded={} duplicate={})", - mChromosome, mHardFilteredCount, mExcludedFilteredCount, mDuplicateGroupFilteredCount); + ISF_LOGGER.info("chr({}) fusion processing complete, filtered(hard={} excluded={})", + mChromosome, mHardFilteredCount, mExcludedFilteredCount); } } @@ -302,16 +305,7 @@ private void processReadGroups(final List readGroups, boolean i ISF_LOGGER.info("chr({}) processed {} {} chimeric read groups", mChromosome, readGroupCount, scope); } - // exclude any group with a duplicate read now that group is complete (since not all reads are marked as duplicates) - if(readGroup.hasDuplicateRead()) - { - ++mDuplicateGroupFilteredCount; - continue; - } - - List reads = readGroup.Reads; - - if(reads.stream().anyMatch(x -> mConfig.Filters.skipRead(x.MateChromosome, x.MatePosStart))) + if(readGroup.reads().stream().anyMatch(x -> mConfig.Filters.skipRead(x.MateChromosome, x.MatePosStart))) { ++mExcludedFilteredCount; continue; @@ -320,10 +314,7 @@ private void processReadGroups(final List readGroups, boolean i FusionFragment fragment = new FusionFragment(readGroup); if(fragment.type() == FusionFragmentType.UNKNOWN) - { - mFusionWriter.writeReadData(fragment.readId(), reads, "INVALID_FRAG"); continue; - } mAllFragments.add(fragment); } @@ -419,7 +410,7 @@ else if(fragment.type() == REALIGN_CANDIDATE) private FusionReadData findExistingFusion(final FusionFragment fragment) { - final Map fusionsByPosition = mFusionsByLocation.get(formChromosomePair(fragment.chromosomes())); + Map fusionsByPosition = mFusionsByLocation.get(formChromosomePair(fragment.chromosomes())); if(fusionsByPosition == null) return null; @@ -436,8 +427,10 @@ private boolean canCreateDiscordantFusion(final FusionFragment fragment) return true; FusionReadData fusionData = new FusionReadData(0, fragment); + fusionData.setJunctionBases(mConfig.RefGenome); setGeneData(fusionData); + fusionData.setHomologyOffsets(); if(!fusionData.hasViableGenes()) return false; @@ -445,7 +438,7 @@ private boolean canCreateDiscordantFusion(final FusionFragment fragment) if(!mPassingFusions.knownFusionCache().hasKnownFusion(fusionData.getGeneName(FS_UP), fusionData.getGeneName(FS_DOWN))) return false; - return fragment.junctionTypes()[FS_UP] == KNOWN || fragment.junctionTypes()[FS_DOWN] == KNOWN; + return fusionData.junctionTypes()[FS_UP] == KNOWN || fusionData.junctionTypes()[FS_DOWN] == KNOWN; } private FusionReadData createOrUpdateFusion(final FusionFragment fragment) @@ -455,17 +448,18 @@ private FusionReadData createOrUpdateFusion(final FusionFragment fragment) // 1. New fusion with correct splice-junction support - may or may not match a known transcript and exon // 2. Potential discordant or realigned fragment + if(mConfig.LogReadIds.contains(fragment.readId())) + { + ISF_LOGGER.debug("specific fusion fragment: {}", fragment); + } + // fusions will be stored in a map keyed by their location pair (chromosome + geneCollectionId) // and in an additional map of precise positions to avoid mismatches on gene collections FusionReadData existingFusion = findExistingFusion(fragment); if(existingFusion != null) { - existingFusion.addFusionFragment(fragment, mConfig.Fusions.CacheFragments); - - // mark donor-acceptor types whether strands are known or not - fragment.junctionTypes()[SE_START] = existingFusion.getInitialFragment().junctionTypes()[SE_START]; - fragment.junctionTypes()[SE_END] = existingFusion.getInitialFragment().junctionTypes()[SE_END]; + existingFusion.addFusionFragment(fragment, mCacheFragments); return null; } @@ -478,10 +472,12 @@ private FusionReadData createOrUpdateFusion(final FusionFragment fragment) } int fusionId = mFusionWriter.getNextFusionId(); - final FusionReadData fusionData = new FusionReadData(fusionId, fragment); + FusionReadData fusionData = new FusionReadData(fusionId, fragment); fusionData.setJunctionBases(mConfig.RefGenome); + fusionData.checkHomologyPositionAdjustment(); setGeneData(fusionData); + fusionData.setHomologyOffsets(); fusions.add(fusionData); @@ -503,15 +499,16 @@ private void setGeneData(final FusionReadData fusionData) { // get the genes supporting the splice junction in the terms of an SV (ie lower chromosome and lower position first) List[] genesByPosition = new List[] { Lists.newArrayList(), Lists.newArrayList() }; - List[] validTransDataList = new List[] { Lists.newArrayList(), Lists.newArrayList() }; + // use the initial fragment to identify FusionFragment initialFragment = fusionData.getInitialFragment(); List transcriptsCache = Lists.newArrayList(); + List[] transcriptLists = new List[] { Lists.newArrayList(), Lists.newArrayList() }; for(int se = SE_START; se <= SE_END; ++se) { - List transDataList = Lists.newArrayList(); + List transDataList = transcriptLists[se]; Set spliceGeneIds = Sets.newHashSet(); for(FusionTransExon transExonRef : initialFragment.getTransExonRefs()[se]) @@ -531,6 +528,7 @@ private void setGeneData(final FusionReadData fusionData) if(!transDataList.contains(transData)) transDataList.add(transData); + // initially keep all transcript refs until they can be check for splicing matches fusionData.getTransExonRefsByPos(se).add(new TransExonRef( transData.GeneId, transData.TransId, transData.TransName, transExonRef.ExonRank, transData.IsCanonical)); @@ -538,16 +536,26 @@ private void setGeneData(final FusionReadData fusionData) } // purge any invalid transcript-exons and mark the junction as known if applicable + // this is no longer required for the sake of the fusion, but can be left in place for the fragment initialFragment.validateTranscriptExons(transDataList, se); if(!spliceGeneIds.isEmpty()) { genesByPosition[se] = spliceGeneIds.stream().map(x -> mGeneTransCache.getGeneDataById(x)).collect(Collectors.toList()); + } + } + + FusionUtils.checkFusionPositionAdjustmentsVsKnownExons(fusionData, transcriptLists, mConfig.RefGenome); + + List[] validTransDataList = new List[] { Lists.newArrayList(), Lists.newArrayList() }; - final int seIndex = se; - validTransDataList[se] = transDataList.stream() - .filter(x -> initialFragment.getTransExonRefs()[seIndex].stream().anyMatch(y -> x.TransId == y.TransId)) - .collect(Collectors.toList()); + for(int se = SE_START; se <= SE_END; ++se) + { + // cull the full list of transcripts down to those matching the junction + for(TranscriptData transcriptData : transcriptLists[se]) + { + if(fusionData.getTransExonRefsByPos(se).stream().anyMatch(x -> x.TransId == transcriptData.TransId)) + validTransDataList[se].add(transcriptData); } } @@ -587,7 +595,7 @@ private void setGeneData(final FusionReadData fusionData) // organise genes by strand based on the orientations around the splice junction // a positive orientation implies either an upstream +ve strand gene or a downstream -ve strand gene - final byte[] sjOrientations = fusionData.junctionOrientations(); + byte[] sjOrientations = fusionData.junctionOrientations(); boolean foundBothStreams = false; boolean foundOneStream = false; @@ -631,7 +639,7 @@ else if(!foundBothStreams && !foundOneStream && (!upstreamGenes.isEmpty() || !do } } - initialFragment.setJunctionTypes(mConfig.RefGenome, fusionData.getGeneStrands(), fusionData.junctionSpliceBases()); + checkFusionPositionAdjustmentsVsCanonicalSpliceSites(fusionData, mConfig.RefGenome); } private void prioritiseLongestCodingFusionGene(final List geneList, final List transDataList) @@ -719,20 +727,20 @@ private void reconcileFusions() ISF_LOGGER.trace("fusion1({}) homology({}/{}) start(junc={} adj={}) end(junc={} adj={})", fusion1.toString(), fusion1.junctionHomology()[SE_START], fusion1.junctionHomology()[SE_END], - fusion1.junctionBases()[SE_START], fusion1.adjacentJunctionBases()[SE_START], - fusion1.junctionBases()[SE_END], fusion1.adjacentJunctionBases()[SE_END]); + fusion1.junctionRefBases()[SE_START], fusion1.postJunctionRefBases()[SE_START], + fusion1.junctionRefBases()[SE_END], fusion1.postJunctionRefBases()[SE_END]); ISF_LOGGER.trace("fusion2({}) homology({}/{}) start(junc={} adj={}) end(junc={} adj={})", fusion2.toString(), fusion2.junctionHomology()[SE_START], fusion2.junctionHomology()[SE_END], - fusion2.junctionBases()[SE_START], fusion2.adjacentJunctionBases()[SE_START], - fusion2.junctionBases()[SE_END], fusion2.adjacentJunctionBases()[SE_END]); + fusion2.junctionRefBases()[SE_START], fusion2.postJunctionRefBases()[SE_START], + fusion2.junctionRefBases()[SE_END], fusion2.postJunctionRefBases()[SE_END]); } - final FusionReadData fusion1Const = fusion1; + FusionReadData fusion1Const = fusion1; // no need to consider discordant junctions since reconciliation is only done for non-local fusions - if(mConfig.Fusions.CacheFragments) - fusion2.getFragments(MATCHED_JUNCTION).forEach(x -> fusion1Const.addFusionFragment(x, mConfig.Fusions.CacheFragments)); + if(mCacheFragments) + fusion2.getFragments(MATCHED_JUNCTION).forEach(x -> fusion1Const.addFusionFragment(x, true)); else fusion1Const.addFragmentTypeCount(MATCHED_JUNCTION, fusion2.getFragmentTypeCount(MATCHED_JUNCTION)); @@ -766,7 +774,7 @@ private void markRelatedFusions() { for(Map.Entry> entry : mFusionCandidates.entrySet()) { - final List fusions = entry.getValue(); + List fusions = entry.getValue(); if(fusions.size() == 1) continue; @@ -780,8 +788,8 @@ private void markRelatedFusions() boolean isSpliced = fusion1.isKnownSpliced(); boolean isUnspliced = fusion1.isUnspliced(); - final List upRefs1 = fusion1.getTransExonRefsByStream(FS_UP); - final List downRefs1 = fusion1.getTransExonRefsByStream(FS_DOWN); + List upRefs1 = fusion1.getTransExonRefsByStream(FS_UP); + List downRefs1 = fusion1.getTransExonRefsByStream(FS_DOWN); for(int j = i + 1; j < fusions.size() - 1; ++j) { @@ -831,13 +839,13 @@ private void createLocalFusions() { // create fusions from fragments with 1 or both junctions matching known splice sites between genes without supp alignment // and then reassign any other fragments to these new fusions - final List newFusions = Lists.newArrayList(); + List newFusions = Lists.newArrayList(); for(Map.Entry> entry : mDiscordantFragments.entrySet()) { - final List fragments = entry.getValue(); + List fragments = entry.getValue(); - final Set allocatedFragments = Sets.newHashSet(); + Set allocatedFragments = Sets.newHashSet(); for(FusionFragment fragment : fragments) { @@ -895,7 +903,7 @@ private void createLocalFusions() fragment.setType(DISCORDANT); } - fusionData.addFusionFragment(fragment, mConfig.Fusions.CacheFragments); + fusionData.addFusionFragment(fragment, mCacheFragments); allocatedFragments.add(fragment); } } @@ -920,14 +928,14 @@ private void assignDiscordantFragments() // attempt to allocate discordant fragments to fusions for(Map.Entry> entry : mDiscordantFragments.entrySet()) { - final List fusions = mFusionCandidates.get(entry.getKey()); + List fusions = mFusionCandidates.get(entry.getKey()); if(fusions == null) continue; - final List fragments = entry.getValue(); + List fragments = entry.getValue(); - final Set allocatedFragments = Sets.newHashSet(); + Set allocatedFragments = Sets.newHashSet(); for(FusionFragment fragment : fragments) { @@ -949,7 +957,7 @@ private void assignDiscordantFragments() fragment.setType(DISCORDANT); } - fusionData.addFusionFragment(fragment, mConfig.Fusions.CacheFragments); + fusionData.addFusionFragment(fragment, mCacheFragments); allocatedFragments.add(fragment); } } @@ -988,7 +996,7 @@ private void assignRealignCandidateFragments() if(fusionData.canRelignFragmentToJunction(fragment)) { fragment.setType(REALIGNED); - fusionData.addFusionFragment(fragment, mConfig.Fusions.CacheFragments); + fusionData.addFusionFragment(fragment, mCacheFragments); } } } @@ -998,9 +1006,6 @@ private void assignRealignCandidateFragments() private void hardFilterFusions() { - if(mConfig.Fusions.MinHardFilterFrags <= 1) - return; - for(List fusionCandidates : mFusionCandidates.values()) { int index = 0; @@ -1024,18 +1029,57 @@ private void hardFilterFusions() private boolean hardFilterFusion(final FusionReadData fusionData) { + String geneUp = fusionData.getGeneName(FS_UP); + String geneDown = fusionData.getGeneName(FS_DOWN); + + boolean isKnownPair = mPassingFusions.knownFusionCache().hasKnownFusion(geneUp, geneDown); + + boolean isPromiscuousGene = !isKnownPair + && (mPassingFusions.knownFusionCache().hasPromiscuousFiveGene(geneUp) + || mPassingFusions.knownFusionCache().hasPromiscuousThreeGene(geneDown)); + + if(!isKnownPair && !isPromiscuousGene) + { + // apply a map qual filter + List splitFragments = fusionData.getFragments(MATCHED_JUNCTION); + + if(splitFragments != null) + { + boolean hasValidMapQual = false; + boolean hasSupplementaries = false; + + for(FusionFragment fragment : splitFragments) + { + for(FusionRead read : fragment.reads()) + { + if(read.isSupplementaryAlignment()) + { + hasSupplementaries = true; + + if(read.MapQuality >= FILTER_MIN_MAP_QUAL) + { + hasValidMapQual = true; + break; + } + } + } + } + + if(hasSupplementaries && !hasValidMapQual) + return true; + } + } + if(mConfig.Fusions.MinHardFilterFrags <= 1) return false; - if(fusionData.getTotalFragmentTypeCount() >= mConfig.Fusions.MinHardFilterFrags) + if(fusionData.supportingFragmentCount() >= mConfig.Fusions.MinHardFilterFrags) return false; - if(mPassingFusions.knownFusionCache().hasKnownFusion(fusionData.getGeneName(FS_UP), fusionData.getGeneName(FS_DOWN))) + if(isKnownPair) return false; - final FusionJunctionType[] junctionTypes = fusionData.getInitialFragment().junctionTypes(); - - return junctionTypes[SE_START] != KNOWN && junctionTypes[SE_END] != KNOWN; + return fusionData.junctionTypes()[SE_START] != KNOWN && fusionData.junctionTypes()[SE_END] != KNOWN; } private void checkLocalDuplicates() @@ -1100,7 +1144,7 @@ private void writeData() mFusionWriter.writeFusionData(allFusions, passingFusions, mFusionCandidates); } - if(!mDiscordantFragments.isEmpty() && (mConfig.Fusions.WriteChimericReads || mConfig.Fusions.WriteChimericFragments)) + if(!mDiscordantFragments.isEmpty() && mConfig.WriteTypes.contains(FUSION_FRAGMENT)) { // assigned fragments have been purged List unusedFragments = Lists.newArrayList(); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragment.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragment.java index 7ac8f760c63..ccf53b9e62b 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragment.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragment.java @@ -1,5 +1,6 @@ package com.hartwig.hmftools.isofox.fusion; +import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_PAIR; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; @@ -36,7 +37,6 @@ public class FusionFragment private final byte[] mOrientations; private final int[] mJunctionPositions; // fusion junction if exists private final byte[] mJunctionOrientations; // orientation at junction if exists - private final FusionJunctionType[] mJunctionTypes; private FusionFragmentType mType; private final String[] mLocationIds; // used to group proximate fragments and fusions @@ -54,7 +54,6 @@ public FusionFragment(final FusionReadGroup readGroup) mJunctionOrientations = new byte[] {0, 0}; mOrientations = new byte[] {0, 0}; mRegionMatchTypes = new RegionMatchType[] { RegionMatchType.NONE, RegionMatchType.NONE }; - mJunctionTypes = new FusionJunctionType[] { FusionJunctionType.UNKNOWN, FusionJunctionType.UNKNOWN }; mLocationIds = new String[] {"", ""}; mTransExonRefs = new List[SE_PAIR]; @@ -77,7 +76,7 @@ public FusionFragment(final FusionReadGroup readGroup) } public String readId() { return mReadGroup.ReadId; } - public List reads() { return mReadGroup.Reads; } + public List reads() { return mReadGroup.reads(); } public FusionReadGroup readGroup() { return mReadGroup; } public FusionFragmentType type() { return mType; } @@ -98,7 +97,6 @@ public boolean isSingleGeneCollection() public final byte[] junctionOrientations() { return mJunctionOrientations; } public final RegionMatchType[] regionMatchTypes() { return mRegionMatchTypes; } - public final FusionJunctionType[] junctionTypes() { return mJunctionTypes; } public boolean isUnspliced() { return mRegionMatchTypes[SE_START] == INTRON && mRegionMatchTypes[SE_END] == INTRON; } public boolean isSpliced() { return exonBoundary(mRegionMatchTypes[SE_START]) && exonBoundary(mRegionMatchTypes[SE_END]); } @@ -127,21 +125,6 @@ public StructuralVariantType getImpliedSvType() public final List[] getTransExonRefs() { return mTransExonRefs; } - /* - public List getGeneIds(int seIndex) - { - final List geneIds = Lists.newArrayList(); - - for(FusionTransExonRef transExonRef : mTransExonRefs[seIndex]) - { - if(!geneIds.contains(transExonRef.GeneId)) - geneIds.add(transExonRef.GeneId); - } - - return geneIds; - } - */ - public final List readsByLocation(final int se) { if(mType == UNKNOWN) @@ -149,9 +132,9 @@ public final List readsByLocation(final int se) // doesn't take junctions into consideration if(isSingleGeneCollection()) - return mReadGroup.Reads; + return mReadGroup.reads(); - return mReadGroup.Reads.stream() + return mReadGroup.reads().stream() .filter(x -> x.Chromosome.equals(mChromosomes[se])) .filter(x -> x.GeneCollections[SE_START] == mGeneCollections[se] || x.GeneCollections[SE_END] == mGeneCollections[se]) .collect(Collectors.toList()); @@ -162,7 +145,7 @@ private void extractTranscriptExonData() // set transcript & exon info for each junction from each applicable read, taking only the highest matches for(int se = SE_START; se <= SE_END; ++se) { - for(final FusionRead read : mReadGroup.Reads) + for(FusionRead read : mReadGroup.reads()) { if(!isSingleGeneCollection()) { @@ -227,46 +210,36 @@ public void validateTranscriptExons(final List transDataList, in continue; } - mJunctionTypes[seIndex] = KNOWN; ++index; } } - public void setJunctionTypes(final RefGenomeInterface refGenome, final byte[] junctionStrands, final String[] junctionSpliceBases) + public String softClipBases(int junctionPosition, byte junctionOrientation) { - if(refGenome == null) - return; + int seIndex = junctionOrientation == ORIENT_FWD ? SE_END : SE_START; - for(int se = SE_START; se <= SE_END; ++se) + for(FusionRead read : mReadGroup.reads()) { - if(mJunctionTypes[se] == KNOWN) - { + if(read.SoftClipLengths[seIndex] == 0) continue; - } - else if(mJunctionPositions[se] > 0) + + if(read.Positions[seIndex] == junctionPosition) { - String daBases = junctionSpliceBases[se]; + int softClipLength = read.SoftClipLengths[seIndex]; - if(junctionStrands != null) + if(junctionOrientation == ORIENT_FWD) { - boolean isDonor = (mJunctionOrientations[se] == junctionStrands[se]); - - if(isDonor && canonicalDonor(daBases, junctionStrands[se])) - mJunctionTypes[se] = FusionJunctionType.CANONICAL; - else if(!isDonor && canonicalAcceptor(daBases, junctionStrands[se])) - mJunctionTypes[se] = FusionJunctionType.CANONICAL; + int boundaryBaseLength = read.BoundaryBases[seIndex].length(); + return read.BoundaryBases[seIndex].substring(boundaryBaseLength - softClipLength); } else { - // try them both - byte asDonorStrand = mJunctionOrientations[se]; - byte asAcceptorStrand = (byte)(-mJunctionOrientations[se]); - - if(canonicalDonor(daBases, asDonorStrand) || canonicalAcceptor(daBases, asAcceptorStrand)) - mJunctionTypes[se] = FusionJunctionType.CANONICAL; + return read.BoundaryBases[seIndex].substring(0, softClipLength); } } } + + return ""; } public String toString() diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentBuilder.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentBuilder.java index e65f09754cb..c363a83707d 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentBuilder.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentBuilder.java @@ -1,5 +1,6 @@ package com.hartwig.hmftools.isofox.fusion; +import static java.lang.Math.max; import static java.lang.Math.min; import static com.hartwig.hmftools.common.genome.chromosome.HumanChromosome.lowerChromosome; @@ -14,8 +15,8 @@ import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.REALIGN_CANDIDATE; import static com.hartwig.hmftools.isofox.fusion.FusionUtils.findSplitReadJunction; import static com.hartwig.hmftools.isofox.fusion.FusionUtils.formLocation; -import static com.hartwig.hmftools.isofox.fusion.FusionUtils.hasRealignableSoftClip; -import static com.hartwig.hmftools.isofox.fusion.FusionUtils.isRealignedFragmentCandidate; +import static com.hartwig.hmftools.isofox.fusion.FusionUtils.aboveJunctionSoftClipThreshold; +import static com.hartwig.hmftools.isofox.fusion.FusionUtils.hasCandidateJunctionSoftClips; import java.util.List; import java.util.Map; @@ -116,7 +117,7 @@ public static void setFragmentProperties(final FusionFragment fragment) // set single junction info for candidate realignable fragments if(fragment.reads().size() == 2 - && fragment.reads().stream().anyMatch(x -> isRealignedFragmentCandidate(x)) + && fragment.reads().stream().anyMatch(x -> hasCandidateJunctionSoftClips(x)) && fragment.reads().stream().noneMatch(x -> x.spansGeneCollections())) { FusionRead read1 = fragment.reads().get(0); @@ -142,9 +143,27 @@ private static void setSuppAlignJunctionData(final FusionFragment fragment) int posIndex = 0; + // handle the scenario where both primaries have supplementaries, in which case use the longer for the split junction + boolean longestSoftClipFirstInPair = false; + int longestSoftClip = 0; + + for(FusionRead read : fragment.reads()) + { + if(!read.isSupplementaryAlignment() && read.HasSuppAlignment) + { + int maxSoftClip = max(read.SoftClipLengths[SE_START], read.SoftClipLengths[SE_END]); + + if(maxSoftClip > longestSoftClip) + { + longestSoftClip = maxSoftClip; + longestSoftClipFirstInPair = read.isFirstOfPair(); + } + } + } + for(FusionRead read : fragment.reads()) { - if(!read.HasSuppAlignment) + if(!read.HasSuppAlignment || read.isFirstOfPair() != longestSoftClipFirstInPair) continue; chromosomes[posIndex] = read.Chromosome; @@ -194,7 +213,7 @@ private static void setSuppAlignJunctionData(final FusionFragment fragment) private static void setSplitReadJunctionData(final FusionFragment fragment, final FusionRead splitRead) { // set the junction data around the spanning N-split - final int[] splitJunction = findSplitReadJunction(splitRead); + int[] splitJunction = findSplitReadJunction(splitRead); if(splitJunction != null) { @@ -221,7 +240,7 @@ private static void setSingleSoftClipJunctionData(final FusionFragment fragment) { for(int se = SE_START; se <= SE_END; ++se) { - if(!hasRealignableSoftClip(read, se, true)) + if(!aboveJunctionSoftClipThreshold(read, se)) continue; if(read.SoftClipLengths[se] > maxScLength) @@ -248,15 +267,15 @@ private static void setSingleSoftClipJunctionData(final FusionFragment fragment) private static void setNonJunctionData(final FusionFragment fragment) { // set gene collections from the reads without any knowledge of which junctions they may support - final List chrGeneCollections = Lists.newArrayListWithCapacity(2); - final List chromosomes = Lists.newArrayListWithCapacity(2); - final Map positions = Maps.newHashMapWithExpectedSize(2); - final List geneCollections = Lists.newArrayListWithCapacity(2); - final Map reads = Maps.newHashMapWithExpectedSize(2); + List chrGeneCollections = Lists.newArrayListWithCapacity(2); + List chromosomes = Lists.newArrayListWithCapacity(2); + Map positions = Maps.newHashMapWithExpectedSize(2); + List geneCollections = Lists.newArrayListWithCapacity(2); + Map reads = Maps.newHashMapWithExpectedSize(2); - final Map> readGroups = Maps.newHashMapWithExpectedSize(2); + Map> readGroups = Maps.newHashMapWithExpectedSize(2); - for(final FusionRead read : fragment.reads()) + for(FusionRead read : fragment.reads()) { for(int se = SE_START; se <= SE_END; ++se) { @@ -264,7 +283,7 @@ private static void setNonJunctionData(final FusionFragment fragment) if(!read.spansGeneCollections() && se == SE_END) continue; - final String chrGeneId = formLocation(read.Chromosome, read.GeneCollections[se], true); // genic status ignored for group determination + String chrGeneId = formLocation(read.Chromosome, read.GeneCollections[se], true); // genic status ignored for group determination List readGroup = readGroups.get(chrGeneId); @@ -313,7 +332,7 @@ private static void setNonJunctionData(final FusionFragment fragment) for(int se = SE_START; se <= SE_END; ++se) { int index = se == SE_START ? lowerIndex : switchIndex(lowerIndex); - final String chrGeneId = chrGeneCollections.get(index); + String chrGeneId = chrGeneCollections.get(index); fragment.geneCollections()[se] = geneCollections.get(index); fragment.orientations()[se] = reads.get(chrGeneId).Orientation; @@ -331,7 +350,7 @@ private static void setNonJunctionData(final FusionFragment fragment) for(int se = SE_START; se <= SE_END; ++se) { int index = se == SE_START ? lowerIndex : switchIndex(lowerIndex); - final String chrGeneId = chrGeneCollections.get(index); + String chrGeneId = chrGeneCollections.get(index); FusionRead read = reads.get(chrGeneId); int requiredScSide = switchIndex(se); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionRead.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionRead.java index d9bf45bccb2..6b885564698 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionRead.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionRead.java @@ -6,8 +6,10 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_PAIR; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.common.sv.StartEndIterator.switchIndex; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_BOUNDARY; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_INTRON; import static com.hartwig.hmftools.isofox.common.RegionMatchType.NONE; -import static com.hartwig.hmftools.isofox.common.RegionMatchType.matchRank; +import static com.hartwig.hmftools.isofox.common.RegionMatchType.WITHIN_EXON; import static com.hartwig.hmftools.isofox.fusion.FusionTransExon.fromList; import static com.hartwig.hmftools.isofox.fusion.FusionUtils.extractTopTransExonRefs; @@ -18,11 +20,14 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.hartwig.hmftools.common.bam.SupplementaryReadData; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.common.BaseDepth; import com.hartwig.hmftools.isofox.common.Read; import com.hartwig.hmftools.isofox.common.RegionMatchType; import com.hartwig.hmftools.isofox.common.RegionReadData; +import htsjdk.samtools.SAMFlag; + public class FusionRead { // skip read ID since always obtainable from the read-group @@ -32,6 +37,8 @@ public class FusionRead public final String Cigar; public final String MateChromosome; public int MatePosStart; + public final int Flags; + public final short MapQuality; public final int[] SoftClipLengths; public final String[] BoundaryBases; @@ -42,11 +49,9 @@ public class FusionRead public boolean HasInterGeneSplit; public boolean HasSuppAlignment; public final SupplementaryReadData SuppData; - public boolean IsDuplicate; public boolean ContainsSplit; - public int Flags; - public final List MappedCoords; + public final List MappedCoords; // directly related to fusion junctions, may be set on one, both or no sides private final int[] mJunctionPositions; @@ -61,25 +66,24 @@ public class FusionRead public FusionRead(final Read read) { - Chromosome = read.Chromosome; - Positions = new int[] { read.PosStart, read.PosEnd}; + Chromosome = read.chromosome(); + Positions = new int[] { read.alignmentStart(), read.alignmentEnd() }; Orientation = read.orientByte(); MateChromosome = read.mateChromosome(); - MatePosStart = read.mateStartPosition(); - MappedCoords = read.getMappedRegionCoords(false); + MatePosStart = read.mateAlignmentStart(); + MappedCoords = read.mappedCoords().alignmentsWithoutInferred(); Cigar = read.cigarStr(); GeneCollections = read.getGeneCollectons(); IsGenicRegion = read.getIsGenicRegion(); HasInterGeneSplit = read.hasInterGeneSplit(); HasSuppAlignment = read.hasSuppAlignment(); - IsDuplicate = read.isDuplicate(); ContainsSplit = read.containsSplit(); Flags = read.flags(); - - SuppData = read.hasSuppAlignment() ? SupplementaryReadData.extractAlignment(read.getSuppAlignment()) : null; + MapQuality = (short)read.mapQuality(); + SuppData = read.supplementaryData(); SoftClipLengths = new int[] - { read.isSoftClipped(SE_START) ? read.leftClipLength() : 0, read.isSoftClipped(SE_END) ? read.rightClipLength() : 0 }; + { read.isSoftClippedNoRegionMatch(SE_START) ? read.leftClipLength() : 0, read.isSoftClippedNoRegionMatch(SE_END) ? read.rightClipLength() : 0 }; ReadBaseLength = read.baseLength(); int extraBasesBuffer = 5; @@ -110,7 +114,7 @@ public FusionRead(final Read read) public int getCoordsBoundary(int se) { - return se == SE_START ? MappedCoords.get(0)[SE_START] : MappedCoords.get(MappedCoords.size() - 1)[SE_END]; + return se == SE_START ? MappedCoords.get(0).start() : MappedCoords.get(MappedCoords.size() - 1).end(); } public int posStart() { return Positions[SE_START]; } @@ -122,6 +126,11 @@ public boolean spansGeneCollections() public boolean isSoftClipped(int se) { return SoftClipLengths[se] > 0; } public boolean isLongestSoftClip(int se) { return SoftClipLengths[se] > SoftClipLengths[switchIndex(se)]; } + public boolean isSupplementaryAlignment() { return (Flags & SAMFlag.SUPPLEMENTARY_ALIGNMENT.intValue()) != 0; } + public boolean isFirstOfPair() { return (Flags & SAMFlag.FIRST_OF_PAIR.intValue()) != 0; } + public boolean isReadPaired() { return (Flags & SAMFlag.READ_PAIRED.intValue()) != 0; } + public boolean isMateUnmapped() { return (Flags & SAMFlag.MATE_UNMAPPED.intValue()) != 0; } + public final int[] junctionPositions() { return mJunctionPositions; } public final List getTransExonRefs() { return mTransExonRefs; } @@ -146,10 +155,10 @@ private void extractReadTransExonRefs(final Read read) { RegionMatchType matchType = entry.getValue(); - if(matchRank(matchType) < matchRank(mRegionMatchType)) + if(!possiblyExonic(matchType) && possiblyExonic(mRegionMatchType)) continue; - if(matchRank(matchType) > matchRank(mRegionMatchType)) + if(possiblyExonic(matchType) && !possiblyExonic(mRegionMatchType)) { mRegionMatchType = matchType; mTransExonRefs.clear(); @@ -165,6 +174,11 @@ else if(!read.getReadTransExonRefs().isEmpty()) } } + private static boolean possiblyExonic(final RegionMatchType matchType) + { + return matchType == EXON_BOUNDARY || matchType == EXON_INTRON || matchType == WITHIN_EXON; + } + public void setUpperTransExonRefs(final List transExonRefs, final RegionMatchType matchType) { if(mUpperTransExonRefs == null) @@ -194,10 +208,10 @@ public void setReadJunctionDepth(final BaseDepth baseDepth) if(mBoundaryDepth == null) mBoundaryDepth = Maps.newHashMap(); - for(final int[] mappedCoords : MappedCoords) + for(BaseRegion mappedCoords : MappedCoords) { - mBoundaryDepth.put(mappedCoords[SE_START], baseDepth.depthAtBase(mappedCoords[SE_START])); - mBoundaryDepth.put(mappedCoords[SE_END], baseDepth.depthAtBase(mappedCoords[SE_END])); + mBoundaryDepth.put(mappedCoords.start(), baseDepth.depthAtBase(mappedCoords.start())); + mBoundaryDepth.put(mappedCoords.end(), baseDepth.depthAtBase(mappedCoords.end())); } } @@ -220,10 +234,10 @@ public static List convertReads(final List reads) public String toString() { - return String.format("range(%s: %d -> %d) cigar(%s) junc(%d - %d) gc(%d - %d) sup=%s igs=%s", - Chromosome, Positions[SE_START], Positions[SE_END], Cigar, + return String.format("range(%s: %d -> %d) cigar(%s) flags(%d) junc(%d - %d) gc(%d - %d) sup(%s hasData=%s) igs=%s", + Chromosome, Positions[SE_START], Positions[SE_END], Cigar, Flags, mJunctionPositions != null ? mJunctionPositions[SE_START] : 0, mJunctionPositions != null ? mJunctionPositions[SE_END] : 0, - GeneCollections[SE_START], GeneCollections[SE_END], HasSuppAlignment, HasInterGeneSplit); + GeneCollections[SE_START], GeneCollections[SE_END], isSupplementaryAlignment(), HasSuppAlignment, HasInterGeneSplit); } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadData.java index 8daed609b5d..c0308819b39 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadData.java @@ -18,7 +18,6 @@ import static com.hartwig.hmftools.isofox.common.RegionMatchType.INTRON; import static com.hartwig.hmftools.isofox.common.CommonUtils.impliedSvType; import static com.hartwig.hmftools.isofox.fusion.FusionConstants.JUNCTION_BASE_LENGTH; -import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MAX_SOFT_CLIP_BASE_LENGTH; import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MIN_SOFT_CLIP_BASE_LENGTH; import static com.hartwig.hmftools.isofox.fusion.FusionConstants.SOFT_CLIP_JUNC_BUFFER; import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.DISCORDANT_JUNCTION; @@ -35,10 +34,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; +import com.hartwig.hmftools.common.codon.Nucleotides; import com.hartwig.hmftools.common.gene.GeneData; -import com.hartwig.hmftools.common.gene.TranscriptData; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeInterface; import com.hartwig.hmftools.common.sv.StructuralVariantType; import com.hartwig.hmftools.isofox.common.TransExonRef; @@ -61,9 +59,13 @@ public class FusionReadData private final String[] mChromosomes; private final int[] mJunctionPositions; private final byte[] mJunctionOrientations; - private final String[] mJunctionBases; // the 10 bases leading up to the junction - private final String[] mAdjacentJunctionBases; // the 10 bases continuing on from the junction + private final String[] mJunctionSoftClipBases; // the 10 bases of soft-clip past a split-read junction if present + private final String[] mJunctionRefBases; // the 10 bases leading up to the junction + private final String[] mPostJunctionRefBases; // the 10 bases continuing on from the junction private final String[] mJunctionSpliceBases; // the 2 donor/acceptor bases + private int mSplitJunctionOverlap; + private boolean mSplitJunctionOverlapApplied; + private final FusionJunctionType[] mJunctionTypes; private final List[] mTransExonRefs; private final int[] mReadDepth; @@ -83,9 +85,13 @@ public FusionReadData(int id, final FusionFragment fragment) mJunctionPositions = new int[] { fragment.junctionPositions()[SE_START], fragment.junctionPositions()[SE_END] }; mJunctionOrientations = new byte[]{ fragment.junctionOrientations()[SE_START], fragment.junctionOrientations()[SE_END] }; - mJunctionBases = new String[] {"", ""}; + mJunctionRefBases = new String[] {"", ""}; + mPostJunctionRefBases = new String[] {"", ""}; + mJunctionSoftClipBases = new String[] {"", ""}; mJunctionSpliceBases = new String[] {"", ""}; - mAdjacentJunctionBases = new String[] {"", ""}; + mSplitJunctionOverlap = 0; + mSplitJunctionOverlapApplied = false; + mJunctionTypes = new FusionJunctionType[] { FusionJunctionType.UNKNOWN, FusionJunctionType.UNKNOWN }; mFragments = null; mFragmentCounts = Maps.newHashMap(); @@ -131,11 +137,14 @@ public FusionReadData(int id, final FusionFragment fragment) public final String[] chromosomes() { return mChromosomes; } public final int[] junctionPositions() { return mJunctionPositions; } public final byte[] junctionOrientations() { return mJunctionOrientations; } - public final String[] junctionBases() { return mJunctionBases; } - public final String[] adjacentJunctionBases() { return mAdjacentJunctionBases; } + public final String[] junctionRefBases() { return mJunctionRefBases; } + public final String[] postJunctionRefBases() { return mPostJunctionRefBases; } public final String[] junctionSpliceBases() { return mJunctionSpliceBases; } public final int[] junctionHomology() { return mJunctionHomology; } - public final int[] streamIndices() { return mStreamIndices; } + + public int splitJunctionOverlap() { return mSplitJunctionOverlap; } + public boolean splitJunctionOverlapApplied() { return mSplitJunctionOverlapApplied; } + public void markSplitJunctionOverlapApplied() { mSplitJunctionOverlapApplied = true; } public boolean hasIncompleteData() { return mIncompleteData; } public void setIncompleteData() { mIncompleteData = true; } @@ -162,7 +171,12 @@ public int getFragmentTypeCount(FusionFragmentType type) return mFragmentCounts.containsKey(type) ? mFragmentCounts.get(type) : 0; } - public int getTotalFragmentTypeCount() { return mFragmentCounts.values().stream().mapToInt(x -> x.intValue()).sum(); } + public int supportingFragmentCount() + { + return mFragmentCounts.entrySet().stream() + .filter(x -> x.getKey().isJunctionType() || x.getKey() == DISCORDANT) + .mapToInt(x -> x.getValue().intValue()).sum(); + } public void addFragmentTypeCount(FusionFragmentType type, int count) { @@ -203,51 +217,111 @@ public void addFusionFragment(final FusionFragment fragment, boolean cacheFragme public boolean isUnspliced() { return getInitialFragment().isUnspliced() && getInitialFragment().type().isJunctionType(); } public boolean hasViableGenes() { return mFusionGenes[FS_UP] != null && mFusionGenes[FS_DOWN] != null; } + public FusionJunctionType[] junctionTypes() { return mJunctionTypes; } public void setJunctionBases(final RefGenomeInterface refGenome) + { + for(int se = SE_START; se <= SE_END; ++se) + { + setJunctionBases(refGenome, se); + } + } + + public void setJunctionBases(final RefGenomeInterface refGenome, int seIndex) { if(!mFragment.type().isJunctionType()) return; + int refBaseLength = JUNCTION_BASE_LENGTH * 2; + try { - for (int se = SE_START; se <= SE_END; ++se) + int junctionPosition = mJunctionPositions[seIndex]; + + String softClipBases = mFragment.type() == MATCHED_JUNCTION ? + mFragment.softClipBases(junctionPosition, junctionOrientations()[seIndex]) : ""; + + if(junctionOrientations()[seIndex] == ORIENT_FWD) { - int junctionBase = mJunctionPositions[se]; + String junctionBases = refGenome.getBaseString( + mChromosomes[seIndex], junctionPosition - refBaseLength + 1, junctionPosition + JUNCTION_BASE_LENGTH); - if (junctionOrientations()[se] == ORIENT_FWD) - { - String junctionBases = refGenome.getBaseString( - mChromosomes[se], junctionBase - JUNCTION_BASE_LENGTH + 1, junctionBase + JUNCTION_BASE_LENGTH); + mJunctionRefBases[seIndex] = junctionBases.substring(0, refBaseLength); + mPostJunctionRefBases[seIndex] = junctionBases.substring(refBaseLength); + mJunctionSpliceBases[seIndex] = mPostJunctionRefBases[seIndex].substring(0, 2); - mJunctionBases[se] = junctionBases.substring(0, JUNCTION_BASE_LENGTH); - mAdjacentJunctionBases[se] = junctionBases.substring(JUNCTION_BASE_LENGTH); - mJunctionSpliceBases[se] = junctionBases.substring(JUNCTION_BASE_LENGTH, JUNCTION_BASE_LENGTH + 2); - } - else - { - String junctionBases = refGenome.getBaseString( - mChromosomes[se], junctionBase - JUNCTION_BASE_LENGTH, junctionBase + JUNCTION_BASE_LENGTH - 1); + mJunctionSoftClipBases[seIndex] = softClipBases.length() > JUNCTION_BASE_LENGTH ? + softClipBases.substring(0, JUNCTION_BASE_LENGTH) : softClipBases; + } + else + { + String junctionBases = refGenome.getBaseString( + mChromosomes[seIndex], junctionPosition - JUNCTION_BASE_LENGTH, junctionPosition + refBaseLength - 1); - mJunctionBases[se] = junctionBases.substring(JUNCTION_BASE_LENGTH); - mJunctionSpliceBases[se] = junctionBases.substring(JUNCTION_BASE_LENGTH - 2, JUNCTION_BASE_LENGTH); - mAdjacentJunctionBases[se] = junctionBases.substring(0, JUNCTION_BASE_LENGTH); - } + mJunctionRefBases[seIndex] = junctionBases.substring(JUNCTION_BASE_LENGTH); + mPostJunctionRefBases[seIndex] = junctionBases.substring(0, JUNCTION_BASE_LENGTH); + mJunctionSpliceBases[seIndex] = mPostJunctionRefBases[seIndex].substring(JUNCTION_BASE_LENGTH - 2); + + int softClipLength = softClipBases.length(); + mJunctionSoftClipBases[seIndex] = softClipLength > JUNCTION_BASE_LENGTH ? + softClipBases.substring(softClipLength - JUNCTION_BASE_LENGTH) : softClipBases; } } catch(Exception e) { // junction may be in an invalid region, just ignore these } - - setHomologyOffsets(); } - private void setHomologyOffsets() + public void checkHomologyPositionAdjustment() { - // test moving the junction point back and forth from the current positions + if(!mFragment.type().isJunctionType()) + return; + + // a search for an exact overlap in junction soft-clip bases in the other junction's ref bases to fix alignment inconsistencies + byte otherJuncOrientation = mJunctionOrientations[SE_END]; + boolean sameOrientation = mJunctionOrientations[SE_START] == otherJuncOrientation; - // in the int-pair array the first element is the number of bases the junction could move + String juncSoftClipBases = mJunctionSoftClipBases[SE_START]; + + if(juncSoftClipBases.length() < JUNCTION_BASE_LENGTH) + return; + + String otherJuncRefBases = mJunctionRefBases[SE_END]; + + if(sameOrientation) + { + otherJuncRefBases = Nucleotides.reverseComplementBases(otherJuncRefBases); + otherJuncOrientation *= -1; + } + + int overlapLength; + + if(otherJuncOrientation == ORIENT_FWD) + { + int indexOfSoftClip = otherJuncRefBases.lastIndexOf(juncSoftClipBases); + + if(indexOfSoftClip < 0) + return; + + // where the end junction has +ve orientation, no overlap would mean these soft-clip bases are flush against the end of the ref bases + overlapLength = otherJuncRefBases.length() - (indexOfSoftClip + juncSoftClipBases.length()); + } + else + { + int indexOfSoftClip = otherJuncRefBases.indexOf(juncSoftClipBases); + + if(indexOfSoftClip < 0) + return; + + overlapLength = indexOfSoftClip; + } + + mSplitJunctionOverlap = max(overlapLength, 0); + } + + public void setHomologyOffsets() + { boolean startHasPosOrient = mJunctionOrientations[SE_START] == ORIENT_FWD; for(int se = SE_START; se <= SE_END; ++se) @@ -260,16 +334,16 @@ private void setHomologyOffsets() // for the start, compare the start bases with the other junction's post-junction bases for(int i = 1; i < JUNCTION_BASE_LENGTH; ++i) { - if(mJunctionBases[se].length() < i || mAdjacentJunctionBases[psIndex].length() < i) + if(mJunctionRefBases[se].length() < i || mPostJunctionRefBases[psIndex].length() < i) break; String junctionStr = mJunctionOrientations[se] == ORIENT_FWD ? - mJunctionBases[se].substring(mJunctionBases[se].length() - i) : - mJunctionBases[se].substring(0, i); + mJunctionRefBases[se].substring(mJunctionRefBases[se].length() - i) : + mJunctionRefBases[se].substring(0, i); String adjacentStr = mJunctionOrientations[psIndex] == ORIENT_FWD ? - mAdjacentJunctionBases[psIndex].substring(0, i) : - mAdjacentJunctionBases[psIndex].substring(mAdjacentJunctionBases[psIndex].length() - i); + mPostJunctionRefBases[psIndex].substring(0, i) : + mPostJunctionRefBases[psIndex].substring(mPostJunctionRefBases[psIndex].length() - i); if(i > 1 && mJunctionOrientations[SE_START] == mJunctionOrientations[SE_END]) { @@ -356,19 +430,11 @@ public void setStreamData(final List upstreamGenes, final List getRelatedFusions() { return mRelatedSplicedFusions; } - public void addRelatedFusion(int id, boolean isSpliced) { if(isSpliced) @@ -484,7 +550,7 @@ else if(mJunctionOrientations[se] == -1 && read.getCoordsBoundary(SE_START) < mJ private boolean softClippedReadSupportsJunction(final FusionRead read, int juncSeIndex) { return softClippedReadSupportsJunction( - read, juncSeIndex, mJunctionPositions[juncSeIndex], mJunctionOrientations[juncSeIndex], mJunctionBases); + read, juncSeIndex, mJunctionPositions[juncSeIndex], mJunctionOrientations[juncSeIndex], mJunctionRefBases); } public static boolean softClippedReadSupportsJunction( @@ -506,7 +572,7 @@ public static boolean softClippedReadSupportsJunction( // test that soft-clipped bases match the other junction's bases int scLength = read.SoftClipLengths[SE_END]; - if(scLength < REALIGN_MIN_SOFT_CLIP_BASE_LENGTH || scLength > REALIGN_MAX_SOFT_CLIP_BASE_LENGTH) + if(scLength < REALIGN_MIN_SOFT_CLIP_BASE_LENGTH) return false; if(junctionBases == null) @@ -536,7 +602,7 @@ public static boolean softClippedReadSupportsJunction( int scLength = read.SoftClipLengths[SE_START]; - if(scLength < REALIGN_MIN_SOFT_CLIP_BASE_LENGTH || scLength > REALIGN_MAX_SOFT_CLIP_BASE_LENGTH) + if(scLength < REALIGN_MIN_SOFT_CLIP_BASE_LENGTH) return false; if(junctionBases == null) @@ -554,7 +620,6 @@ public static boolean softClippedReadSupportsJunction( } } - public int[] getReadDepth() { return mReadDepth; } public int[] getMaxSplitLengths() { return mMaxSplitLengths; } public String getGeneName(int stream) @@ -619,7 +684,7 @@ else if(entry.getKey() == REALIGNED) junctionPositions[fs] = mJunctionPositions[mStreamIndices[fs]]; junctionOrientations[fs] = mJunctionOrientations[mStreamIndices[fs]]; - junctionTypes[fs] = sampleFragment.junctionTypes()[mStreamIndices[fs]]; + junctionTypes[fs] = mJunctionTypes[mStreamIndices[fs]]; GeneData geneData = mFusionGenes[fs]; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadGroup.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadGroup.java index 624016637be..e97ea7e397d 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadGroup.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionReadGroup.java @@ -3,45 +3,55 @@ import java.util.List; import java.util.Map; import com.google.common.collect.Lists; +import com.hartwig.hmftools.isofox.common.Read; public class FusionReadGroup { public final String ReadId; - public final List Reads; + + private final List mReads; + private boolean mIsComplete; public FusionReadGroup(final String readId, final List reads) { ReadId = readId; - Reads = Lists.newArrayListWithCapacity(reads.size()); - Reads.addAll(reads); + + mReads = Lists.newArrayListWithCapacity(reads.size()); + mReads.addAll(reads); + mIsComplete = readGroupComplete(); } - public int size() { return Reads.size(); } + public int size() { return mReads.size(); } + + public List reads() { return mReads; } + public void addRead(final FusionRead read) + { + mReads.add(read); + mIsComplete = readGroupComplete(); + } - public boolean isComplete() { return Reads.size() == 3 || (Reads.size() == 2 && !hasSuppAlignment(Reads)); } + public boolean isComplete() { return mIsComplete; } - public boolean hasSuppAlignment() { return hasSuppAlignment(Reads); } + public boolean hasSuppAlignment() { return hasSuppAlignment(mReads); } public static boolean hasSuppAlignment(final List reads) { return reads.stream().anyMatch(x -> x.HasSuppAlignment); } - public boolean hasDuplicateRead() { return Reads.stream().anyMatch(x -> x.IsDuplicate); } - public void merge(final FusionReadGroup other) { - Reads.addAll(other.Reads); + other.reads().forEach(x -> addRead(x)); } public String toString() { - return String.format("%s reads(%d) complete(%s)", ReadId, Reads.size(), isComplete()); + return String.format("%s reads(%d) complete(%s)", ReadId, mReads.size(), mIsComplete); } public String findOtherChromosome(final String chromosome) { - for(FusionRead read : Reads) + for(FusionRead read : mReads) { if(!read.MateChromosome.equals(chromosome)) return read.MateChromosome; @@ -53,8 +63,41 @@ public String findOtherChromosome(final String chromosome) return null; } + private boolean readGroupComplete() + { + int suppCount = 0; + int nonSuppCount = 0; + int expectedSuppCount = 0; + int expectedNonSuppCount = 1; + + for(FusionRead read : mReads) + { + if(read.isReadPaired() && !read.isMateUnmapped()) + { + expectedNonSuppCount = 2; + } + + if(read.isSupplementaryAlignment()) + { + ++suppCount; + } + else + { + ++nonSuppCount; + + if(read.HasSuppAlignment) + { + ++expectedSuppCount; + } + } + } + + return (expectedNonSuppCount == nonSuppCount) && (expectedSuppCount == suppCount); + } + public static void mergeChimericReadMaps( - final Map partialGroups, final List completeGroups, final Map sourceMap) + final Map partialGroups, final List completeGroups, + final Map sourceMap) { // 1. copies complete groups from the source map into complete groups map // 2. checks for a partial match by combining partials and source, and if found removes from partials @@ -62,7 +105,7 @@ public static void mergeChimericReadMaps( // note: source map is logically const for(Map.Entry entry : sourceMap.entrySet()) { - final FusionReadGroup srcReadGroup = entry.getValue(); + FusionReadGroup srcReadGroup = entry.getValue(); if(srcReadGroup.isComplete()) { @@ -71,7 +114,7 @@ public static void mergeChimericReadMaps( else { // look for an existing incomplete group to add these reads to - final String readId = entry.getKey(); + String readId = entry.getKey(); FusionReadGroup existingReadGroup = partialGroups.get(readId); if(existingReadGroup == null) diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionTaskManager.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionTaskManager.java index 7f6e93b7b42..100226c46cd 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionTaskManager.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionTaskManager.java @@ -12,6 +12,7 @@ import com.google.common.collect.Maps; import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; import com.hartwig.hmftools.isofox.IsofoxConfig; +import com.hartwig.hmftools.isofox.WriteType; public class FusionTaskManager { @@ -136,55 +137,9 @@ public void close() mRacFragmentCache.totalGroupCount(), totalHardFiltered, mHardFilteredCache.hardFilteredCount()); // write any unassigned RAC fragments - if(mConfig.Fusions.WriteChimericReads || mConfig.Fusions.WriteChimericFragments) + if(mConfig.WriteTypes.contains(WriteType.FUSION_FRAGMENT)) mFusionWriter.writeUnfusedFragments(mRacFragmentCache.getUnassignedFragments()); - if(mConfig.RunPerfChecks) - { - List incompleteGroups = Lists.newArrayList(); - - for(Map.Entry> chrEntry : mIncompleteReadGroups.entrySet()) - { - String chromosome = chrEntry.getKey(); - - if(mConfig.Filters.excludeChromosome(chromosome)) - continue; - - Map rgMap = chrEntry.getValue(); - for(FusionReadGroup readGroup : rgMap.values()) - { - if(!mConfig.Filters.SpecificChrRegions.Chromosomes.isEmpty()) - { - if(readGroup.Reads.stream().anyMatch(x -> mConfig.Filters.SpecificChrRegions.excludeChromosome(x.MateChromosome))) - continue; - } - - if(!skipMissingReads(readGroup.Reads)) - { - incompleteGroups.add(readGroup); - } - } - } - - mFusionWriter.writeIncompleteGroupReads(incompleteGroups); - } - mFusionWriter.close(); } - - private boolean skipMissingReads(final List reads) - { - for(final FusionRead read : reads) - { - if(read.HasSuppAlignment) - { - if(mConfig.Filters.skipRead(read.SuppData.Chromosome, read.SuppData.Position)) - return true; - - ISF_LOGGER.debug("read({}) missing supp({})", read, read.SuppData); - } - } - - return false; - } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionUtils.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionUtils.java index 2cdf378668a..198a587b00f 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionUtils.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionUtils.java @@ -1,19 +1,26 @@ package com.hartwig.hmftools.isofox.fusion; +import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; +import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; import static com.hartwig.hmftools.common.region.BaseRegion.positionWithin; import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_REV; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; import static com.hartwig.hmftools.common.region.BaseRegion.positionsWithin; +import static com.hartwig.hmftools.common.sv.StartEndIterator.switchIndex; +import static com.hartwig.hmftools.isofox.common.CommonUtils.canonicalAcceptor; +import static com.hartwig.hmftools.isofox.common.CommonUtils.canonicalDonor; import static com.hartwig.hmftools.isofox.common.CommonUtils.deriveCommonRegions; import static com.hartwig.hmftools.isofox.common.RegionMatchType.NONE; import static com.hartwig.hmftools.isofox.common.RegionMatchType.matchRank; -import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MAX_SOFT_CLIP_BASE_LENGTH; import static com.hartwig.hmftools.isofox.fusion.FusionConstants.REALIGN_MIN_SOFT_CLIP_BASE_LENGTH; +import static com.hartwig.hmftools.isofox.fusion.FusionJunctionType.CANONICAL; +import static com.hartwig.hmftools.isofox.fusion.FusionJunctionType.KNOWN; +import static com.hartwig.hmftools.isofox.fusion.FusionJunctionType.UNKNOWN; import static com.hartwig.hmftools.isofox.fusion.FusionTransExon.fromList; import static com.hartwig.hmftools.isofox.fusion.FusionTransExon.mergeUnique; @@ -24,9 +31,14 @@ import com.google.common.collect.Lists; import com.hartwig.hmftools.common.gene.ExonData; import com.hartwig.hmftools.common.gene.TranscriptData; +import com.hartwig.hmftools.common.genome.refgenome.RefGenomeInterface; +import com.hartwig.hmftools.common.region.BaseRegion; +import com.hartwig.hmftools.isofox.common.BaseDepth; import com.hartwig.hmftools.isofox.common.RegionMatchType; import com.hartwig.hmftools.isofox.common.TransExonRef; +import org.jetbrains.annotations.Nullable; + public class FusionUtils { public static String formLocation(final String chromosome, final int geneCollectionId, boolean isGenic) @@ -48,28 +60,33 @@ public static int[] findSplitReadJunction(final FusionRead read) return read.junctionPositions(); } - public static boolean hasRealignableSoftClip(final FusionRead read, int se, boolean checkMax) + public static boolean aboveJunctionSoftClipThreshold(final int softClipLength) + { + return softClipLength >= REALIGN_MIN_SOFT_CLIP_BASE_LENGTH; + } + + public static boolean aboveJunctionSoftClipThreshold(final FusionRead read, int se) { - return (read.SoftClipLengths[se] >= REALIGN_MIN_SOFT_CLIP_BASE_LENGTH - && (!checkMax || read.SoftClipLengths[se] <= REALIGN_MAX_SOFT_CLIP_BASE_LENGTH)); + return read.SoftClipLengths[se] >= REALIGN_MIN_SOFT_CLIP_BASE_LENGTH; } - public static boolean isRealignedFragmentCandidate(final FusionRead read) + public static boolean hasCandidateJunctionSoftClips(final FusionRead read) { - return hasRealignableSoftClip(read, SE_START, true) || hasRealignableSoftClip(read, SE_END, true); + return aboveJunctionSoftClipThreshold(read.SoftClipLengths[SE_START]) + || aboveJunctionSoftClipThreshold(read.SoftClipLengths[SE_END]); } public static void setMaxSplitMappedLength( int seIndex, final List reads, final int[] junctPositions, final byte[] junctOrientations, final int[] maxSplitLengths) { // find the longest section mapped across the junction - final List matchingReads = reads.stream() + List matchingReads = reads.stream() .filter(x -> positionWithin(junctPositions[seIndex], x.posStart(), x.posEnd())).collect(Collectors.toList()); if(matchingReads.isEmpty()) // can occur with the fragments from a fusion merged in due to homology return; - List mappedCoords; + List mappedCoords; if(matchingReads.size() == 1) { @@ -82,21 +99,21 @@ public static void setMaxSplitMappedLength( int mappedBases = 0; - for(int[] coord : mappedCoords) + for(BaseRegion coord : mappedCoords) { if(junctOrientations[seIndex] == ORIENT_REV) { - if(coord[SE_END] < junctPositions[seIndex]) + if(coord.end() < junctPositions[seIndex]) continue; - mappedBases += coord[SE_END] - max(junctPositions[seIndex], coord[SE_START]) + 1; + mappedBases += coord.end() - max(junctPositions[seIndex], coord.start()) + 1; } else { - if(coord[SE_START] > junctPositions[seIndex]) + if(coord.start() > junctPositions[seIndex]) break; - mappedBases += min(junctPositions[seIndex], coord[SE_END]) - coord[SE_START] + 1; + mappedBases += min(junctPositions[seIndex], coord.end()) - coord.start() + 1; } } @@ -144,30 +161,30 @@ public static void checkMissingGeneData(final FusionRead read, final List transExonRefs = Lists.newArrayList(); RegionMatchType topMatchType = NONE; for(TranscriptData transData : transDataList) { - if(!positionsWithin(upperCoords[SE_START], upperCoords[SE_END], transData.TransStart, transData.TransEnd)) + if(!positionsWithin(upperCoords.start(), upperCoords.end(), transData.TransStart, transData.TransEnd)) continue; for(ExonData exonData : transData.exons()) { - if(!positionsOverlap(upperCoords[SE_START], upperCoords[SE_END], exonData.Start, exonData.End)) + if(!positionsOverlap(upperCoords.start(), upperCoords.end(), exonData.Start, exonData.End)) continue; - if(exonData.Start > upperCoords[SE_END]) + if(exonData.Start > upperCoords.end()) break; RegionMatchType matchType; - if(upperCoords[SE_START] == exonData.Start || upperCoords[SE_END] == exonData.End) + if(upperCoords.start() == exonData.Start || upperCoords.end() == exonData.End) { matchType = RegionMatchType.EXON_BOUNDARY; } - else if(positionsWithin(upperCoords[SE_START], upperCoords[SE_END], exonData.Start, exonData.End)) + else if(positionsWithin(upperCoords.start(), upperCoords.end(), exonData.Start, exonData.End)) { matchType = RegionMatchType.WITHIN_EXON; } @@ -193,25 +210,236 @@ else if(positionsWithin(upperCoords[SE_START], upperCoords[SE_END], exonData.Sta read.setUpperTransExonRefs(transExonRefs, topMatchType); } - public static final String SUPP_ALIGNMENT_DELIM = ","; + public static void checkFusionPositionAdjustmentsVsKnownExons( + final FusionReadData fusion, final List[] transcriptLists, final RefGenomeInterface refGenome) + { + int positionBuffer = fusion.splitJunctionOverlap(); + boolean adjustmentsApplied = false; + + int[] requiredPositionAdjusts = new int[] {0, 0}; + + for(int se = SE_START; se <= SE_END; ++se) + { + List transDataList = transcriptLists[se]; + List matchedTransExons = Lists.newArrayList(); + + ExonBoundaryMatch topExonMatch = null; + + // look for an exact exon boundary match using the overlap and record if it would require a position adjustment + int juncPosition = fusion.junctionPositions()[se]; + byte juncOrient = fusion.junctionOrientations()[se]; + + // take any previous adjustment from the other position's known exon match + if(requiredPositionAdjusts[se] != 0) + { + juncPosition -= requiredPositionAdjusts[se] * juncOrient; + } + + for(TranscriptData transData : transDataList) + { + ExonBoundaryMatch exonMatch = findExonBoundaryMatch(transData, juncPosition, juncOrient, positionBuffer); + + if(exonMatch == null) + continue; - public static Integer suppAlignmentPosition(final String suppAlignment) + if(topExonMatch == null || (exonMatch.isExact() && !topExonMatch.isExact())) + { + topExonMatch = exonMatch; + + // only keep transcripts matching the best + matchedTransExons.clear(); + matchedTransExons.add(exonMatch.transExonRef()); + } + else if(exonMatch.isExact() == topExonMatch.isExact()) + { + matchedTransExons.add(exonMatch.transExonRef()); + } + } + + if(!matchedTransExons.isEmpty()) + { + // purge other previously added non-matching transcript references + fusion.getTransExonRefsByPos(se).clear(); + fusion.getTransExonRefsByPos(se).addAll(matchedTransExons); + } + + if(topExonMatch != null) + { + fusion.junctionTypes()[se] = FusionJunctionType.KNOWN; + + if(!adjustmentsApplied) + { + // adjust the positions if required + if(topExonMatch.boundaryPosition() != juncPosition) + { + int positionAdjust = abs(topExonMatch.boundaryPosition() - juncPosition); + + requiredPositionAdjusts[se] = positionAdjust; + + // adjust the other position by the remainder + int otherSe = switchIndex(se); + int otherPositionAdjust = positionBuffer - positionAdjust; + requiredPositionAdjusts[otherSe] = otherPositionAdjust; + } + else + { + // the other position must absorb any position adjustment + int otherSe = switchIndex(se); + requiredPositionAdjusts[otherSe] = positionBuffer; + } + + // cancel any check for position adjustment for the upper / other position + positionBuffer = 0; + adjustmentsApplied = true; + } + } + } + + if(adjustmentsApplied) + { + fusion.markSplitJunctionOverlapApplied(); + + for(int se = SE_START; se <= SE_END; ++se) + { + int positionAdjustment = requiredPositionAdjusts[se]; + + if(positionAdjustment != 0) + { + fusion.junctionPositions()[se] -= positionAdjustment * fusion.junctionOrientations()[se]; + + // reset junction bases now the position has shifted + fusion.setJunctionBases(refGenome, se); + } + } + } + } + + public static void checkFusionPositionAdjustmentsVsCanonicalSpliceSites(final FusionReadData fusion, final RefGenomeInterface refGenome) { - // 21,39794900,-,33M43S,255,0; - if(suppAlignment == null) - return null; + // now fusion genes have been identified, check for canonical splice sites, and apply any remaining position adjustments + checkCanonicalSpliceJunction(fusion); + + if(fusion.splitJunctionOverlap() == 0 || fusion.splitJunctionOverlapApplied()) + return; + + int positionAdjustment = fusion.splitJunctionOverlap(); + + // favour known over canonical over unknown + int[] juncPositions = fusion.junctionPositions(); + byte[] juncOrientations = fusion.junctionOrientations(); + + int juncStartTypeOrdinal = fusion.junctionTypes()[SE_START].ordinal(); + int juncEndTypeOrdinal = fusion.junctionTypes()[SE_END].ordinal(); - final String[] items = suppAlignment.split(SUPP_ALIGNMENT_DELIM); - return items.length >= 5 ? Integer.parseInt(items[1]) : null; + int[] positionsAdjustments = new int[] {0, 0}; + + if(juncStartTypeOrdinal < juncEndTypeOrdinal) + { + // for a DEL this shifts the position up further up since orientation is -ve + positionsAdjustments[SE_END] = positionAdjustment; + } + else if(juncEndTypeOrdinal < juncStartTypeOrdinal) + { + positionsAdjustments[SE_START] = positionAdjustment; + } + else + { + // split the change + int halfOverlap = positionAdjustment / 2; + + positionsAdjustments[SE_START] = (positionAdjustment % 2) == 0 ? halfOverlap : halfOverlap + 1; // round up if an odd length + positionsAdjustments[SE_END] = positionAdjustment - positionsAdjustments[SE_START]; + } + + for(int se = SE_START; se <= SE_END; ++se) + { + if(positionsAdjustments[se] != 0) + { + juncPositions[se] -= positionsAdjustments[se] * juncOrientations[se]; + fusion.setJunctionBases(refGenome, se); + + if(fusion.junctionTypes()[se] != KNOWN) + { + // this can revert an existing canonical junction back to unknown after a position adjustment + fusion.junctionTypes()[se] = matchesCanonicalSpliceJunction( + juncOrientations[se], fusion.junctionSpliceBases()[se], fusion.geneStrandByPosition(se)) ? CANONICAL : UNKNOWN; + } + } + } } - public static String suppAlignmentChromosome(final String suppAlignment) + private record ExonBoundaryMatch(TransExonRef transExonRef, int boundaryPosition, boolean isExact) {} + + private static ExonBoundaryMatch findExonBoundaryMatch( + final TranscriptData transData, final int juncPosition, final byte juncOrientation, final int positionBuffer) { - if(suppAlignment == null) - return null; + int juncPosLower = juncPosition; + int juncPosUpper = juncPosition; + + if(positionBuffer > 0) + { + if(juncOrientation == ORIENT_FWD) + juncPosLower -= positionBuffer; + else + juncPosUpper += positionBuffer; + } + + for(ExonData exon : transData.exons()) + { + int exonPosition = juncOrientation == ORIENT_FWD ? exon.End : exon.Start; - final String[] items = suppAlignment.split(SUPP_ALIGNMENT_DELIM); - return items.length >= 5 ? items[0] : null; + if(positionWithin(exonPosition, juncPosLower, juncPosUpper)) + { + TransExonRef transExonRef = new TransExonRef( + transData.GeneId, transData.TransId, transData.TransName, exon.Rank, transData.IsCanonical); + + boolean isExact = juncPosition == exonPosition; + + return new ExonBoundaryMatch(transExonRef, exonPosition, isExact); + } + } + + return null; } + public static void checkCanonicalSpliceJunction(final FusionReadData fusion) + { + for(int se = SE_START; se <= SE_END; ++se) + { + // now that the strandedness of the fusion has been determined, check for canonical splice sites if not matching known + if(fusion.junctionTypes()[se] == KNOWN) + continue; + + if(matchesCanonicalSpliceJunction( + fusion.junctionOrientations()[se], fusion.junctionSpliceBases()[se], fusion.geneStrandByPosition(se))) + { + fusion.junctionTypes()[se] = CANONICAL; + } + } + } + + public static boolean matchesCanonicalSpliceJunction( + final byte juncOrientation, final String juncSpliceBases, @Nullable final Byte juncStrand) + { + if(juncStrand != null) + { + boolean isDonor = (juncOrientation == juncStrand); + + if(isDonor && canonicalDonor(juncSpliceBases, juncStrand)) + return true; + else if(!isDonor && canonicalAcceptor(juncSpliceBases, juncStrand)) + return true; + } + else + { + // try them both if strand is not known + byte asDonorStrand = juncOrientation; + byte asAcceptorStrand = (byte)(-juncOrientation); + + if(canonicalDonor(juncSpliceBases, asDonorStrand) || canonicalAcceptor(juncSpliceBases, asAcceptorStrand)) + return true; + } + + return false; + } } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionWriter.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionWriter.java index d477f0bb19e..3ae0911fd88 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionWriter.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/FusionWriter.java @@ -1,6 +1,7 @@ package com.hartwig.hmftools.isofox.fusion; -import static com.hartwig.hmftools.common.bam.SupplementaryReadData.fromAlignment; +import static java.lang.String.valueOf; + import static com.hartwig.hmftools.common.rna.RnaFusionFile.PASS_FUSION_FILE_ID; import static com.hartwig.hmftools.common.utils.file.FileDelimiters.TSV_DELIM; import static com.hartwig.hmftools.common.utils.file.FileWriterUtils.closeBufferedWriter; @@ -8,6 +9,7 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; +import static com.hartwig.hmftools.isofox.WriteType.FUSION_FRAGMENT; import static com.hartwig.hmftools.isofox.fusion.FusionReadData.fusionId; import java.io.BufferedWriter; @@ -16,12 +18,9 @@ import java.util.Map; import java.util.StringJoiner; -import com.hartwig.hmftools.common.bam.SupplementaryReadData; import com.hartwig.hmftools.common.rna.RnaFusion; import com.hartwig.hmftools.common.rna.RnaFusionFile; import com.hartwig.hmftools.isofox.IsofoxConfig; -import com.hartwig.hmftools.isofox.common.Read; -import com.hartwig.hmftools.isofox.common.TransExonRef; public class FusionWriter { @@ -29,8 +28,6 @@ public class FusionWriter private BufferedWriter mFusionWriter; private BufferedWriter mPassingFusionWriter; private BufferedWriter mFragmentWriter; - private final ChimericReadCache mChimericReadCache; - private final boolean mWriteReads; private final boolean mWriteFragments; private int mNextFusionId; @@ -40,12 +37,10 @@ public class FusionWriter public FusionWriter(final IsofoxConfig config) { mConfig = config; - mWriteReads = mConfig.Fusions.WriteChimericReads; - mWriteFragments = mConfig.Fusions.WriteChimericFragments; + mWriteFragments = mConfig.WriteTypes.contains(FUSION_FRAGMENT); mFusionWriter = null; mFragmentWriter = null; - mChimericReadCache = new ChimericReadCache(config); mNextFusionId = 0; initialiseFusionWriters(); @@ -59,7 +54,6 @@ public void close() closeBufferedWriter(mFusionWriter); closeBufferedWriter(mPassingFusionWriter); closeBufferedWriter(mFragmentWriter); - mChimericReadCache.close(); } private void initialiseFusionWriters() @@ -104,7 +98,7 @@ public synchronized void writeFusionData( mPassingFusionWriter.newLine(); } - if(mWriteReads || mWriteFragments) + if(mWriteFragments) { for(List fusionCandidate : fusionCandidates.values()) { @@ -114,11 +108,7 @@ public synchronized void writeFusionData( { for(FusionFragment fragment : fragments) { - if(mWriteFragments) - writeFragmentData(fragment, fusionId(fusion.id())); - - if(mWriteReads) - writeReadData(fragment.readId(), fragment.reads(), fusionId(fusion.id())); + writeFragmentData(fragment, fusionId(fusion.id())); } } } @@ -137,7 +127,6 @@ public synchronized void writeUnfusedFragments(final List fragme return; fragments.forEach(x -> writeFragmentData(x, "UNFUSED")); - fragments.forEach(x -> writeReadData(x.readId(), x.reads(), "UNFUSED")); } private void initialiseFragmentWriter() @@ -147,23 +136,27 @@ private void initialiseFragmentWriter() try { - final String outputFileName = mConfig.formOutputFile("fusion_frags.csv"); + final String outputFileName = mConfig.formOutputFile("fusion_fragment.tsv"); mFragmentWriter = createBufferedWriter(outputFileName, false); - mFragmentWriter.write("ReadId,ReadCount,FusionGroup,Type,SameGeneSet,ScCount,HasSupp"); + + StringJoiner sj = new StringJoiner(TSV_DELIM); + sj.add("ReadId").add("ReadCount").add("FusionId").add("FragType").add("SameGeneSet").add("ScCount").add("HasSupp"); + + // mFragmentWriter.write("ReadId,ReadCount,FusionGroup,Type,SameGeneSet,ScCount,HasSupp"); for(int se = SE_START; se <= SE_END; ++se) { - final String prefix = se == SE_START ? "Start" : "End"; - mFragmentWriter.write(",Chr" + prefix); - mFragmentWriter.write(",Orient" + prefix); - mFragmentWriter.write(",JuncPos" + prefix); - mFragmentWriter.write(",JuncOrient" + prefix); - mFragmentWriter.write(",JuncType" + prefix); - mFragmentWriter.write(",GeneSet" + prefix); - mFragmentWriter.write(",Region" + prefix); + String prefix = se == SE_START ? "Start" : "End"; + sj.add("Chr" + prefix); + sj.add("Orient" + prefix); + sj.add("JuncPos" + prefix); + sj.add("JuncOrient" + prefix); + sj.add("GeneSet" + prefix); + sj.add("Region" + prefix); } + mFragmentWriter.write(sj.toString()); mFragmentWriter.newLine(); } catch (IOException e) @@ -173,11 +166,6 @@ private void initialiseFragmentWriter() } } - public void writeIncompleteGroupReads(final List incompleteGroups) - { - incompleteGroups.forEach(x -> mChimericReadCache.writeReadData(x.ReadId, x.Reads, "INCOMPLETE_GROUPS")); - } - public synchronized void writeFragmentData(final FusionFragment fragment, final String fusionId) { if(!mWriteFragments) @@ -185,35 +173,32 @@ public synchronized void writeFragmentData(final FusionFragment fragment, final try { - mFragmentWriter.write(String.format("%s,%d,%s,%s,%s,%d,%s", - fragment.readId(), fragment.reads().size(), fusionId, fragment.type(), - fragment.isSingleGeneCollection(), - fragment.reads().stream().filter(x -> x.SoftClipLengths[SE_START] > 0 || x.SoftClipLengths[SE_END] > 0).count(), - fragment.hasSuppAlignment())); + StringJoiner sj = new StringJoiner(TSV_DELIM); + + sj.add(fragment.readId()); + sj.add(valueOf(fragment.reads().size())); + sj.add(fusionId); + sj.add(valueOf(fragment.type())); + sj.add(valueOf(fragment.isSingleGeneCollection())); + sj.add(valueOf(fragment.reads().stream().filter(x -> x.SoftClipLengths[SE_START] > 0 || x.SoftClipLengths[SE_END] > 0).count())); + sj.add(valueOf(fragment.hasSuppAlignment())); for(int se = SE_START; se <= SE_END; ++se) { - mFragmentWriter.write(String.format(",%s,%d,%d,%d,%s,%d,%s", - fragment.chromosomes()[se], fragment.orientations()[se], - fragment.junctionPositions()[se], fragment.junctionOrientations()[se], fragment.junctionTypes()[se], - fragment.geneCollections()[se], fragment.regionMatchTypes()[se])); + sj.add(fragment.chromosomes()[se]); + sj.add(valueOf(fragment.orientations()[se])); + sj.add(valueOf(fragment.junctionPositions()[se])); + sj.add(valueOf(fragment.junctionOrientations()[se])); + sj.add(valueOf(fragment.geneCollections()[se])); + sj.add(valueOf(fragment.regionMatchTypes()[se])); } + mFragmentWriter.write(sj.toString()); mFragmentWriter.newLine(); } catch (IOException e) { - ISF_LOGGER.error("failed to write chimeric fragment data: {}", e.toString()); + ISF_LOGGER.error("failed to write fusion fragment data: {}", e.toString()); } } - - public synchronized void writeReadData(final String readId, final List reads, final String groupStatus) - { - if(mWriteReads) - { - // not sure if will keep this - mChimericReadCache.writeReadData(readId, reads, groupStatus); - } - } - } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/PassingFusions.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/PassingFusions.java index ffc912d5562..d53f579e34b 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/PassingFusions.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/PassingFusions.java @@ -211,7 +211,7 @@ else if(fusion.JunctionTypes[SE_START] == FusionJunctionType.CANONICAL && fusion return false; } - if(fusion.totalFragments() < requiredFragments) + if(fusion.supportingFragments() < requiredFragments) { fusion.setFilter(FRAGMENT_COUNT); return false; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/SupplementaryJunctionData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/SupplementaryJunctionData.java index dc3bef82dc3..6130faecb47 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/SupplementaryJunctionData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/SupplementaryJunctionData.java @@ -59,17 +59,17 @@ public static SupplementaryJunctionData fromReads(final ChimericReadGroup readGr if(read == null) return null; - SupplementaryReadData suppData = SupplementaryReadData.extractAlignment(read.getSuppAlignment()); + SupplementaryReadData suppData = read.supplementaryData(); if(suppData == null) return null; // find the junction from this read's SC and same for the supp mapping data - ClippedSide scSide = Read.clippedSide(read); + Boolean maxSoftOnLeft = read.longestSoftClipIsLeft(); - SupplementaryJunctionData suppJuncData = new SupplementaryJunctionData(read.Id); + SupplementaryJunctionData suppJuncData = new SupplementaryJunctionData(read.id()); - if(scSide.isLeft()) + if(maxSoftOnLeft != null && maxSoftOnLeft) { suppJuncData.LocalJunctionPos = read.getCoordsBoundary(SE_START); suppJuncData.LocalJunctionOrient = ORIENT_REV; @@ -82,12 +82,12 @@ public static SupplementaryJunctionData fromReads(final ChimericReadGroup readGr suppJuncData.RemoteChromosome = suppData.Chromosome; Cigar remoteCigar = cigarFromStr(suppData.Cigar); - scSide = ClippedSide.fromCigar(remoteCigar, true); + ClippedSide remoteSoftClipSide = ClippedSide.fromCigar(remoteCigar, true); - if(scSide == null) + if(remoteSoftClipSide == null) return null; - if(scSide.isLeft()) + if(remoteSoftClipSide.isLeft()) { suppJuncData.RemoteJunctionPos = suppData.Position; } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCohortData.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCohortData.java index 9d1475792f5..aa8e1eac6a4 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCohortData.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCohortData.java @@ -125,7 +125,7 @@ public static String toCsv(final FusionCohortData fusion) { StringJoiner output = new StringJoiner(CSV_DELIM); - for (int fs = FS_UP; fs <= FS_DOWN; ++fs) + for(int fs = FS_UP; fs <= FS_DOWN; ++fs) { output.add(fusion.GeneIds[fs]); output.add(fusion.GeneNames[fs]); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCollection.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCollection.java index 5e13c7b0428..46355f7aafc 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCollection.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/FusionCollection.java @@ -126,11 +126,11 @@ public void writeCohortFusions() for(Map> chrPairLists : mFusions.values()) { - for (List fusionLists : chrPairLists.values()) + for(List fusionLists : chrPairLists.values()) { - for (FusionCohortData fusion : fusionLists) + for(FusionCohortData fusion : fusionLists) { - if (fusion.sampleCount() < mConfig.Fusions.MinSampleThreshold) + if(fusion.sampleCount() < mConfig.Fusions.MinSampleThreshold) continue; ++cohortFusionCount; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/UnknownSpliceAnalyser.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/UnknownSpliceAnalyser.java index 99ea722982e..28027f8be59 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/UnknownSpliceAnalyser.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/fusion/cohort/UnknownSpliceAnalyser.java @@ -111,7 +111,7 @@ private void loadLineElementsFile(final String filename) Map fieldsIndexMap = createFieldsIndexMap(header, ","); - for(final String line : fileContents) + for(String line : fileContents) { String[] values = line.split(","); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoEpitopeReader.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoEpitopeReader.java index abf5d515622..bacf02fa123 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoEpitopeReader.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoEpitopeReader.java @@ -11,6 +11,7 @@ import static com.hartwig.hmftools.isofox.IsofoxConstants.SINGLE_MAP_QUALITY; import static com.hartwig.hmftools.isofox.common.GeneReadData.createGeneReadData; import static com.hartwig.hmftools.isofox.common.Read.findOverlappingRegions; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.processOverlappingRegions; import static com.hartwig.hmftools.isofox.common.RegionMatchType.validExonMatch; import static com.hartwig.hmftools.isofox.neo.NeoFragmentMatcher.checkBaseCoverage; import static com.hartwig.hmftools.isofox.neo.NeoFragmentMatcher.findFusionSupport; @@ -116,7 +117,7 @@ private boolean filterOnRestrictedGenes(final NeoEpitopeData neData) public void calcFragmentSupport() { - for(final NeoEpitopeData neData : mNeoEpitopes) + for(NeoEpitopeData neData : mNeoEpitopes) { clearCache(); @@ -176,17 +177,17 @@ private void initialiseGeneData(final String geneId) private void processSamRecord(final SAMRecord record) { - final Read read = Read.from(record); + Read read = new Read(record); - read.processOverlappingRegions(findOverlappingRegions(mCurrentGenes.getExonRegions(), read)); + processOverlappingRegions(read, findOverlappingRegions(mCurrentGenes.getExonRegions(), read)); mCurrentGenes.setReadGeneCollections(read, mCurrentGenes.regionBounds()); // only handle complete groups - ChimericReadGroup readGroup = mReadGroups.get(read.Id); + ChimericReadGroup readGroup = mReadGroups.get(read.id()); if(readGroup == null) { - mReadGroups.put(read.Id, new ChimericReadGroup(read)); + mReadGroups.put(read.id(), new ChimericReadGroup(read)); return; } @@ -195,7 +196,7 @@ private void processSamRecord(final SAMRecord record) if(readGroup.isComplete()) { processFragmentReads(readGroup); - mReadGroups.remove(read.Id); + mReadGroups.remove(read.id()); } } @@ -281,12 +282,12 @@ private void processFragmentReads(final ChimericReadGroup readGroup) for(Read read : readGroup.reads()) { - if(!read.Chromosome.equals(mCurrentNeoData.Chromosomes[fs])) + if(!read.chromosome().equals(mCurrentNeoData.Chromosomes[fs])) continue; // check that this read covers some part of the neo section - if(read.getMappedRegionCoords(false).stream() - .noneMatch(x -> positionsOverlap(codingBaseRange[SE_START], codingBaseRange[SE_END], x[SE_START], x[SE_END]))) + if(read.getMappedRegionCoordsWithoutInferred().stream() + .noneMatch(x -> positionsOverlap(codingBaseRange[SE_START], codingBaseRange[SE_END], x.start(), x.end()))) { continue; } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoFragmentMatcher.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoFragmentMatcher.java index 51230ad0246..b0b2924c6ae 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoFragmentMatcher.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/neo/NeoFragmentMatcher.java @@ -16,6 +16,7 @@ import java.util.List; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.common.Read; import com.hartwig.hmftools.isofox.fusion.ChimericReadGroup; @@ -39,8 +40,8 @@ public static NeoFragmentSupport findFusionSupport(final NeoEpitopeData neData, // if this is a single-chromosome fusion, the read may extend into the other stream's bases and so support the fusion // or it may support an un-fused gene - boolean readWithinStream = (junctionSide == SE_START && read.PosStart >= codingBaseRange[SE_START]) - || (junctionSide == SE_END && read.PosEnd <= codingBaseRange[SE_END]); + boolean readWithinStream = (junctionSide == SE_START && read.alignmentStart() >= codingBaseRange[SE_START]) + || (junctionSide == SE_END && read.alignmentEnd() <= codingBaseRange[SE_END]); if(readWithinStream) { @@ -51,8 +52,8 @@ public static NeoFragmentSupport findFusionSupport(final NeoEpitopeData neData, if(overlapBases < MIN_BASE_OVERLAP) return support; - int maxStartPos = max(read.PosStart, codingBaseRange[SE_START]); - int minEndPos = min(read.PosEnd, codingBaseRange[SE_END]); + int maxStartPos = max(read.alignmentStart(), codingBaseRange[SE_START]); + int minEndPos = min(read.alignmentEnd(), codingBaseRange[SE_END]); int matchLevel = compareCodingBases(read, neoCodingBases, neData.CodingBaseCoords[stream], maxStartPos, minEndPos); @@ -64,7 +65,7 @@ public static NeoFragmentSupport findFusionSupport(final NeoEpitopeData neData, // soft-clipped bases from the read which span the fusion junction should match the coding bases on the other stream, // after adjusting for strand/orientation - if(read.isSoftClipped(junctionSide)) + if(read.isSoftClippedNoRegionMatch(junctionSide)) { final String postJuncCodingBases = neData.getFusionSoftClippedBases(stream); @@ -124,10 +125,10 @@ public static NeoFragmentSupport findFusionSupport(final NeoEpitopeData neData, for(int i = 0; i < read.getMappedRegionCoords().size() - 1; ++i) { - final int[] coordLower = read.getMappedRegionCoords().get(i); - final int[] coordUpper = read.getMappedRegionCoords().get(i + 1); + BaseRegion coordLower = read.getMappedRegionCoords().get(i); + BaseRegion coordUpper = read.getMappedRegionCoords().get(i + 1); - if(coordLower[SE_END] == fusionJunction[SE_START] && coordUpper[SE_START] == fusionJunction[SE_END]) + if(coordLower.end() == fusionJunction[SE_START] && coordUpper.start() == fusionJunction[SE_END]) { supportsSplit = true; break; @@ -142,15 +143,15 @@ public static NeoFragmentSupport findFusionSupport(final NeoEpitopeData neData, if(overlapBases < MIN_BASE_OVERLAP) return support; - int maxStartPos = max(read.PosStart, codingBaseRange[SE_START]); - int minEndPos = min(read.PosEnd, codingBaseRange[SE_END]); + int maxStartPos = max(read.alignmentStart(), codingBaseRange[SE_START]); + int minEndPos = min(read.alignmentEnd(), codingBaseRange[SE_END]); int matchLevel = compareCodingBases(read, neoCodingBases, neData.CodingBaseCoords[stream], maxStartPos, minEndPos); if(matchLevel == MISMATCH) return support; - if(positionWithin(neData.Positions[FS_UP], read.PosStart, read.PosEnd)) + if(positionWithin(neData.Positions[FS_UP], read.alignmentStart(), read.alignmentEnd())) { ++support.NovelFragments[matchLevel]; } @@ -166,25 +167,25 @@ public static NeoFragmentSupport findFusionSupport(final NeoEpitopeData neData, return support; } - public static int calcCoordinatesOverlap(final List coords1, final List coords2) + public static int calcCoordinatesOverlap(final List baseRegions, final List coords2) { int overlapBases = 0; - for(int[] coord1 : coords1) + for(BaseRegion region : baseRegions) { for(int[] coord2 : coords2) { - overlapBases += calcBaseOverlap(coord1, coord2); + overlapBases += calcBaseOverlap(region, coord2); } } return overlapBases; } - public static int calcBaseOverlap(final int[] range1, final int[] range2) + public static int calcBaseOverlap(final BaseRegion range1, final int[] range2) { - int maxStart = max(range1[SE_START], range2[SE_START]); - int minEnd = min(range1[SE_END], range2[SE_END]); + int maxStart = max(range1.start(), range2[SE_START]); + int minEnd = min(range1.end(), range2[SE_END]); return maxStart <= minEnd ? minEnd - maxStart + 1 : 0; } @@ -271,30 +272,30 @@ public static int compareCodingBases( int readBaseIndex = 0; String readBases = ""; - if(read.isSoftClipped(SE_START)) + if(read.isSoftClippedNoRegionMatch(SE_START)) readBaseIndex += read.leftClipLength(); - for(int[] mappedCoords : read.getMappedRegionCoords()) + for(BaseRegion mappedCoords : read.getMappedRegionCoords()) { - if(posStart > mappedCoords[SE_END]) + if(posStart > mappedCoords.end()) { - readBaseIndex += mappedCoords[SE_END] - mappedCoords[SE_START] + 1; + readBaseIndex += mappedCoords.end() - mappedCoords.start() + 1; continue; } // will now point at the first base of this next region int basePos = 0; - if(positionWithin(posStart, mappedCoords[SE_START], mappedCoords[SE_END])) + if(positionWithin(posStart, mappedCoords.start(), mappedCoords.end())) { - readBaseIndex += posStart - mappedCoords[SE_START]; + readBaseIndex += posStart - mappedCoords.start(); basePos = posStart; } else { - basePos = mappedCoords[SE_START]; + basePos = mappedCoords.start(); } - for(; basePos <= mappedCoords[SE_END]; ++basePos) + for(; basePos <= mappedCoords.end(); ++basePos) { if(basePos > posEnd || readBaseIndex >= read.baseLength()) break; @@ -310,26 +311,26 @@ public static int compareCodingBases( int neoBaseIndex = 0; String neoBases = ""; - for(int[] mappedCoords : neoCoords) + for(int[] neoCoord : neoCoords) { - if(posStart > mappedCoords[SE_END]) + if(posStart > neoCoord[SE_END]) { - neoBaseIndex += mappedCoords[SE_END] - mappedCoords[SE_START] + 1; + neoBaseIndex += neoCoord[SE_END] - neoCoord[SE_START] + 1; continue; } int basePos = 0; - if(positionWithin(posStart, mappedCoords[SE_START], mappedCoords[SE_END])) + if(positionWithin(posStart, neoCoord[SE_START], neoCoord[SE_END])) { - neoBaseIndex += posStart - mappedCoords[SE_START]; + neoBaseIndex += posStart - neoCoord[SE_START]; basePos = posStart; } else { - basePos = mappedCoords[SE_START]; + basePos = neoCoord[SE_START]; } - for(; basePos <= mappedCoords[SE_END]; ++basePos) + for(; basePos <= neoCoord[SE_END]; ++basePos) { if(basePos > posEnd || neoBaseIndex >= neoCodingBases.length()) break; @@ -398,10 +399,10 @@ public static void checkBaseCoverage(final NeoEpitopeData neData, final Chimeric for(Read read : readGroup.reads()) { - if(!read.Chromosome.equals(chromosome)) + if(!read.chromosome().equals(chromosome)) continue; - if(read.getMappedRegionCoords().stream().anyMatch(x -> positionWithin(refBase, x[SE_START], x[SE_END]))) + if(read.getMappedRegionCoords().stream().anyMatch(x -> positionWithin(refBase, x.start(), x.end()))) { coversBase = true; break; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSjCohortCache.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSjCohortCache.java index f480d75ab43..146c665e759 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSjCohortCache.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSjCohortCache.java @@ -66,7 +66,7 @@ private void loadAltSjCohortFile(final String cohortFile) String[] values = line.split(fileDelim, -1); int sampleCount = Integer.parseInt(values[sampleCountIndex]); - final String asjKey = formKey(values[chrIndex], Integer.parseInt(values[posStartIndex]), Integer.parseInt(values[posEndIndex])); + String asjKey = formKey(values[chrIndex], Integer.parseInt(values[posStartIndex]), Integer.parseInt(values[posEndIndex])); mCohortFrequency.put(asjKey, sampleCount); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSpliceJunctionFinder.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSpliceJunctionFinder.java index f67839158eb..716dc1acb45 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSpliceJunctionFinder.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/AltSpliceJunctionFinder.java @@ -15,7 +15,6 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.isofox.IsofoxConstants.MAX_NOVEL_SJ_DISTANCE; import static com.hartwig.hmftools.isofox.IsofoxFunction.ALT_SPLICE_JUNCTIONS; -import static com.hartwig.hmftools.isofox.common.Read.clippedSide; import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_BOUNDARY; import static com.hartwig.hmftools.common.rna.AltSpliceJunctionContext.EXONIC; import static com.hartwig.hmftools.common.rna.AltSpliceJunctionContext.SPLICE_JUNC; @@ -37,13 +36,14 @@ import java.util.stream.Collectors; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.hartwig.hmftools.common.fusion.KnownFusionData; import com.hartwig.hmftools.common.fusion.KnownFusionType; import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.gene.TranscriptData; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.common.rna.AltSpliceJunctionContext; import com.hartwig.hmftools.common.rna.AltSpliceJunctionType; -import com.hartwig.hmftools.common.bam.ClippedSide; import com.hartwig.hmftools.common.rna.NovelSpliceJunction; import com.hartwig.hmftools.common.rna.NovelSpliceJunctionFile; import com.hartwig.hmftools.isofox.IsofoxConfig; @@ -53,6 +53,7 @@ import com.hartwig.hmftools.isofox.common.Read; import com.hartwig.hmftools.isofox.common.RegionMatchType; import com.hartwig.hmftools.isofox.common.RegionReadData; +import com.hartwig.hmftools.isofox.common.TransExonRef; import com.hartwig.hmftools.isofox.common.TransMatchType; public class AltSpliceJunctionFinder @@ -112,32 +113,29 @@ public void evaluateFragmentReads( genes.stream().mapToInt(x -> x.Gene.GeneStart).min().orElse(0) - MAX_NOVEL_SJ_DISTANCE, genes.stream().mapToInt(x -> x.Gene.GeneStart).max().orElse(0) + MAX_NOVEL_SJ_DISTANCE }; - if(!positionsWithin(read1.PosStart, read1.PosEnd, geneBounds[SE_START], geneBounds[SE_END]) - || !positionsWithin(read2.PosStart, read2.PosEnd, geneBounds[SE_START], geneBounds[SE_END])) + if(!positionsWithin(read1.alignmentStart(), read1.alignmentEnd(), geneBounds[SE_START], geneBounds[SE_END]) + || !positionsWithin(read2.alignmentStart(), read2.alignmentEnd(), geneBounds[SE_START], geneBounds[SE_END])) { return; } // at least one of the reads must fall within a gene final List candidateGenes = genes.stream() - .filter(x -> positionsWithin(read1.PosStart, read1.PosEnd, x.Gene.GeneStart,x.Gene.GeneEnd) - || positionsWithin(read2.PosStart, read2.PosEnd, x.Gene.GeneStart,x.Gene.GeneEnd)) + .filter(x -> positionsWithin(read1.alignmentStart(), read1.alignmentEnd(), x.Gene.GeneStart,x.Gene.GeneEnd) + || positionsWithin(read2.alignmentStart(), read2.alignmentEnd(), x.Gene.GeneStart,x.Gene.GeneEnd)) .collect(Collectors.toList()); if(candidateGenes.isEmpty()) return; - AltSpliceJunction firstAltSJ = null; - if(isCandidateCircular(read1, read2)) { - firstAltSJ = registerAltSpliceJunction(candidateGenes, read1, read2, relatedTransIds); - - if(firstAltSJ == null) - return; + registerAltSpliceJunction(candidateGenes, read1, read2, relatedTransIds); } else { + AltSpliceJunction firstAltSJ = null; + if(AltSpliceJunctionFinder.isCandidate(read1)) { firstAltSJ = registerAltSpliceJunction(candidateGenes, read1, relatedTransIds); @@ -148,7 +146,7 @@ public void evaluateFragmentReads( if(AltSpliceJunctionFinder.isCandidate(read2)) { // avoid double-counting overlapping reads - if(firstAltSJ != null && positionsOverlap(read1.PosStart, read1.PosEnd, read2.PosStart, read2.PosEnd)) + if(firstAltSJ != null && positionsOverlap(read1.alignmentStart(), read1.alignmentEnd(), read2.alignmentStart(), read2.alignmentEnd())) return; secondAltSJ = registerAltSpliceJunction(candidateGenes, read2, relatedTransIds); @@ -169,7 +167,7 @@ private static boolean isCandidate(final Read read) if(read.getTranscriptClassifications().values().contains(TransMatchType.SPLICE_JUNCTION)) return false; - if(read.getMappedRegionCoords().size() == 1) + if(read.mappedCoords().originalAlignmentCount() == 1) return false; return true; @@ -207,17 +205,34 @@ public AltSpliceJunction createFromRead(final Read read, final List rel int[] spliceJunction = new int[SE_PAIR]; // find the novel splice junction, and all associated transcripts - final List mappedCoords = read.getMappedRegionCoords(); + List mappedCoords = read.getMappedRegionCoords(); - if(read.inferredCoordAdded(true)) + if(read.mappedCoords().lowerInferredAlignmentAdded()) { - spliceJunction[SE_START] = mappedCoords.get(1)[SE_END]; - spliceJunction[SE_END] = mappedCoords.get(2)[SE_START]; + spliceJunction[SE_START] = mappedCoords.get(1).end(); + spliceJunction[SE_END] = mappedCoords.get(2).start(); } else { - spliceJunction[SE_START] = mappedCoords.get(0)[SE_END]; - spliceJunction[SE_END] = mappedCoords.get(1)[SE_START]; + // look for consecutive mapped coords which don't match any known splice junction + for(int i = 0; i < mappedCoords.size() - 1; ++i) + { + BaseRegion firstCoords = mappedCoords.get(i); + BaseRegion nextCoords = mappedCoords.get(i + 1); + + int junctionStart = firstCoords.end(); + int junctionEnd = nextCoords.start(); + + if(matchesKnownSpliceSite(read.getMappedRegions(), junctionStart, junctionEnd)) + continue; + + spliceJunction[SE_START] = junctionStart; + spliceJunction[SE_END] = junctionEnd; + break; + } + + if(spliceJunction[SE_START] == 0 || spliceJunction[SE_END] == 0) + return null; } checkJunctionHomology(spliceJunction, read.getMappedRegions()); @@ -231,18 +246,105 @@ public AltSpliceJunction createFromRead(final Read read, final List rel AltSpliceJunctionType sjType = classifySpliceJunction(relatedTransIds, sjStartRegions, sjEndRegions, regionContexts); AltSpliceJunction altSplicJunction = new AltSpliceJunction( - mGenes.chromosome(), spliceJunction, sjType, read.Id, regionContexts, sjStartRegions, sjEndRegions); + mGenes.chromosome(), spliceJunction, sjType, read.id(), regionContexts, sjStartRegions, sjEndRegions); altSplicJunction.setCandidateTranscripts(read.getMappedRegions().keySet().stream().collect(Collectors.toList())); return altSplicJunction; } + private static boolean matchesKnownSpliceSite( + final Map mappedRegions, int donorPosition, int acceptorPosition) + { + List donorRegions = null; + List acceptorRegions = null; + + for(Map.Entry entry : mappedRegions.entrySet()) + { + if(entry.getValue() != EXON_BOUNDARY) + continue; + + if(donorPosition == entry.getKey().end()) + { + if(donorRegions == null) + donorRegions = Lists.newArrayList(); + + donorRegions.add(entry.getKey()); + } + else if(acceptorPosition == entry.getKey().start()) + { + if(acceptorRegions == null) + acceptorRegions = Lists.newArrayList(); + + acceptorRegions.add(entry.getKey()); + } + } + + if(donorRegions == null || acceptorRegions == null) + return false; + + for(RegionReadData donorRegion : donorRegions) + { + for(TransExonRef donorExonRef : donorRegion.getTransExonRefs()) + { + for(RegionReadData acceptorRegion : acceptorRegions) + { + for(TransExonRef acceptorExonRef : acceptorRegion.getTransExonRefs()) + { + if(donorExonRef.TransId == acceptorExonRef.TransId + && abs(donorExonRef.ExonRank - acceptorExonRef.ExonRank) == 1) + { + return true; + } + } + } + } + } + + return false; + } + private static final int MAX_HOMOLOGY_LENGTH = 5; private void checkJunctionHomology(final int[] spliceJunction, final Map readRegions) { - if(readRegions.values().stream().anyMatch(x -> x == EXON_BOUNDARY)) // only consider if neither side matches a known junction + // skip if both junctions already match at least one transcript's exon boundaries even if involving skipping + Set matchedRegions = null; + boolean matchesBoth = false; + + for(int se = SE_START; se <= SE_END; ++se) + { + int splicePosition = spliceJunction[se]; + + for(RegionReadData regionReadData : readRegions.keySet()) + { + boolean matchesBoundary = (se == SE_START && splicePosition == regionReadData.end()) + || (se == SE_END && splicePosition == regionReadData.start()); + + if(matchesBoundary) + { + if(se == SE_START) + { + if(matchedRegions == null) + { + matchedRegions = Sets.newHashSet(); + } + + matchedRegions.add(regionReadData); + } + else + { + if(matchedRegions != null && matchedRegions.contains(regionReadData)) + { + matchesBoth = true; + break; + } + } + } + } + } + + if(matchesBoth) return; // check for a junction position (or both) which can be moved by homology to match a known splice site @@ -278,13 +380,13 @@ private void checkJunctionHomology(final int[] spliceJunction, final Map 0) { // SJ is past the nearest exon boundary, so compare the preceding bases - final String startBases = junctionBases[SE_START].substring(juncBaseIndex - abs(minDistance), juncBaseIndex); - final String endBases = junctionBases[SE_START].substring(juncBaseIndex - abs(minDistance), juncBaseIndex); + String startBases = junctionBases[SE_START].substring(juncBaseIndex - abs(minDistance), juncBaseIndex); + String endBases = junctionBases[SE_START].substring(juncBaseIndex - abs(minDistance), juncBaseIndex); if(!startBases.equals(endBases)) return; } - else + else if(minDistance < 0) { // try to shift the junction forwards - requiring the bases to the right at the start to match at each end @@ -292,8 +394,8 @@ private void checkJunctionHomology(final int[] spliceJunction, final Map sjStartRegions, final List sjEndRegions, final AltSpliceJunctionContext[] regionContexts) { // collect up all exon regions matching the observed novel splice junction - final List sjMatchedTransIds = Lists.newArrayList(); + List sjMatchedTransIds = Lists.newArrayList(); for(Map.Entry entry : read.getMappedRegions().entrySet()) { - final RegionReadData region = entry.getKey(); + RegionReadData region = entry.getKey(); RegionMatchType matchType = entry.getValue(); if(matchType == RegionMatchType.NONE) @@ -421,7 +523,7 @@ private void classifyRegions( for(Map.Entry entry : read.getMappedRegions().entrySet()) { - final RegionReadData region = entry.getKey(); + RegionReadData region = entry.getKey(); RegionMatchType matchType = entry.getValue(); if(matchType == RegionMatchType.NONE) @@ -517,21 +619,21 @@ public void checkNovelExon(AltSpliceJunction firstAltSJ, AltSpliceJunction secon return; } - final List regions1 = Lists.newArrayList(firstAltSJ.sjStartRegions()); + List regions1 = Lists.newArrayList(firstAltSJ.sjStartRegions()); regions1.addAll(firstAltSJ.sjEndRegions()); - final List regions2 = Lists.newArrayList(secondAltSJ.sjStartRegions()); + List regions2 = Lists.newArrayList(secondAltSJ.sjStartRegions()); regions2.addAll(secondAltSJ.sjEndRegions()); List commonTranscripts = Lists.newArrayList(); - for(final RegionReadData region1 : regions1) + for(RegionReadData region1 : regions1) { List transIds1 = region1.getTransExonRefs().stream().map(x -> x.TransId).collect(Collectors.toList()); for(Integer transId1 : transIds1) { - for(final RegionReadData region2 : regions2) + for(RegionReadData region2 : regions2) { List transIds2 = region2.getTransExonRefs().stream().map(x -> x.TransId).collect(Collectors.toList()); @@ -562,6 +664,9 @@ private AltSpliceJunction registerAltSpliceJunction( { AltSpliceJunction altSpliceJunc = createFromRead(read, regionTranscripts); + if(altSpliceJunc == null) + return null; + AltSpliceJunction existingSpliceJunc = mAltSpliceJunctions.stream() .filter(x -> x.matches(altSpliceJunc)).findFirst().orElse(null); @@ -625,7 +730,7 @@ public void prioritiseGenes() for(AltSpliceJunction altSJ : mAltSpliceJunctions) { - final List transIds = altSJ.candidateTransIds(); + List transIds = altSJ.candidateTransIds(); GeneReadData topGene = null; int topMatch = 0; @@ -634,7 +739,7 @@ public void prioritiseGenes() List candidateGenes = Lists.newArrayList(); - for(final GeneReadData gene : mGenes.genes()) + for(GeneReadData gene : mGenes.genes()) { if(gene.getExonRegions().stream().anyMatch(x -> altSJ.sjStartRegions().contains(x) || altSJ.sjEndRegions().contains(x))) { @@ -645,7 +750,7 @@ public void prioritiseGenes() if(candidateGenes.isEmpty()) candidateGenes = mGenes.genes(); - for(final GeneReadData gene : candidateGenes) + for(GeneReadData gene : candidateGenes) { if(spliceStrand != 0 && gene.Gene.Strand != spliceStrand) continue; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntron.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntron.java index 5aced121617..ef4cfb8a8a1 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntron.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntron.java @@ -36,7 +36,7 @@ public RetainedIntron(final List regions, boolean isStart) for(RegionReadData region : mRegions) { - for (final TransExonRef transRef : region.getTransExonRefs()) + for(TransExonRef transRef : region.getTransExonRefs()) { trancriptInfo.add(format("%s-%s", transRef.TransName, transRef.ExonRank)); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntronFinder.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntronFinder.java index f233673021a..31f4fe47ead 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntronFinder.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/RetainedIntronFinder.java @@ -68,11 +68,11 @@ public void evaluateFragmentReads(final Read read1, final Read read2) // - intronic read // - spliced read // - another exon-intron read - same exon - dismissed as likely unspliced - final List splicedTrans = Lists.newArrayList(); + List splicedTrans = Lists.newArrayList(); List retIntrons = Lists.newArrayList(); - for (int i = 0; i <= 1; ++i) + for(int i = 0; i <= 1; ++i) { Read read = (i == 0) ? read1 : read2; @@ -106,7 +106,7 @@ public void evaluateFragmentReads(final Read read1, final Read read2) { if(retIntron1.regions().stream().anyMatch(x -> retIntron2.regions().contains(x))) { - ISF_LOGGER.trace("reads({}) support the same exon from exon-intron reads", read1.Id); + ISF_LOGGER.trace("reads({}) support the same exon from exon-intron reads", read1.id()); return; } } @@ -123,7 +123,7 @@ else if(retIntron1.matches(retIntron2)) RetainedIntron existingRetIntron = mRetainedIntrons.stream().filter(x -> x.matches(retIntron)).findFirst().orElse(null); - if (existingRetIntron != null) + if(existingRetIntron != null) { existingRetIntron.addFragmentCount(hasSpliceSupport); } @@ -140,7 +140,7 @@ private RetainedIntron evaluateRead(Read read) int spannedPosition = 0; boolean spannedIsStart = false; - final List candidateRegions = Lists.newArrayList(); + List candidateRegions = Lists.newArrayList(); if(read.getMappedRegions().values().stream().anyMatch(x -> x != RegionMatchType.EXON_INTRON)) return null; @@ -150,29 +150,29 @@ private RetainedIntron evaluateRead(Read read) RegionReadData region = entry.getKey(); // check each end in turn - for (int se = SE_START; se <= SE_END; ++se) + for(int se = SE_START; se <= SE_END; ++se) { boolean usesStart = se == SE_START; int regionPos = usesStart ? region.start() : region.end(); - if (!read.getMappedRegionCoords().stream().anyMatch(x -> positionWithin(regionPos, x[SE_START], x[SE_END]))) + if(!read.getMappedRegionCoords().stream().anyMatch(x -> positionWithin(regionPos, x.start(), x.end()))) continue; // cannot be the last or first exon - if ((usesStart && region.getPreRegions().isEmpty()) || (!usesStart && region.getPostRegions().isEmpty())) + if((usesStart && region.getPreRegions().isEmpty()) || (!usesStart && region.getPostRegions().isEmpty())) continue; spannedIsStart = usesStart; // take the outer-most region(s) if there are more than one - if (!candidateRegions.isEmpty()) + if(!candidateRegions.isEmpty()) { - if (usesStart) + if(usesStart) { - if (regionPos > spannedPosition) + if(regionPos > spannedPosition) continue; - if (spannedPosition > regionPos) + if(spannedPosition > regionPos) { candidateRegions.clear(); spannedPosition = regionPos; @@ -180,10 +180,10 @@ private RetainedIntron evaluateRead(Read read) } else { - if (regionPos < spannedPosition) + if(regionPos < spannedPosition) continue; - if (spannedPosition < regionPos) + if(spannedPosition < regionPos) { candidateRegions.clear(); spannedPosition = regionPos; @@ -221,7 +221,7 @@ public static BufferedWriter createWriter(final IsofoxConfig config) { try { - final String outputFileName = config.formOutputFile("retained_intron.csv"); + String outputFileName = config.formOutputFile("retained_intron.csv"); BufferedWriter writer = createBufferedWriter(outputFileName, false); writer.write("GeneId,GeneName,Chromosome,Strand,Position"); @@ -249,12 +249,12 @@ private synchronized static void writeRetainedIntrons( { try { - for(final RetainedIntron retIntron: retainedIntrons) + for(RetainedIntron retIntron: retainedIntrons) { if(retIntron.getFragmentCount() < MIN_FRAG_COUNT && retIntron.getSplicedFragmentCount() < MIN_SPLICED_FRAG_COUNT) continue; - for(final GeneReadData gene : genes) + for(GeneReadData gene : genes) { // log if the gene can be linked to one of the transcripts if(!gene.getTranscripts().stream().anyMatch(x -> retIntron.regions().stream().anyMatch(y -> y.hasTransId(x.TransId)))) diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/SpliceSiteCounter.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/SpliceSiteCounter.java index 22fe40f6d5e..100833b029a 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/SpliceSiteCounter.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/SpliceSiteCounter.java @@ -14,6 +14,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.IsofoxConfig; import com.hartwig.hmftools.isofox.common.GeneCollection; import com.hartwig.hmftools.isofox.common.RegionReadData; @@ -37,7 +38,7 @@ public SpliceSiteCounter(final BufferedWriter writer) public void clear() { mSiteCounts.clear(); } public void registerSpliceSiteSupport( - final List readMappedCoords1, final List readMappedCoords2, final List allRegions) + final List readMappedCoords1, final List readMappedCoords2, final List allRegions) { final Set traversedSites = Sets.newHashSet(); final Set supportedSites = Sets.newHashSet(); @@ -50,7 +51,7 @@ public void registerSpliceSiteSupport( } private void registerSpliceSiteSupport( - final List readMappedCoords, final List allRegions, + final List readMappedCoords, final List allRegions, final Set traversedSites, final Set supportedSites) { // for each read region (ie unique exon) record if the read supports its splice junction on each side, or skips it @@ -59,10 +60,10 @@ private void registerSpliceSiteSupport( for(int i = 0; i < readMappedCoords.size() - 1; ++i) { - int[] mappedCoordLower = readMappedCoords.get(i); - int[] mappedCoordUpper = readMappedCoords.get(i + 1); - int junctionLower = mappedCoordLower[SE_END]; - int junctionUpper = mappedCoordUpper[SE_START]; + BaseRegion mappedCoordLower = readMappedCoords.get(i); + BaseRegion mappedCoordUpper = readMappedCoords.get(i + 1); + int junctionLower = mappedCoordLower.end(); + int junctionUpper = mappedCoordUpper.start(); // record if a region start or end is traversed entirely by a mapped coord junction for(RegionReadData region : allRegions) diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjCohortAnalyser.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjCohortAnalyser.java index 0ec8702ffcc..1e5a22fd300 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjCohortAnalyser.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjCohortAnalyser.java @@ -112,7 +112,7 @@ public void processAltSpliceJunctions() ISF_LOGGER.info("cancerType({}) loading alt-SJs for {} samples", cancerType, sampleIds.size()); - for(final String sampleId : sampleIds) + for(String sampleId : sampleIds) { final Path altSJFile = filenames.stream().filter(x -> x.toString().contains(sampleId)).findFirst().orElse(null); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjWriter.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjWriter.java index addf3a24a53..917f2ffeed9 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjWriter.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/AltSjWriter.java @@ -146,7 +146,7 @@ public void writeCohortFrequencies(final Map 1) { @@ -163,7 +163,7 @@ private void findRecurrentVariants() } } - for(final SpliceVariant variant : chrPosVariants) + for(SpliceVariant variant : chrPosVariants) { if(variant.SampleIds.size() > 1) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceSiteCache.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceSiteCache.java index 63bae1f8a48..fbe605c2af3 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceSiteCache.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceSiteCache.java @@ -148,7 +148,7 @@ public static Map> loadSpliceSiteFile(final Path filen lines.remove(0); - for(final String line : lines) + for(String line : lines) { // GeneSetId,Chromosome,SpliceSitePosition,TraverseFrags,SupportFrags,SkipFrags final String[] items = line.split(OLD_FILE_DELIM); @@ -189,7 +189,7 @@ private static boolean inRequiredGenes(final List geneDataList, int po if(geneDataList == null) return false; - for(final GeneData geneData : geneDataList) + for(GeneData geneData : geneDataList) { if(positionWithin(position, geneData.GeneStart, geneData.GeneEnd)) return true; @@ -332,7 +332,7 @@ private void loadSpliceSitePercentiles(final String filename) // skip field names String line = fileReader.readLine(); - if (line == null) + if(line == null) { ISF_LOGGER.error("empty cohort splice site percentiles file({})", filename); return; @@ -355,7 +355,7 @@ private void loadSpliceSitePercentiles(final String filename) { String[] items = line.split(OLD_FILE_DELIM, -1); - if (items.length != expectedColCount) + if(items.length != expectedColCount) { ISF_LOGGER.error("invalid splice site percentile data length({}) vs expected({}): {}", items.length, expectedColCount, line); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceVariantMatcher.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceVariantMatcher.java index cee374743f7..4ff3b0d75ec 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceVariantMatcher.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/novel/cohort/SpliceVariantMatcher.java @@ -237,7 +237,7 @@ private List getSomaticVariants(final String sampleId) .and(Tables.SOMATICVARIANT.GENE.notEqual("")) .fetch(); - for (Record record : result) + for(Record record : result) { // filter to specific gene list final String gene = record.getValue(Tables.SOMATICVARIANT.GENE); @@ -327,7 +327,7 @@ private void findRelatedAltSpliceJunctions( int exonPosition = 0; String closestTransStr = ""; - for(final TranscriptData transData : transDataList) + for(TranscriptData transData : transDataList) { for(int i = 0; i < transData.exons().size(); ++i) { @@ -397,7 +397,7 @@ else if(withinRange(exon.End, variant.Position, SPLICE_REGION_NON_CODING_DISTANC final ExonData nextExon = i < transData.exons().size() - 1 ? transData.exons().get(i + 1) : null; // look for any alt SJs which match with the somatic variant - for(final AltSpliceJuncData altSJ : altSpliceJunctions) + for(AltSpliceJuncData altSJ : altSpliceJunctions) { if(matchedAltSJs.contains(altSJ)) continue; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ChrExpectedCountsTask.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ChrExpectedCountsTask.java index fb7be35ef7f..d0f42c26b20 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ChrExpectedCountsTask.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ChrExpectedCountsTask.java @@ -72,14 +72,6 @@ private void generateExpectedCounts() GeneCollection geneCollection = new GeneCollection(mCollectionId++, geneReadDataList); - for(GeneReadData geneReadData : geneReadDataList) - { - if(mConfig.EnrichedGeneIds.contains(geneReadData.Gene.GeneId)) - { - geneCollection.setEnrichedTranscripts(mGeneTransCache.getTranscripts(geneReadData.Gene.GeneId)); - } - } - mExpRatesGenerator.generateExpectedRates(geneCollection); ISF_LOGGER.trace("chr({}) gene({}) processed({} of {})", diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedCountsGenerator.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedCountsGenerator.java index 8183c15b3f3..e2ead19230c 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedCountsGenerator.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedCountsGenerator.java @@ -2,16 +2,15 @@ import static java.lang.Math.min; -import static com.hartwig.hmftools.common.utils.file.FileWriterUtils.createBufferedWriter; import static com.hartwig.hmftools.common.region.BaseRegion.positionsOverlap; import static com.hartwig.hmftools.common.region.BaseRegion.positionsWithin; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; -import static com.hartwig.hmftools.isofox.FragmentAllocator.calcFragmentLength; import static com.hartwig.hmftools.isofox.common.FragmentMatchType.LONG; import static com.hartwig.hmftools.isofox.common.FragmentMatchType.SHORT; import static com.hartwig.hmftools.isofox.common.FragmentMatchType.SPLICED; import static com.hartwig.hmftools.isofox.common.FragmentMatchType.UNSPLICED; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.calcFragmentLength; import static com.hartwig.hmftools.isofox.expression.ExpectedRatesCommon.formTranscriptDefinitions; import static com.hartwig.hmftools.isofox.refdata.RefDataWriter.writeExpectedCounts; diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedGcRatiosGenerator.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedGcRatiosGenerator.java index f216dd320c7..20fe099cafe 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedGcRatiosGenerator.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/ExpectedGcRatiosGenerator.java @@ -19,6 +19,7 @@ import com.hartwig.hmftools.common.gene.ExonData; import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.gene.TranscriptData; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.adjusts.GcRatioCounts; import htsjdk.samtools.SAMException; @@ -64,7 +65,7 @@ private void generateExpectedCounts() int genesProcessed = 0; int nextLogCount = 100; - for(final GeneData geneData : mGeneDataList) + for(GeneData geneData : mGeneDataList) { final List transDataList = mGeneTransCache.getTranscripts(geneData.GeneId); @@ -156,11 +157,11 @@ private void calculateTranscriptGcRatios(final String chromosome, final Transcri int readLength = mConfig.ReadLength; boolean endOfTrans = false; - for(final ExonData exon : transData.exons()) + for(ExonData exon : transData.exons()) { for(int startPos = exon.Start; startPos <= exon.End; ++startPos) { - final List readRegions = generateReadRegions(transData, startPos, readLength); + List readRegions = generateReadRegions(transData, startPos, readLength); if(readRegions.isEmpty()) { @@ -182,9 +183,9 @@ private void calculateTranscriptGcRatios(final String chromosome, final Transcri writeExpectedGcRatios(mWriter, transData.TransName, gcRatioCounts.getCounts()); } - public List generateReadRegions(final TranscriptData transData, int startPos, int readLength) + private List generateReadRegions(final TranscriptData transData, int startPos, int readLength) { - List readRegions = Lists.newArrayListWithExpectedSize(10); + List readRegions = Lists.newArrayListWithExpectedSize(10); // set out the fragment reads either within a single exon or spanning one or more int exonCount = transData.exons().size(); @@ -206,14 +207,14 @@ public List generateReadRegions(final TranscriptData transData, int start if(nextRegionStart + remainingReadBases - 1 <= exon.End) { int regionEnd = nextRegionStart + remainingReadBases - 1; - readRegions.add(new int[] { nextRegionStart, regionEnd }); + readRegions.add(new BaseRegion(nextRegionStart, regionEnd)); return readRegions; } int regionEnd = exon.End; int regionLength = regionEnd - nextRegionStart + 1; remainingReadBases -= regionLength; - readRegions.add(new int[] { nextRegionStart, regionEnd }); + readRegions.add(new BaseRegion(nextRegionStart, regionEnd)); if(i == exonCount - 1) { diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataConfig.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataConfig.java index 605b61da1cf..80012210086 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataConfig.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataConfig.java @@ -44,7 +44,6 @@ public class RefDataConfig public final RefGenomeInterface RefGenome; public final List RestrictedGeneIds; // specific set of genes to process - public final List EnrichedGeneIds; public final int Threads; public final String OutputDir; @@ -80,9 +79,6 @@ public RefDataConfig(final ConfigBuilder configBuilder) RefGenome = null; } - EnrichedGeneIds = Lists.newArrayList(); - IsofoxConstants.populateEnrichedGeneIds(EnrichedGeneIds, RefGenVersion); - FragmentSizeData = loadFragmentSizeConfig(configBuilder); if(GenerateExpectedCounts) @@ -138,7 +134,6 @@ public RefDataConfig(final int readLength) RefGenVersion = V37; RefGenome = null; - EnrichedGeneIds = Lists.newArrayList(); FragmentSizeData = Lists.newArrayList(); RestrictedGeneIds = Lists.newArrayList(); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataWriter.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataWriter.java index 85da47b84f3..ab4a64f7efe 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataWriter.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/refdata/RefDataWriter.java @@ -83,7 +83,7 @@ public synchronized static void writeExpectedCounts( writer.write(String.format("%s,%s,%.0f", collectionId, tcData.combinedKey(), lengthCounts[0])); - for (int i = 1; i < lengthCounts.length; ++i) + for(int i = 1; i < lengthCounts.length; ++i) { writer.write(String.format(",%.0f", lengthCounts[i])); } diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/GeneResult.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/GeneResult.java index 1a8ceae62a9..2d1275a2a5a 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/GeneResult.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/GeneResult.java @@ -32,7 +32,7 @@ public class GeneResult private double mRawTpm; private double mAdjustedTpm; private double mFitResiduals; - private double mLowMapQualsAllocation; + private double mMultiMappedAllocation; // cohort values for annotation private double mMedianTpmCohort; @@ -56,7 +56,7 @@ public GeneResult(final GeneCollection geneCollection, final GeneReadData geneRe mRawTpm = 0; mAdjustedTpm = 0; mUnsplicedAlloc = 0; - mLowMapQualsAllocation = 0; + mMultiMappedAllocation = 0; mMedianTpmCohort = 0; mPercentileTpmCohort = 0; @@ -71,6 +71,9 @@ public void setFitAllocation(double splicedAlloc, double unsplicedAlloc) mUnsplicedAlloc = unsplicedAlloc; } + public void addSplicedAlloc(double splicedAlloc) { mSplicedAlloc += splicedAlloc; } + public void addUnsplicedAlloc(double unsplicedAlloc) { mUnsplicedAlloc += unsplicedAlloc; } + public void setTPM(double raw, double adjusted) { mRawTpm = raw; @@ -88,7 +91,7 @@ public void setTPM(double raw, double adjusted) public double getSplicedAlloc() { return mSplicedAlloc; } public double getUnsplicedAlloc() { return mUnsplicedAlloc; } - public void setLowMapQualsAllocation(double alloc) { mLowMapQualsAllocation = alloc; } + public void setMultiMappedAllocation(double alloc) { mMultiMappedAllocation = alloc; } public void setCohortValues(double medianTpmCohort, double percentileTpmCohort, double medianTpmCancer, double percentileTpmCancer) { @@ -118,7 +121,7 @@ public static String header() .add(FLD_ADJ_TPM) .add("RawTPM") .add("FitResiduals") - .add("LowMapQualFrags") + .add("MultiMappedFragments") .add(FLD_MEDIAN_TPM_CANCER) .add(FLD_PERC_TPM_CANCER) .add(FLD_MEDIAN_TPM_COHORT) @@ -142,7 +145,7 @@ public String toLine() .add(String.format("%6.3e", mAdjustedTpm)) .add(String.format("%6.3e", mRawTpm)) .add(String.format("%.1f", getFitResiduals())) - .add(String.format("%.1f", mLowMapQualsAllocation)) + .add(String.format("%.1f", mMultiMappedAllocation)) .add(String.format("%6.3e", mMedianTpmCancer)) .add(String.format("%.3f", mPercentileTpmCancer)) .add(String.format("%6.3e", mMedianTpmCohort)) diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/ResultsWriter.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/ResultsWriter.java index bc96a709ed7..5af1086e474 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/ResultsWriter.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/ResultsWriter.java @@ -1,17 +1,24 @@ package com.hartwig.hmftools.isofox.results; import static java.lang.String.format; - -import static com.hartwig.hmftools.common.bam.SupplementaryReadData.fromAlignment; +import static java.lang.String.valueOf; + +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_CHROMOSOME; +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_GENE_ID; +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_GENE_NAME; +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_POS_END; +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_POS_START; +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_TRANS_ID; +import static com.hartwig.hmftools.common.utils.file.CommonFields.FLD_TRANS_NAME; import static com.hartwig.hmftools.common.utils.file.FileDelimiters.TSV_DELIM; import static com.hartwig.hmftools.isofox.WriteType.CHIMERIC_POSITION_DATA; -import static com.hartwig.hmftools.isofox.WriteType.CHIMERIC_READ; import static com.hartwig.hmftools.isofox.WriteType.FRAG_LENGTH_BY_GENE; import static com.hartwig.hmftools.isofox.WriteType.GC_RATIO; +import static com.hartwig.hmftools.isofox.WriteType.MULTI_MAP_LOCI; import static com.hartwig.hmftools.isofox.WriteType.READ; import static com.hartwig.hmftools.isofox.WriteType.SPLICE_SITE; import static com.hartwig.hmftools.isofox.WriteType.TRANS_COMBO; -import static com.hartwig.hmftools.isofox.common.Read.clippedSide; +import static com.hartwig.hmftools.isofox.common.ReadUtils.consensusDuplicateCount; import static com.hartwig.hmftools.isofox.novel.CanonicalSpliceJunctionFile.CANONICAL_SJ_FILE_ID; import static com.hartwig.hmftools.common.rna.GeneExpressionFile.GENE_EXPRESSION_FILE_ID; import static com.hartwig.hmftools.common.rna.RnaStatisticFile.SUMMARY_FILE_ID; @@ -29,7 +36,7 @@ import static com.hartwig.hmftools.isofox.common.FragmentType.CHIMERIC; import static com.hartwig.hmftools.isofox.common.FragmentType.DUPLICATE; import static com.hartwig.hmftools.isofox.common.FragmentType.FORWARD_STRAND; -import static com.hartwig.hmftools.isofox.common.FragmentType.LOW_MAP_QUAL; +import static com.hartwig.hmftools.isofox.common.FragmentType.MULTI_MAPPED; import static com.hartwig.hmftools.isofox.common.FragmentType.REVERSE_STRAND; import static com.hartwig.hmftools.isofox.common.FragmentType.TOTAL; import static com.hartwig.hmftools.isofox.common.FragmentType.TRANS_SUPPORTING; @@ -48,23 +55,21 @@ import java.util.List; import java.util.Map; import java.util.StringJoiner; +import java.util.stream.Collectors; import com.google.common.collect.Maps; -import com.hartwig.hmftools.common.bam.ClippedSide; -import com.hartwig.hmftools.common.bam.SupplementaryReadData; import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.gene.ExonData; import com.hartwig.hmftools.common.gene.TranscriptData; -import com.hartwig.hmftools.isofox.WriteType; -import com.hartwig.hmftools.isofox.common.BaseDepth; +import com.hartwig.hmftools.isofox.common.FragmentType; import com.hartwig.hmftools.isofox.common.Read; +import com.hartwig.hmftools.isofox.common.RegionMatchType; +import com.hartwig.hmftools.isofox.common.TransMatchType; import com.hartwig.hmftools.isofox.fusion.ChimericPosData; -import com.hartwig.hmftools.isofox.fusion.ChimericReadGroup; import com.hartwig.hmftools.isofox.fusion.ChimericRemoteRegion; import com.hartwig.hmftools.isofox.novel.CanonicalSpliceJunctionFile; import com.hartwig.hmftools.common.rna.RnaStatisticFile; import com.hartwig.hmftools.common.rna.RnaStatistics; -import com.hartwig.hmftools.isofox.FragmentAllocator; import com.hartwig.hmftools.isofox.IsofoxConfig; import com.hartwig.hmftools.isofox.adjusts.FragmentSizeCalcs; import com.hartwig.hmftools.isofox.common.BamReadCounter; @@ -104,8 +109,8 @@ public class ResultsWriter private BufferedWriter mReadGcRatioWriter; private BufferedWriter mRetainedIntronWriter; private BufferedWriter mSpliceSiteWriter; - private BufferedWriter mChimericReadWriter; private BufferedWriter mChimericPositionDataWriter; + private BufferedWriter mMultiMapLociWriter; public ResultsWriter(final IsofoxConfig config) { @@ -124,7 +129,6 @@ public ResultsWriter(final IsofoxConfig config) mReadGcRatioWriter = null; mRetainedIntronWriter = null; mSpliceSiteWriter = null; - mChimericReadWriter = null; mChimericPositionDataWriter = null; if(mConfig.runFunction(TRANSCRIPT_COUNTS)) @@ -151,8 +155,8 @@ public void close() closeBufferedWriter(mReadGcRatioWriter); closeBufferedWriter(mRetainedIntronWriter); closeBufferedWriter(mSpliceSiteWriter); - closeBufferedWriter(mChimericReadWriter); closeBufferedWriter(mChimericPositionDataWriter); + closeBufferedWriter(mMultiMapLociWriter); } private void initialiseExternalWriters() @@ -171,7 +175,7 @@ private void initialiseExternalWriters() if(mConfig.runFunction(READ_COUNTS)) mReadDataWriter = BamReadCounter.createReadDataWriter(mConfig); else - mReadDataWriter = FragmentAllocator.createReadDataWriter(mConfig); + mReadDataWriter = createReadDataWriter(mConfig); } if(mConfig.writeType(SPLICE_SITE)) @@ -192,11 +196,11 @@ private void initialiseExternalWriters() if(mConfig.writeType(TRANS_COMBO)) mCategoryCountsWriter = TranscriptExpression.createWriter(mConfig); - if(mConfig.writeType(CHIMERIC_READ)) - initialiseChimericReadWriter(); - if(mConfig.writeType(CHIMERIC_POSITION_DATA)) initialiseChimericPositionDataWriter(); + + if(mConfig.writeType(MULTI_MAP_LOCI)) + mMultiMapLociWriter = createMultiMapLociWriter(mConfig); } public BufferedWriter getCategoryCountsWriter() { return mCategoryCountsWriter;} @@ -204,10 +208,10 @@ private void initialiseExternalWriters() public BufferedWriter getAltSjPassingWriter() { return mAltSjPassingWriter;} public BufferedWriter getRetainedIntronWriter() { return mRetainedIntronWriter;} public BufferedWriter getReadDataWriter() { return mReadDataWriter; } + public BufferedWriter getMultiMapLociWriter() { return mMultiMapLociWriter; } public BufferedWriter getSpliceSiteWriter() { return mSpliceSiteWriter; } public BufferedWriter getFragmentLengthWriter() { return mGeneFragLengthWriter; } public BufferedWriter getReadGcRatioWriter() { return mReadGcRatioWriter; } - public BufferedWriter getChimericReadWriter() { return mChimericReadWriter; } public BufferedWriter getChimericPositionDataWriter() { return mChimericPositionDataWriter; } public void writeSummaryStats(final RnaStatistics summaryStats) @@ -273,7 +277,7 @@ private void initialiseGeneCollectionWriter() sj.add("GeneSetId").add("GeneCount").add("Chromosome").add("RangeStart").add("RangeEnd"); sj.add("TotalFragments").add("Duplicates").add("SupportingTrans").add("Unspliced").add("AltSJ").add("Chimeric"); - sj.add("LowMapQual").add("ForwardStrand").add("ReverseStrand").add("Genes"); + sj.add("MultiMapped").add("ForwardStrand").add("ReverseStrand").add("Genes"); mGeneCollectionWriter.write(sj.toString()); mGeneCollectionWriter.newLine(); @@ -293,21 +297,21 @@ public synchronized void writeGeneCollectionData(final GeneCollection geneCollec { StringJoiner sj = new StringJoiner(TSV_DELIM); sj.add(geneCollection.chrId()); - sj.add(String.valueOf(geneCollection.genes().size())); + sj.add(valueOf(geneCollection.genes().size())); sj.add(geneCollection.chromosome()); - sj.add(String.valueOf(geneCollection.regionBounds()[SE_START])); - sj.add(String.valueOf(geneCollection.regionBounds()[SE_END])); - - final FragmentTypeCounts fragmentCounts = geneCollection.fragmentTypeCounts(); - sj.add(String.valueOf(fragmentCounts.typeCount(TOTAL))); - sj.add(String.valueOf(fragmentCounts.typeCount(DUPLICATE))); - sj.add(String.valueOf(fragmentCounts.typeCount(TRANS_SUPPORTING))); - sj.add(String.valueOf(fragmentCounts.typeCount(UNSPLICED))); - sj.add(String.valueOf(fragmentCounts.typeCount(ALT))); - sj.add(String.valueOf(fragmentCounts.typeCount(CHIMERIC))); - sj.add(String.valueOf(fragmentCounts.typeCount(LOW_MAP_QUAL))); - sj.add(String.valueOf(fragmentCounts.typeCount(FORWARD_STRAND))); - sj.add(String.valueOf(fragmentCounts.typeCount(REVERSE_STRAND))); + sj.add(valueOf(geneCollection.regionBounds()[SE_START])); + sj.add(valueOf(geneCollection.regionBounds()[SE_END])); + + FragmentTypeCounts fragmentCounts = geneCollection.fragmentTypeCounts(); + sj.add(valueOf(fragmentCounts.typeCount(TOTAL))); + sj.add(valueOf(fragmentCounts.typeCount(DUPLICATE))); + sj.add(valueOf(fragmentCounts.typeCount(TRANS_SUPPORTING))); + sj.add(valueOf(fragmentCounts.typeCount(UNSPLICED))); + sj.add(valueOf(fragmentCounts.typeCount(ALT))); + sj.add(valueOf(fragmentCounts.typeCount(CHIMERIC))); + sj.add(valueOf(fragmentCounts.typeCount(MULTI_MAPPED))); + sj.add(valueOf(fragmentCounts.typeCount(FORWARD_STRAND))); + sj.add(valueOf(fragmentCounts.typeCount(REVERSE_STRAND))); sj.add(geneCollection.geneNames(geneCollection.genes().size())); @@ -356,12 +360,17 @@ public synchronized void writeExonData(final GeneReadData geneReadData, final Tr { if(mExonDataWriter == null) { - final String outputFileName = mConfig.formOutputFile("exon_data.csv"); + final String outputFileName = mConfig.formOutputFile("exon_data.tsv"); mExonDataWriter = createBufferedWriter(outputFileName, false); - mExonDataWriter.write("GeneId,GeneName,TransId,TransName,ExonRank,ExonStart,ExonEnd,SharedTrans"); - mExonDataWriter.write(",TotalCoverage,AvgDepth,UniqueBases,UniqueBaseCoverage,UniqueBaseAvgDepth,Fragments,UniqueFragments"); - mExonDataWriter.write(",SpliceJuncStart,SpliceJuncEnd,UniqueSpliceJuncStart,UniqueSpliceJuncEnd"); + + StringJoiner sj = new StringJoiner(TSV_DELIM); + + sj.add(FLD_GENE_ID).add(FLD_GENE_NAME).add(FLD_TRANS_ID).add(FLD_TRANS_NAME).add("ExonRank").add("ExonStart").add("ExonEnd"); + sj.add("SharedTrans").add("TotalCoverage").add("AvgDepth").add("UniqueBases").add("UniqueBaseCoverage").add("UniqueBaseAvgDepth"); + sj.add("Fragments").add("UniqueFragments").add("SpliceJuncStart").add("SpliceJuncEnd").add("UniqueSpliceJuncStart").add("UniqueSpliceJuncEnd"); + + mExonDataWriter.write(sj.toString()); mExonDataWriter.newLine(); } @@ -375,150 +384,156 @@ public synchronized void writeExonData(final GeneReadData geneReadData, final Tr if(exonReadData == null) continue; - mExonDataWriter.write(format("%s,%s,%d,%s", - geneReadData.Gene.GeneId, geneReadData.Gene.GeneName, transData.TransId, transData.TransName)); + StringJoiner sj = new StringJoiner(TSV_DELIM); - mExonDataWriter.write(format(",%d,%d,%d,%d", - exon.Rank, exon.Start, exon.End, exonReadData.getTransExonRefs().size())); + sj.add(geneReadData.Gene.GeneId); + sj.add(geneReadData.Gene.GeneName); + sj.add(String.valueOf(transData.TransId)); + sj.add(transData.TransName); + sj.add(String.valueOf(exon.Rank)); + sj.add(String.valueOf(exon.Start)); + sj.add(String.valueOf(exon.End)); + sj.add(String.valueOf(exonReadData.getTransExonRefs().size())); int[] matchCounts = exonReadData.getTranscriptReadCount(transData.TransId); int[] startSjCounts = exonReadData.getTranscriptJunctionMatchCount(transData.TransId, SE_START); int[] endSjCounts = exonReadData.getTranscriptJunctionMatchCount(transData.TransId, SE_END); + sj.add(String.valueOf(matchCounts[TRANS_COUNT])); + sj.add(String.valueOf(matchCounts[UNIQUE_TRANS_COUNT])); + sj.add(String.valueOf(startSjCounts[TRANS_COUNT])); + sj.add(String.valueOf(endSjCounts[TRANS_COUNT])); + sj.add(String.valueOf(startSjCounts[UNIQUE_TRANS_COUNT])); + sj.add(String.valueOf(endSjCounts[UNIQUE_TRANS_COUNT])); + int uniqueBaseTotalDepth = exonReadData.uniqueBaseTotalDepth(); int uniqueBaseCount = exonReadData.uniqueBaseCount(); double uniqueAvgDepth = uniqueBaseCount > 0 ? uniqueBaseTotalDepth / (double)uniqueBaseCount : 0; - mExonDataWriter.write(format(",%d,%.0f,%d,%d,%.0f", - exonReadData.baseCoverage(1), exonReadData.averageDepth(), - uniqueBaseCount, exonReadData.uniqueBaseCoverage(1), uniqueAvgDepth)); - - mExonDataWriter.write(format(",%d,%d,%d,%d,%d,%d", - matchCounts[TRANS_COUNT], matchCounts[UNIQUE_TRANS_COUNT], - startSjCounts[TRANS_COUNT], endSjCounts[TRANS_COUNT], - startSjCounts[UNIQUE_TRANS_COUNT], endSjCounts[UNIQUE_TRANS_COUNT])); + sj.add(String.valueOf(exonReadData.baseCoverage(1))); + sj.add(String.format("%.0f", exonReadData.averageDepth())); + sj.add(String.valueOf(uniqueBaseCount)); + sj.add(String.valueOf(exonReadData.uniqueBaseCoverage(1))); + sj.add(String.format("%.0f", uniqueAvgDepth)); + mExonDataWriter.write(sj.toString()); mExonDataWriter.newLine(); } } catch(IOException e) { - ISF_LOGGER.error("failed to write exon expression file: {}", e.toString()); + ISF_LOGGER.error("failed to write exon data file: {}", e.toString()); } } - private void initialiseChimericReadWriter() + public static BufferedWriter createReadDataWriter(final IsofoxConfig config) { try { - final String outputFileName = mConfig.formOutputFile("chimeric_reads.tsv"); - mChimericReadWriter = createBufferedWriter(outputFileName, false); + String outputFileName = config.formOutputFile("read_data.tsv"); + + BufferedWriter writer = createBufferedWriter(outputFileName, false); StringJoiner sj = new StringJoiner(TSV_DELIM); - sj.add("GroupCount").add("GroupComplete").add("ReadId"); - sj.add("Chromosome").add("PosStart").add("PosEnd").add("Cigar").add("Flags").add("MapQual"); - sj.add("IsSupp").add("IsDup").add("MateChr").add("MatePosition").add("SuppChr").add("SuppPosition"); - sj.add("GeneSet").add("GeneName").add("BaseDepth"); + sj.add("ReadId").add("GeneInfo"); + sj.add(FLD_CHROMOSOME).add(FLD_POS_START).add(FLD_POS_END).add("Cigar").add("InsertSize").add("MateChr").add("MatePosStart"); + sj.add("Flags").add("FirstInPair").add("ReadReversed").add("IsSupp").add("SuppData"); + sj.add("Duplicate").add("ConsensusDupCount"); - mChimericReadWriter.write(sj.toString()); - mChimericReadWriter.newLine(); + sj.add("FragType").add("TransInfo"); + + writer.write(sj.toString()); + writer.newLine(); + return writer; } catch (IOException e) { - ISF_LOGGER.error("failed to initialise chimeric read data: {}", e.toString()); + ISF_LOGGER.error("failed to create read data writer: {}", e.toString()); + return null; } } - public static synchronized void writeChimericReadData( - final BufferedWriter writer, final ChimericReadGroup readGroup, final BaseDepth baseDepth) + public synchronized static void writeReadData( + final BufferedWriter writer, final List overlapGenes, final Read read, + final FragmentType geneReadType, int validTranscripts) { - if(writer == null) - return; - try { - Read primaryRead = null; - ClippedSide maxClippedSide = null; - String suppChromosome = ""; - int suppPosition = 0; + StringJoiner sj = new StringJoiner(TSV_DELIM); - for(Read read : readGroup.reads()) - { - ClippedSide clippedSide = clippedSide(read); + String geneInfo = overlapGenes.stream() + .map(x -> format("%s:%s", x.Gene.GeneId, x.Gene.GeneName)).collect(Collectors.joining(ITEM_DELIM)); - if(primaryRead == null) - { - primaryRead = read; - maxClippedSide = clippedSide; - } - else if(primaryRead.isSupplementaryAlignment() && !read.isSupplementaryAlignment()) - { - primaryRead = read; - maxClippedSide = clippedSide; - } - else - { + sj.add(read.id()); + sj.add(geneInfo); - if(clippedSide.Length > maxClippedSide.Length) - { - primaryRead = read; - maxClippedSide = clippedSide; - } - } + sj.add(read.chromosome()); + sj.add(valueOf(read.alignmentStart())); + sj.add(valueOf(read.alignmentEnd())); + sj.add(read.cigarStr()); + sj.add(valueOf(read.fragmentInsertSize())); + sj.add(read.mateChromosome()); + sj.add(valueOf(read.mateAlignmentStart())); - if(suppChromosome.isEmpty() && !read.isSupplementaryAlignment() && read.hasSuppAlignment()) - { - String[] suppDataItems = read.getSuppAlignment().split(CSV_DELIM, -1); + sj.add(valueOf(read.flags())); + sj.add(valueOf(read.isFirstOfPair())); + sj.add(valueOf(read.isReadReversed())); + sj.add(valueOf(read.isSupplementaryAlignment())); + sj.add(read.supplementaryData() != null ? read.supplementaryData().asDelimStr() : ""); - if(suppDataItems.length > 2) - { - suppChromosome = suppDataItems[0]; - suppPosition = Integer.parseInt(suppDataItems[1]); - } - } + sj.add(valueOf(read.isDuplicate())); + if(read.isConsensusRead()) + { + sj.add(valueOf(consensusDuplicateCount(read.bamRecord()))); } - - StringJoiner sj = new StringJoiner(TSV_DELIM); - sj.add(String.valueOf(readGroup.size())); - sj.add(String.valueOf(readGroup.isComplete())); - sj.add(primaryRead.Id); - sj.add(primaryRead.Chromosome); - sj.add(String.valueOf(primaryRead.PosStart)); - sj.add(String.valueOf(primaryRead.PosEnd)); - sj.add(primaryRead.cigarStr()); - sj.add(String.valueOf(primaryRead.flags())); - sj.add(String.valueOf(primaryRead.mapQuality())); - sj.add(String.valueOf(primaryRead.isSupplementaryAlignment())); - sj.add(String.valueOf(primaryRead.isDuplicate())); - sj.add(primaryRead.mateChromosome()); - sj.add(String.valueOf(primaryRead.mateStartPosition())); - - sj.add(suppChromosome); - sj.add(String.valueOf(suppPosition)); - - sj.add(String.valueOf(primaryRead.getGeneCollectons()[SE_START])); - - String geneId = ""; - - if(!primaryRead.getReadTransExonRefs().isEmpty()) + else { - TransExonRef transExonRef = primaryRead.getReadTransExonRefs().values().iterator().next().get(0); - geneId = transExonRef.GeneId; + sj.add("0"); } - sj.add(geneId); + sj.add(geneReadType.toString()); + + // info for each transcript: TransId|TransClass|ValidTrans|ExonRank|ExonStart|RegionStart|RegionEnd|RegionClass|ScMatched start/end" + + StringJoiner transInfo = new StringJoiner(ITEM_DELIM); + + for(Map.Entry entry : read.getTranscriptClassifications().entrySet()) + { + int transId = entry.getKey(); + TransMatchType transType = entry.getValue(); + + for(Map.Entry rEntry : read.getMappedRegions().entrySet()) + { + RegionReadData region = rEntry.getKey(); + RegionMatchType matchType = rEntry.getValue(); + + if(!region.hasTransId(transId)) + continue; + - int basePosition = maxClippedSide.Length > 0 ? - (maxClippedSide.isLeft() ? primaryRead.PosStart : primaryRead.PosEnd) : primaryRead.PosStart; + StringJoiner transSj = new StringJoiner("|"); + transSj.add(valueOf(transId)); + transSj.add(transType.toString()); + transSj.add(valueOf(validTranscripts)); + transSj.add(valueOf(region.getExonRank(transId))); + transSj.add(valueOf(region.start())); + transSj.add(valueOf(region.end())); + transSj.add(matchType.toString()); + transSj.add(valueOf(read.mappedCoords().softClipRegionsMatched(SE_START))); + transSj.add(valueOf(read.mappedCoords().softClipRegionsMatched(SE_END))); + + transInfo.add(transSj.toString()); + } + } - sj.add(String.valueOf(baseDepth.depthAtBase(basePosition))); + sj.add(transInfo.toString()); writer.write(sj.toString()); writer.newLine(); } - catch (IOException e) + catch(IOException e) { - ISF_LOGGER.error("failed to write chimeric read data: {}", e.toString()); + ISF_LOGGER.error("failed to write read data file: {}", e.toString()); } } @@ -559,10 +574,10 @@ public static synchronized void writeChimericPositionData(final BufferedWriter w StringJoiner sj = new StringJoiner(TSV_DELIM); sj.add(posData.Chromosome); - sj.add(String.valueOf(posData.Position)); - sj.add(String.valueOf(posData.ReadCount)); - sj.add(String.valueOf(posData.DuplicateCount)); - sj.add(String.valueOf(posData.SuppCount)); + sj.add(valueOf(posData.Position)); + sj.add(valueOf(posData.ReadCount)); + sj.add(valueOf(posData.DuplicateCount)); + sj.add(valueOf(posData.SuppCount)); if(!posData.RemoteRegions.isEmpty()) { @@ -570,8 +585,8 @@ public static synchronized void writeChimericPositionData(final BufferedWriter w ChimericRemoteRegion maxRemoteRegion = posData.RemoteRegions.get(0); sj.add(maxRemoteRegion.toString()); - sj.add(String.valueOf(maxRemoteRegion.Count)); - sj.add(String.valueOf(posData.RemoteRegions.size())); + sj.add(valueOf(maxRemoteRegion.Count)); + sj.add(valueOf(posData.RemoteRegions.size())); } else { @@ -591,6 +606,27 @@ public static synchronized void writeChimericPositionData(final BufferedWriter w } } + public static BufferedWriter createMultiMapLociWriter(final IsofoxConfig config) + { + try + { + BufferedWriter writer = createBufferedWriter(config.formOutputFile("multi_map_loci.tsv"), false); + + StringJoiner sj = new StringJoiner(TSV_DELIM); + sj.add("GeneCollectionId").add("ReadId").add("RecordType"); + sj.add(FLD_CHROMOSOME).add(FLD_POS_START).add(FLD_POS_END); + sj.add("Spliced").add("Genes").add("InGeneCollection"); + writer.write(sj.toString()); + writer.newLine(); + return writer; + } + catch(IOException e) + { + ISF_LOGGER.error("failed to create multi-map loci writer: {}", e.toString()); + return null; + } + } + private static final int MIN_SPLICE_JUNCTON_FRAGMENTS = 3; public synchronized void writeSpliceJunctionData(final GeneCollection geneCollection) diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/SummaryStats.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/SummaryStats.java index fd9cbbd4517..ddbca2826cd 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/SummaryStats.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/SummaryStats.java @@ -1,6 +1,5 @@ package com.hartwig.hmftools.isofox.results; -import static com.hartwig.hmftools.common.rna.RnaQcFilter.qcFiltersToString; import static com.hartwig.hmftools.isofox.IsofoxConfig.ISF_LOGGER; import static com.hartwig.hmftools.isofox.common.FragmentType.ALT; import static com.hartwig.hmftools.isofox.common.FragmentType.CHIMERIC; @@ -28,7 +27,7 @@ public class SummaryStats { public static RnaStatistics createSummaryStats( - final FragmentTypeCounts fragmentTypeCounts, long enrichedGeneFragCount, int spliceGeneCount, + final FragmentTypeCounts fragmentTypeCounts, int spliceGeneCount, double medianGCRatio, final List fragmentLengths, int maxReadLength, int lowCoverageThreshold, int splicedGeneThreshold) { @@ -37,8 +36,6 @@ public static RnaStatistics createSummaryStats( double totalFragmentsDenom = totalFragments; - double enrichedGenePercent = totalFragments > 0 ? enrichedGeneFragCount / totalFragmentsDenom : 0; - long fowardFrags = fragmentTypeCounts.typeCount(FORWARD_STRAND); double totalStrandFrags = fowardFrags + fragmentTypeCounts.typeCount(REVERSE_STRAND); double forwardStrandPerc = totalStrandFrags > 0 ? fowardFrags / totalStrandFrags : 0; @@ -61,7 +58,6 @@ public static RnaStatistics createSummaryStats( .fragmentLength5thPercent(!fragLengths.isEmpty() ? fragLengths.get(0) : 0) .fragmentLength50thPercent(!fragLengths.isEmpty() ? fragLengths.get(1) : 0) .fragmentLength95thPercent(!fragLengths.isEmpty() ? fragLengths.get(2) : 0) - .enrichedGenePercent(enrichedGenePercent) .medianGCRatio(medianGCRatio) .forwardStrandPercent(forwardStrandPerc) .build(); diff --git a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/TranscriptResult.java b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/TranscriptResult.java index c6512823fee..0e73715bc6e 100644 --- a/isofox/src/main/java/com/hartwig/hmftools/isofox/results/TranscriptResult.java +++ b/isofox/src/main/java/com/hartwig/hmftools/isofox/results/TranscriptResult.java @@ -40,7 +40,7 @@ public class TranscriptResult private double mRawFitAllocation; private double mRawTpm; private double mAdjustedTpm; - private double mLowMapQualsAllocation; + private double mMultiMappedAllocation; public TranscriptResult( final GeneCollection geneCollection, final GeneReadData geneReadData, final TranscriptData transData, @@ -102,12 +102,13 @@ public TranscriptResult( mRawFitAllocation = 0; mRawTpm = 0; mAdjustedTpm = 0; - mLowMapQualsAllocation = 0; + mMultiMappedAllocation = 0; } public void setFitAllocation(double alloc) { mFitAllocation = alloc; } + public void addFitAllocation(double alloc) { mFitAllocation += alloc; } public void setPreGcFitAllocation(double alloc) { mRawFitAllocation = alloc; } - public void setLowMapQualsAllocation(double alloc) { mLowMapQualsAllocation = alloc; } + public void setMultiMappedAllocation(double alloc) { mMultiMappedAllocation = alloc; } public void setTPM(double raw, double adjusted) { @@ -125,7 +126,7 @@ public static double calcEffectiveLength(int transLength, final List readCoords = Lists.newArrayList(); - readCoords.add(new int[] {100, 200}); + List readCoords = Lists.newArrayList(); + readCoords.add(new BaseRegion(100, 200)); baseDepth.processRead(readCoords); readCoords.clear(); - readCoords.add(new int[] {100, 150}); + readCoords.add(new BaseRegion(100, 150)); baseDepth.processRead(readCoords); // reads out the range have no effect readCoords.clear(); - readCoords.add(new int[] {50, 60}); - readCoords.add(new int[] {250, 260}); + readCoords.add(new BaseRegion(50, 60)); + readCoords.add(new BaseRegion(250, 260)); baseDepth.processRead(readCoords); assertEquals(51, baseDepth.basesWithDepth()); @@ -68,8 +69,8 @@ public void testBaseDepth() assertEquals(0, baseDepth.depthAtBase(201)); readCoords.clear(); - readCoords.add(new int[] {100, 110}); - readCoords.add(new int[] {120, 130}); + readCoords.add(new BaseRegion(100, 110)); + readCoords.add(new BaseRegion(120, 130)); baseDepth.processRead(readCoords); assertEquals(3, baseDepth.depthAtBase(100)); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/MultiMapReadTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/MultiMapReadTest.java new file mode 100644 index 00000000000..50bcd0d295c --- /dev/null +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/MultiMapReadTest.java @@ -0,0 +1,85 @@ +package com.hartwig.hmftools.isofox; + +import static com.hartwig.hmftools.common.bam.SamRecordUtils.XA_ATTRIBUTE; +import static com.hartwig.hmftools.common.test.SamRecordTestUtils.createSamRecord; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.hartwig.hmftools.isofox.common.Read; + +import org.junit.Test; + +import htsjdk.samtools.SAMRecord; + +public class MultiMapReadTest +{ + private static final String READ_BASES = "ACGTACGTACGTACGTACGT"; // 20 bases, matches 20M + + private static SAMRecord createRead(final String xaTag) + { + SAMRecord record = createSamRecord( + "READ_01", "1", 1000, READ_BASES, "20M", "1", 1200, false, false, null); + + if(xaTag != null) + record.setAttribute(XA_ATTRIBUTE, xaTag); + + return record; + } + + @Test + public void testUniqueReadHasSingleLocus() + { + Read read = new Read(createRead(null)); + assertEquals(1, read.numLoci()); + assertNull(read.altLoci()); + } + + @Test + public void testXaParsedIntoAltLoci() + { + // standard lifted bwa XA: chr,+/-pos,CIGAR,NM; - the position sign encodes strand and must be stripped + Read read = new Read(createRead("2,+5000,20M,1;3,-8000,20M,2;")); + + assertEquals(3, read.numLoci()); + assertEquals(2, read.altLoci().size()); + + Read.AltAlignment firstAlt = read.altLoci().get(0); + assertEquals("2", firstAlt.Region.Chromosome); + assertEquals(5000, firstAlt.Region.start()); + assertEquals(5019, firstAlt.Region.end()); // 20M spans 20 reference bases + assertFalse(firstAlt.Spliced); + + Read.AltAlignment secondAlt = read.altLoci().get(1); + assertEquals("3", secondAlt.Region.Chromosome); + assertEquals(8000, secondAlt.Region.start()); + } + + @Test + public void testSplicedAltSpanFromCigar() + { + // an alt whose CIGAR has an N gap is flagged spliced and its reference span includes the skipped intron + Read read = new Read(createRead("5,+7000,10M100N10M,0;")); + + assertEquals(2, read.numLoci()); + + Read.AltAlignment alt = read.altLoci().get(0); + assertTrue(alt.Spliced); + assertEquals(7000, alt.Region.start()); + assertEquals(7119, alt.Region.end()); // 10 + 100 + 10 = 120 reference bases + } + + @Test + public void testMalformedXaEntriesTolerated() + { + // a malformed and an empty entry are skipped, leaving the two valid alternate loci + Read read = new Read(createRead("2,+5000,20M,1;garbage;;4,+9000,20M,0;")); + + assertEquals(3, read.numLoci()); + assertEquals(2, read.altLoci().size()); + assertEquals("4", read.altLoci().get(1).Region.Chromosome); + assertEquals(9000, read.altLoci().get(1).Region.start()); + } +} diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/NeoEpitopesTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/NeoEpitopesTest.java index 14388fa1c3e..e442f733059 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/NeoEpitopesTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/NeoEpitopesTest.java @@ -29,6 +29,7 @@ import com.hartwig.hmftools.common.neo.NeoEpitopeFile; import com.hartwig.hmftools.common.neo.NeoEpitopeType; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.common.Read; import com.hartwig.hmftools.isofox.neo.NeoEpitopeData; import com.hartwig.hmftools.isofox.neo.NeoFragmentSupport; @@ -73,7 +74,7 @@ public void testNovelRanges() public void testCoordsOverlap() { - int[] range1 = new int[] {10, 20}; + BaseRegion range1 = new BaseRegion(10, 20); int[] range2 = new int[] {30, 40}; assertEquals(0, calcBaseOverlap(range1, range2)); @@ -86,15 +87,21 @@ public void testCoordsOverlap() range2 = new int[] {5, 25}; assertEquals(11, calcBaseOverlap(range1, range2)); - List coords1 = Lists.newArrayList(); - coords1.add(new int[] {10, 20}); - coords1.add(new int[] {30, 40}); - coords1.add(new int[] {50, 60}); - coords1.add(new int[] {70, 80}); - - assertEquals(44, calcCoordinatesOverlap(coords1, coords1)); + List coords1 = Lists.newArrayList(); + coords1.add(new BaseRegion(10, 20)); + coords1.add(new BaseRegion(30, 40)); + coords1.add(new BaseRegion(50, 60)); + coords1.add(new BaseRegion(70, 80)); List coords2 = Lists.newArrayList(); + coords2.add(new int[] {10, 20}); + coords2.add(new int[] {30, 40}); + coords2.add(new int[] {50, 60}); + coords2.add(new int[] {70, 80}); + + assertEquals(44, calcCoordinatesOverlap(coords1, coords2)); + + coords2 = Lists.newArrayList(); coords2.add(new int[] {0, 10}); coords2.add(new int[] {21, 30}); coords2.add(new int[] {41, 50}); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/NovelJunctionsTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/NovelJunctionsTest.java index 572f61ad1f5..b0800fd730a 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/NovelJunctionsTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/NovelJunctionsTest.java @@ -21,6 +21,7 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.isofox.ReadCountsTest.REF_BASE_STR_1; import static com.hartwig.hmftools.isofox.ReadCountsTest.REF_BASE_STR_2; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.processOverlappingRegions; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -33,6 +34,7 @@ import com.hartwig.hmftools.common.gene.GeneData; import com.hartwig.hmftools.common.gene.ExonData; import com.hartwig.hmftools.common.gene.TranscriptData; +import com.hartwig.hmftools.common.test.MockRefGenome; import com.hartwig.hmftools.isofox.adjusts.FragmentSize; import com.hartwig.hmftools.isofox.common.GeneCollection; import com.hartwig.hmftools.isofox.common.GeneReadData; @@ -48,6 +50,8 @@ public class NovelJunctionsTest { + private static final String REF_BASES = REF_BASE_STR_1.repeat(50); + @Test public void testAltSpliceJunctionTypes() { @@ -58,6 +62,9 @@ public void testAltSpliceJunctionTypes() String chromosome = CHR_1; String geneId = GENE_ID_1; + MockRefGenome refGenome = (MockRefGenome)config.RefGenome; + refGenome.RefGenomeMap.put(chromosome, REF_BASES); // for homology tests + GeneData geneData = new GeneData(geneId, geneId, chromosome, (byte) 1, 100, 1500, ""); int transId1 = 1; @@ -105,7 +112,7 @@ public void testAltSpliceJunctionTypes() gene.setTranscripts(transcripts); - AltSpliceJunctionFinder asjFinder = new AltSpliceJunctionFinder(createIsofoxConfig(), ALT_SJ_COHORT_CACHE, null, null); + AltSpliceJunctionFinder asjFinder = new AltSpliceJunctionFinder(config, ALT_SJ_COHORT_CACHE, null, null); GeneCollection genes = new GeneCollection(0, Lists.newArrayList(gene)); asjFinder.setGeneData(genes); @@ -130,7 +137,7 @@ public void testAltSpliceJunctionTypes() List overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); AltSpliceJunction altSJ = asjFinder.createFromRead(read, transIds); assertEquals(NOVEL_3_PRIME, altSJ.type()); @@ -150,7 +157,7 @@ public void testAltSpliceJunctionTypes() overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); altSJ = asjFinder.createFromRead(read, transIds); altSJ.setGeneData(gene.Gene.GeneId, gene.Gene.GeneName); @@ -162,7 +169,7 @@ public void testAltSpliceJunctionTypes() read = createReadRecord(1, chromosome, 361, 409, REF_BASE_STR_1, createCigar(0, 10, 29, 10, 0)); overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); altSJ = asjFinder.createFromRead(read, transIds); altSJ.setGeneData(gene.Gene.GeneId, gene.Gene.GeneName); @@ -172,7 +179,7 @@ public void testAltSpliceJunctionTypes() read = createReadRecord(1, chromosome, 721, 779, REF_BASE_STR_1, createCigar(0, 10, 39, 10, 0)); overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); assertTrue(overlappingRegions.isEmpty()); altSJ = asjFinder.createFromRead(read, transIds); @@ -186,7 +193,7 @@ public void testAltSpliceJunctionTypes() read = createReadRecord(1, chromosome, 291, 809, REF_BASE_STR_1, createCigar(0, 10, 499, 10, 0)); overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); altSJ = asjFinder.createFromRead(read, transIds); altSJ.setGeneData(gene.Gene.GeneId, gene.Gene.GeneName); @@ -198,7 +205,7 @@ public void testAltSpliceJunctionTypes() read = createReadRecord(1, chromosome, 841, 959, REF_BASE_STR_1, createCigar(0, 10, 99, 10, 0)); overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); altSJ = asjFinder.createFromRead(read, transIds); altSJ.setGeneData(gene.Gene.GeneId, gene.Gene.GeneName); @@ -210,8 +217,8 @@ public void testAltSpliceJunctionTypes() Read read1 = createReadRecord(1, chromosome, 991, 1209, REF_BASE_STR_1, createCigar(0, 10, 199, 10, 0)); Read read2 = createReadRecord(1, chromosome, 1291, 1409, REF_BASE_STR_1, createCigar(0, 10, 99, 10, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read2.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); transIds = Lists.newArrayList(transId1); AltSpliceJunction firstAltSJ = asjFinder.createFromRead(read1, transIds); @@ -226,7 +233,7 @@ public void testAltSpliceJunctionTypes() read = createReadRecord(1, chromosome, 291, 609, REF_BASE_STR_1, createCigar(0, 10, 299, 10, 0)); overlappingRegions = gene.findOverlappingRegions(read); - read.processOverlappingRegions(overlappingRegions); + processOverlappingRegions(read, overlappingRegions); transIds = read.getTranscriptClassifications().keySet().stream().collect(Collectors.toList()); altSJ = asjFinder.createFromRead(read, transIds); @@ -245,8 +252,9 @@ public void testAltSpliceJunctionTypes() // circular exon looking like a DP Read[] readPair = createSupplementaryReadPair(1, genes, genes, 400, 419, 481, 500, createCigar(5, 20, 0), createCigar(0, 20, 5), true); - readPair[0].processOverlappingRegions(gene.findOverlappingRegions(readPair[0])); - readPair[1].processOverlappingRegions(gene.findOverlappingRegions(readPair[1])); + + processOverlappingRegions(readPair[0], gene.findOverlappingRegions(readPair[0])); + processOverlappingRegions(readPair[1], gene.findOverlappingRegions(readPair[1])); transIds = Lists.newArrayList(transId1); AltSpliceJunction circularAltSJ = asjFinder.createFromReads(readPair[0], readPair[1], transIds); @@ -318,8 +326,8 @@ public void testRetainedIntrons() Read read1 = createReadRecord(1, chromosome, 291, 310, REF_BASE_STR_1, createCigar(0, 20, 0)); Read read2 = createReadRecord(1, chromosome, 340, 360, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -329,8 +337,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 281, 320, REF_BASE_STR_2, createCigar(0, 40, 0)); read2 = createReadRecord(1, chromosome, 340, 360, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -344,8 +352,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 391, 430, REF_BASE_STR_2, createCigar(0, 40, 0)); read2 = createReadRecord(1, chromosome, 440, 460, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -361,8 +369,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 391, 430, REF_BASE_STR_2, createCigar(0, 40, 0)); read2 = createReadRecord(1, chromosome, 491, 609, REF_BASE_STR_1, createCigar(0, 10, 99, 10, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read2.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -380,8 +388,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 91, 110, REF_BASE_STR_1, createCigar(0, 20, 0)); read2 = createReadRecord(1, chromosome, 121, 140, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read2.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -390,8 +398,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 1491, 1510, REF_BASE_STR_1, createCigar(0, 20, 0)); read2 = createReadRecord(1, chromosome, 1551, 1570, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read2.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -403,8 +411,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 391, 410, REF_BASE_STR_1, createCigar(0, 20, 0)); read2 = createReadRecord(1, chromosome, 491, 510, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read2.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); @@ -415,8 +423,8 @@ public void testRetainedIntrons() read1 = createReadRecord(1, chromosome, 491, 510, REF_BASE_STR_1, createCigar(0, 20, 0)); read2 = createReadRecord(1, chromosome, 591, 610, REF_BASE_STR_1, createCigar(0, 20, 0)); - read1.processOverlappingRegions(gene.findOverlappingRegions(read1)); - read2.processOverlappingRegions(gene.findOverlappingRegions(read2)); + processOverlappingRegions(read1, gene.findOverlappingRegions(read1)); + processOverlappingRegions(read2, gene.findOverlappingRegions(read2)); riFinder.evaluateFragmentReads(read1, read2); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadCountsTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadCountsTest.java index 6a5d5598c0e..5667fd26ad2 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadCountsTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadCountsTest.java @@ -3,6 +3,7 @@ import static com.hartwig.hmftools.isofox.TestUtils.createCigar; import static com.hartwig.hmftools.isofox.TestUtils.createReadRecord; import static com.hartwig.hmftools.isofox.TestUtils.createRegion; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.processOverlappingRegions; import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_BOUNDARY; import static com.hartwig.hmftools.isofox.common.RegionMatchType.EXON_INTRON; import static com.hartwig.hmftools.isofox.common.RegionMatchType.WITHIN_EXON; @@ -26,7 +27,7 @@ public class ReadCountsTest { - public static final String REF_BASE_STR_1 = "ABCDEFGHIJKLMNOPQRST"; + public static final String REF_BASE_STR_1 = "ACGT".repeat(5); public static final String REF_BASE_STR_2 = REF_BASE_STR_1 + REF_BASE_STR_1; @Test @@ -37,15 +38,15 @@ public void testReadCoordinates() // simple matched read assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(100, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(119, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(100, read.getMappedRegionCoords().get(0).start()); + assertEquals(119, read.getMappedRegionCoords().get(0).end()); // with soft-clippings read = createReadRecord(1, "1", 100, 119, REF_BASE_STR_1, createCigar(10, 20, 10)); assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(100, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(119, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(100, read.getMappedRegionCoords().get(0).start()); + assertEquals(119, read.getMappedRegionCoords().get(0).end()); // with 2 splits Cigar cigar = new Cigar(); @@ -58,12 +59,12 @@ public void testReadCoordinates() read = createReadRecord(1, "1", 100, 159, REF_BASE_STR_1, cigar); assertEquals(3, read.getMappedRegionCoords().size()); - assertEquals(100, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(104, read.getMappedRegionCoords().get(0)[SE_END]); - assertEquals(125, read.getMappedRegionCoords().get(1)[SE_START]); - assertEquals(134, read.getMappedRegionCoords().get(1)[SE_END]); - assertEquals(165, read.getMappedRegionCoords().get(2)[SE_START]); - assertEquals(169, read.getMappedRegionCoords().get(2)[SE_END]); + assertEquals(100, read.getMappedRegionCoords().get(0).start()); + assertEquals(104, read.getMappedRegionCoords().get(0).end()); + assertEquals(125, read.getMappedRegionCoords().get(1).start()); + assertEquals(134, read.getMappedRegionCoords().get(1).end()); + assertEquals(165, read.getMappedRegionCoords().get(2).start()); + assertEquals(169, read.getMappedRegionCoords().get(2).end()); // with a delete // 10M2D8M @@ -76,8 +77,8 @@ public void testReadCoordinates() read = createReadRecord(1, "1", 100, 119, REF_BASE_STR_1.substring(0, 18), cigar); assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(100, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(119, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(100, read.getMappedRegionCoords().get(0).start()); + assertEquals(119, read.getMappedRegionCoords().get(0).end()); // with an insert // 10M2D8M @@ -90,8 +91,8 @@ public void testReadCoordinates() read = createReadRecord(1, "1", 100, 117, REF_BASE_STR_1, cigar); assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(100, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(117, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(100, read.getMappedRegionCoords().get(0).start()); + assertEquals(117, read.getMappedRegionCoords().get(0).end()); } @Test @@ -106,8 +107,8 @@ public void testReadRegionTypes() Read read = createReadRecord(1, "1", 110, 130, REF_BASE_STR_1, createCigar(0, 21, 0)); assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(110, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(130, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(110, read.getMappedRegionCoords().get(0).start()); + assertEquals(130, read.getMappedRegionCoords().get(0).end()); // test classification of reads assertEquals(WITHIN_EXON, read.getRegionMatchType(region)); @@ -132,12 +133,12 @@ public void testReadRegionTypes() read = createReadRecord(1, "1", 110, 204, REF_BASE_STR_1, cigar); assertEquals(3, read.getMappedRegionCoords().size()); - assertEquals(110, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(120, read.getMappedRegionCoords().get(0)[SE_END]); - assertEquals(140, read.getMappedRegionCoords().get(1)[SE_START]); - assertEquals(160, read.getMappedRegionCoords().get(1)[SE_END]); - assertEquals(180, read.getMappedRegionCoords().get(2)[SE_START]); - assertEquals(204, read.getMappedRegionCoords().get(2)[SE_END]); + assertEquals(110, read.getMappedRegionCoords().get(0).start()); + assertEquals(120, read.getMappedRegionCoords().get(0).end()); + assertEquals(140, read.getMappedRegionCoords().get(1).start()); + assertEquals(160, read.getMappedRegionCoords().get(1).end()); + assertEquals(180, read.getMappedRegionCoords().get(2).start()); + assertEquals(204, read.getMappedRegionCoords().get(2).end()); RegionReadData region1 = new RegionReadData("1", 100, 120); RegionReadData region2 = new RegionReadData("1", 140, 160); @@ -164,12 +165,12 @@ public void testUnmappedSpliceJunctions() createCigar(5, 10, 0)); List regions = Lists.newArrayList(region2); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(EXON_BOUNDARY, read.getRegionMatchType(region1)); assertEquals(2, read.getMappedRegionCoords().size()); - assertEquals(146, read.getMappedRegionCoords().get(0)[SE_START]); - assertEquals(150, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(146, read.getMappedRegionCoords().get(0).start()); + assertEquals(150, read.getMappedRegionCoords().get(0).end()); // test again on the up side, with 2 different regions matching the inferred bases read = createReadRecord(1, "1", 141, 155, REF_BASE_STR_1.substring(0, 15), @@ -190,44 +191,44 @@ public void testUnmappedSpliceJunctions() region3.addPreRegion(region4); regions = Lists.newArrayList(region1, region4); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region1)); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region2)); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region3)); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region4)); assertEquals(2, read.getMappedRegionCoords().size()); - assertEquals(200, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1)[SE_START]); - assertEquals(204, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1)[SE_END]); + assertEquals(200, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1).start()); + assertEquals(204, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1).end()); // test with a soft-clipped and overhanging base together read = createReadRecord(1, "1", 141, 152, REF_BASE_STR_1.substring(0, 15), createCigar(0, 12, 3)); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region1)); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region2)); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region3)); assertEquals(EXON_BOUNDARY, read.getMappedRegions().get(region4)); assertEquals(2, read.getMappedRegionCoords().size()); - assertEquals(200, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1)[SE_START]); - assertEquals(204, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1)[SE_END]); + assertEquals(200, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1).start()); + assertEquals(204, read.getMappedRegionCoords().get(read.getMappedRegionCoords().size() - 1).end()); // test again with ambiguous mapping of 1 base to more than 1 adjacent exons, and observe the read coords being truncated read = createReadRecord(1, "1", 132, 151, REF_BASE_STR_1.substring(0, 19) + REF_BASE_STR_1.substring(10, 11), createCigar(0, 20, 0)); - read.processOverlappingRegions(Lists.newArrayList(region1, region4)); + processOverlappingRegions(read, Lists.newArrayList(region1, region4)); assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(150, read.getMappedRegionCoords().get(0)[SE_END]); + assertEquals(150, read.getMappedRegionCoords().get(0).end()); read = createReadRecord(1, "1", 199, 218, REF_BASE_STR_1.substring(9, 10) + REF_BASE_STR_1.substring(0, 19), createCigar(0, 20, 0)); - read.processOverlappingRegions(Lists.newArrayList(region2, region3)); + processOverlappingRegions(read, Lists.newArrayList(region2, region3)); assertEquals(1, read.getMappedRegionCoords().size()); - assertEquals(200, read.getMappedRegionCoords().get(0)[SE_START]); + assertEquals(200, read.getMappedRegionCoords().get(0).start()); } @Test diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadUtilsTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadUtilsTest.java index 513c21a4506..21994342614 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadUtilsTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/ReadUtilsTest.java @@ -1,31 +1,38 @@ package com.hartwig.hmftools.isofox; -import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; -import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; +import static com.hartwig.hmftools.common.bam.CigarUtils.cigarFromStr; import static com.hartwig.hmftools.isofox.ReadCountsTest.REF_BASE_STR_1; import static com.hartwig.hmftools.isofox.ReadCountsTest.REF_BASE_STR_2; import static com.hartwig.hmftools.isofox.TestUtils.CHR_1; import static com.hartwig.hmftools.isofox.TestUtils.createCigar; import static com.hartwig.hmftools.isofox.TestUtils.createReadRecord; import static com.hartwig.hmftools.isofox.TestUtils.createRegion; -import static com.hartwig.hmftools.isofox.common.Read.markRegionBases; import static com.hartwig.hmftools.isofox.common.CommonUtils.deriveCommonRegions; import static com.hartwig.hmftools.isofox.common.CommonUtils.findStringOverlaps; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.markRegionBases; +import static com.hartwig.hmftools.isofox.common.ReadUtils.trimAdapterBases; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static htsjdk.samtools.CigarOperator.D; +import static htsjdk.samtools.CigarOperator.I; +import static htsjdk.samtools.CigarOperator.M; +import static htsjdk.samtools.CigarOperator.N; + import java.util.List; import com.google.common.collect.Lists; +import com.hartwig.hmftools.common.region.BaseRegion; import com.hartwig.hmftools.isofox.common.FragmentTracker; +import com.hartwig.hmftools.isofox.common.MappedCoords; import com.hartwig.hmftools.isofox.common.Read; import com.hartwig.hmftools.isofox.common.RegionReadData; import org.junit.Test; import htsjdk.samtools.Cigar; +import htsjdk.samtools.CigarElement; import htsjdk.samtools.SAMFlag; public class ReadUtilsTest @@ -46,32 +53,82 @@ public void testCigarCreation() @Test public void testMappingCoords() { - List mappings1 = Lists.newArrayList(); + List cigarElements = Lists.newArrayList( + new CigarElement(10, M), + new CigarElement(10, I), + new CigarElement(10, M), + new CigarElement(10, D), + new CigarElement(10, M)); + + MappedCoords mappedCoords = MappedCoords.build(cigarElements, 100); + assertEquals(100, mappedCoords.alignments().get(0).start()); + assertEquals(139, mappedCoords.alignments().get(0).end()); + + assertEquals(1, mappedCoords.alignmentCount()); + + assertTrue(mappedCoords.alignmentsOverlap(90, 101)); + assertTrue(mappedCoords.alignmentsOverlap(138, 150)); + assertTrue(mappedCoords.alignmentsWithin(99, 139)); + + cigarElements = Lists.newArrayList( + new CigarElement(11, M), + new CigarElement(9, N), + new CigarElement(11, M), + new CigarElement(9, N), + new CigarElement(11, M)); + + mappedCoords = MappedCoords.build(cigarElements, 100); + + assertEquals(3, mappedCoords.alignmentCount()); + assertEquals(100, mappedCoords.alignments().get(0).start()); + assertEquals(110, mappedCoords.alignments().get(0).end()); + assertEquals(120, mappedCoords.alignments().get(1).start()); + assertEquals(130, mappedCoords.alignments().get(1).end()); + assertEquals(140, mappedCoords.alignments().get(2).start()); + assertEquals(150, mappedCoords.alignments().get(2).end()); + + assertEquals(3, mappedCoords.alignmentCount()); + assertEquals(3, mappedCoords.originalAlignmentCount()); + + // now add inferred sections + mappedCoords.addInferredRegion(true, 80, 90); + + assertEquals(4, mappedCoords.alignmentCount()); + assertEquals(3, mappedCoords.originalAlignmentCount()); + + mappedCoords.addInferredRegion(false, 160, 170); + assertEquals(5, mappedCoords.alignmentCount()); + } + + @Test + public void testOverlappingCoordinates() + { + List mappings1 = Lists.newArrayList(); // no overlaps - mappings1.add(new int[] { 10, 20 }); - mappings1.add(new int[] { 40, 50 }); + mappings1.add(new BaseRegion(10, 20)); + mappings1.add(new BaseRegion(40, 50)); - List mappings2 = Lists.newArrayList(); + List mappings2 = Lists.newArrayList(); - mappings2.add(new int[] { 60, 70 }); - mappings2.add(new int[] { 80, 90 }); + mappings2.add(new BaseRegion(60, 70)); + mappings2.add(new BaseRegion(80, 90)); - List commonMappings = deriveCommonRegions(mappings1, mappings2); + List commonMappings = deriveCommonRegions(mappings1, mappings2); assertEquals(4, commonMappings.size()); mappings1.clear(); mappings2.clear(); // widening of all regions only - mappings1.add(new int[] { 10, 20 }); - mappings1.add(new int[] { 40, 50 }); - mappings1.add(new int[] { 70, 80 }); + mappings1.add(new BaseRegion(10, 20)); + mappings1.add(new BaseRegion(40, 50)); + mappings1.add(new BaseRegion(70, 80)); // no overlaps - mappings2.add(new int[] { 25, 35 }); - mappings2.add(new int[] { 55, 65 }); - mappings2.add(new int[] { 85, 95 }); + mappings2.add(new BaseRegion(25, 35)); + mappings2.add(new BaseRegion(55, 65)); + mappings2.add(new BaseRegion(85, 95)); commonMappings = deriveCommonRegions(mappings1, mappings2); assertEquals(6, commonMappings.size()); @@ -85,51 +142,51 @@ public void testMappingCoords() // widening of all regions only mappings2.clear(); - mappings2.add(new int[] { 5, 15 }); - mappings2.add(new int[] { 35, 45 }); - mappings2.add(new int[] { 55, 75 }); + mappings2.add(new BaseRegion(5, 15)); + mappings2.add(new BaseRegion(35, 45)); + mappings2.add(new BaseRegion(55, 75)); commonMappings = deriveCommonRegions(mappings1, mappings2); assertEquals(3, commonMappings.size()); - assertEquals(5, commonMappings.get(0)[SE_START]); - assertEquals(20, commonMappings.get(0)[SE_END]); - assertEquals(35, commonMappings.get(1)[SE_START]); - assertEquals(50, commonMappings.get(1)[SE_END]); - assertEquals(55, commonMappings.get(2)[SE_START]); - assertEquals(80, commonMappings.get(2)[SE_END]); + assertEquals(5, commonMappings.get(0).start()); + assertEquals(20, commonMappings.get(0).end()); + assertEquals(35, commonMappings.get(1).start()); + assertEquals(50, commonMappings.get(1).end()); + assertEquals(55, commonMappings.get(2).start()); + assertEquals(80, commonMappings.get(2).end()); // one other region overlapping all others mappings2.clear(); - mappings2.add(new int[] { 5, 95 }); + mappings2.add(new BaseRegion(5, 95)); commonMappings = deriveCommonRegions(mappings1, mappings2); assertEquals(1, commonMappings.size()); - assertEquals(5, commonMappings.get(0)[SE_START]); - assertEquals(95, commonMappings.get(0)[SE_END]); + assertEquals(5, commonMappings.get(0).start()); + assertEquals(95, commonMappings.get(0).end()); mappings2.clear(); mappings1.clear(); // a mix of various scenarios - mappings1.add(new int[] { 10, 20 }); + mappings1.add(new BaseRegion(10, 20)); - mappings2.add(new int[] { 30, 40 }); + mappings2.add(new BaseRegion(30, 40)); - mappings2.add(new int[] { 50, 60 }); - mappings1.add(new int[] { 55, 75 }); - mappings1.add(new int[] { 85, 95 }); - mappings2.add(new int[] { 70, 110 }); + mappings2.add(new BaseRegion(50, 60)); + mappings1.add(new BaseRegion(55, 75)); + mappings1.add(new BaseRegion(85, 95)); + mappings2.add(new BaseRegion(70, 110)); - mappings2.add(new int[] { 120, 130 }); + mappings2.add(new BaseRegion(120, 130)); - mappings1.add(new int[] { 140, 150 }); + mappings1.add(new BaseRegion(140, 150)); commonMappings = deriveCommonRegions(mappings1, mappings2); assertEquals(5, commonMappings.size()); - assertEquals(50, commonMappings.get(2)[SE_START]); - assertEquals(110, commonMappings.get(2)[SE_END]); + assertEquals(50, commonMappings.get(2).start()); + assertEquals(110, commonMappings.get(2).end()); } @Test @@ -164,8 +221,8 @@ public void testBaseAssignment() RegionReadData region = createRegion("GEN01", 1, 1, "1", 100, 119); region.setRefBases(REF_BASE_STR_1); - List readCoords = Lists.newArrayList(); - readCoords.add(new int[] { 100, 119 }); + List readCoords = Lists.newArrayList(); + readCoords.add(new BaseRegion(100, 119)); markRegionBases(readCoords, region); assertEquals(20, region.baseCoverage(1)); @@ -173,9 +230,9 @@ public void testBaseAssignment() region.clearState(); readCoords.clear(); - readCoords.add(new int[] { 100, 104 }); - readCoords.add(new int[] { 110, 114 }); - readCoords.add(new int[] { 118, 119 }); + readCoords.add(new BaseRegion(100, 104)); + readCoords.add(new BaseRegion(110, 114)); + readCoords.add(new BaseRegion(118, 119)); markRegionBases(readCoords, region); assertEquals(12, region.baseCoverage(1)); @@ -213,14 +270,31 @@ public void testBaseComparisons() } @Test - public void testAdapterTrimming() + public void testAdapterTrimming2() { Read read1 = createReadRecord(1, CHR_1, 105, 124, REF_BASE_STR_2, createCigar(10, 20, 10)); Read read2 = createReadRecord(1, CHR_1, 100, 119, REF_BASE_STR_2, createCigar(10, 20, 10)); read2.setFlag(SAMFlag.READ_REVERSE_STRAND, true); - - read1.trimAdapterSoftClipBases(read2); - read2.trimAdapterSoftClipBases(read1); + + assertEquals(95, read1.unclippedStart()); + assertEquals(134, read1.unclippedEnd()); + assertEquals(90, read2.unclippedStart()); + assertEquals(129, read2.unclippedEnd()); + + trimAdapterBases(read1, read2); + + assertEquals(129, read1.unclippedEnd()); + assertEquals(95, read2.unclippedStart()); + + assertEquals("10S20M5S", read1.cigarStr()); + assertEquals("5S20M10S", read2.cigarStr()); + + // test passing in reversed + read1 = createReadRecord(1, CHR_1, 105, 124, REF_BASE_STR_2, createCigar(10, 20, 10)); + read2 = createReadRecord(1, CHR_1, 100, 119, REF_BASE_STR_2, createCigar(10, 20, 10)); + read2.setFlag(SAMFlag.READ_REVERSE_STRAND, true); + + trimAdapterBases(read2, read1); assertEquals(129, read1.unclippedEnd()); assertEquals(95, read2.unclippedStart()); @@ -233,13 +307,59 @@ public void testAdapterTrimming() read2 = createReadRecord(1, CHR_1, 94, 113, REF_BASE_STR_2, createCigar(10, 20, 10)); read2.setFlag(SAMFlag.READ_REVERSE_STRAND, true); - read1.trimAdapterSoftClipBases(read2); - read2.trimAdapterSoftClipBases(read1); + assertEquals(95, read1.unclippedStart()); + assertEquals(134, read1.unclippedEnd()); + assertEquals(84, read2.unclippedStart()); + assertEquals(123, read2.unclippedEnd()); + + trimAdapterBases(read1, read2); assertEquals(124, read1.unclippedEnd()); assertEquals(94, read2.unclippedStart()); assertEquals("10S20M", read1.cigarStr()); assertEquals("20M10S", read2.cigarStr()); + + // test a scenario where N-splitting has lead to conflicting alignments, requiring position indices to find the point of adapter SCs + + // test 1: no trimming + // read 1: 100-114 - 886N - 1000-1019 - 5S + // read 2: 5S - 1000-1019 - 981N - 2000-2014 + + read1 = createReadRecord(1, CHR_1, 100, 1019, REF_BASE_STR_2, cigarFromStr("15M885N20M5S")); + read2 = createReadRecord(1, CHR_1, 1000, 2014, REF_BASE_STR_2, cigarFromStr("5S20M980N15M")); + read2.setFlag(SAMFlag.READ_REVERSE_STRAND, true); + + assertEquals(100, read1.unclippedStart()); + assertEquals(1024, read1.unclippedEnd()); + assertEquals(995, read2.unclippedStart()); + assertEquals(2014, read2.unclippedEnd()); + + trimAdapterBases(read2, read1); + + assertEquals(100, read1.unclippedStart()); + assertEquals(1024, read1.unclippedEnd()); + assertEquals(995, read2.unclippedStart()); + assertEquals(2014, read2.unclippedEnd()); + + // test 2: trim from both sides + // read 1: 110-114 - 886N - 1000-1019 - 10S + // read 2: 10S - 1000-1019 - 981N - 2000-2004 + read1 = createReadRecord(1, CHR_1, 110, 1019, REF_BASE_STR_2, cigarFromStr("5M885N20M10S")); + read2 = createReadRecord(1, CHR_1, 1000, 2004, REF_BASE_STR_2, cigarFromStr("10S20M980N5M")); + read2.setFlag(SAMFlag.READ_REVERSE_STRAND, true); + + assertEquals(110, read1.unclippedStart()); + assertEquals(1029, read1.unclippedEnd()); + assertEquals(990, read2.unclippedStart()); + assertEquals(2004, read2.unclippedEnd()); + + trimAdapterBases(read2, read1); + + assertEquals(1024, read1.unclippedEnd()); + assertEquals(995, read2.unclippedStart()); + + assertEquals("5M885N20M5S", read1.cigarStr()); + assertEquals("5S20M980N5M", read2.cigarStr()); } } \ No newline at end of file diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/TestUtils.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/TestUtils.java index 7be89da2f42..89664e88625 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/TestUtils.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/TestUtils.java @@ -1,5 +1,7 @@ package com.hartwig.hmftools.isofox; +import static java.lang.String.format; + import static com.hartwig.hmftools.common.test.GeneTestUtils.addGeneData; import static com.hartwig.hmftools.common.test.GeneTestUtils.addTransExonData; import static com.hartwig.hmftools.common.test.GeneTestUtils.createEnsemblGeneData; @@ -11,11 +13,14 @@ import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; import static com.hartwig.hmftools.isofox.IsofoxConstants.SINGLE_MAP_QUALITY; import static com.hartwig.hmftools.isofox.common.Read.findOverlappingRegions; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.processOverlappingRegions; +import static com.hartwig.hmftools.isofox.fusion.FusionDataTest.suppDataFromRead; import static htsjdk.samtools.CigarOperator.D; import static htsjdk.samtools.CigarOperator.N; import static htsjdk.samtools.SAMFlag.FIRST_OF_PAIR; import static htsjdk.samtools.SAMFlag.SECOND_OF_PAIR; +import static htsjdk.samtools.SAMFlag.SUPPLEMENTARY_ALIGNMENT; import java.util.List; @@ -25,6 +30,7 @@ import com.hartwig.hmftools.common.gene.TranscriptData; import com.hartwig.hmftools.common.test.MockRefGenome; import com.hartwig.hmftools.common.genome.refgenome.RefGenomeInterface; +import com.hartwig.hmftools.common.test.SamRecordTestUtils; import com.hartwig.hmftools.isofox.common.GeneCollection; import com.hartwig.hmftools.isofox.common.GeneReadData; import com.hartwig.hmftools.isofox.common.Read; @@ -42,6 +48,7 @@ import htsjdk.samtools.CigarElement; import htsjdk.samtools.CigarOperator; import htsjdk.samtools.SAMFlag; +import htsjdk.samtools.SAMRecord; public class TestUtils { @@ -270,22 +277,48 @@ public static Read createReadRecord( } public static Read createReadRecord( - final int id, final String chromosome, int posStart, int posEnd, final String readBases, final Cigar cigar, + final int id, final String chromosome, int posStart, int posEnd, final String specificReadBases, final Cigar cigar, int flags, final String mateChr, int mateStartPos) { - Cigar readCigar = cigar != null ? cigar : createCigar(0, (int) (posEnd - posStart + 1), 0); + int readAlignmentSpan = posEnd - posStart + 1; - Read read = new Read(String.valueOf(id), chromosome, posStart, posEnd, readBases, readCigar, - 0, flags, mateChr, mateStartPos); + String cigarStr = cigar != null ? cigar.toString() : format("%dM", readAlignmentSpan); + // Cigar readCigar = cigar != null ? cigar : createCigar(0, (int) (posEnd - posStart + 1), 0); - read.setFlag(SAMFlag.PROPER_PAIR, true); - read.setFlag(SAMFlag.READ_PAIRED, true); - read.setStrand(false, true); - read.setMapQuality(SINGLE_MAP_QUALITY); - return read; + String readBases = specificReadBases; + + if(readBases == null) + { + int readBaseLength = 0; + + if(cigar != null) + { + readBaseLength = cigar.getCigarElements().stream() + .filter(x -> x.getOperator().consumesReadBases()).mapToInt(x -> x.getLength()).sum(); + } + else + { + readBaseLength = readAlignmentSpan; + } + + readBases = generateRandomBases(readBaseLength); + + } + + SAMRecord record = SamRecordTestUtils.createSamRecord( + String.valueOf(id), chromosome, posStart, readBases, cigarStr, mateChr, mateStartPos, + false, false, null); + + record.setFlags(flags); + record.setMappingQuality(SINGLE_MAP_QUALITY); + record.setProperPairFlag(true); + record.setReadPairedFlag(true); + + return new Read(record); } - public static Read[] createSupplementaryReadPair(final int id, final GeneCollection gc1, final GeneCollection gc2, + public static Read[] createSupplementaryReadPair( + final int id, final GeneCollection gc1, final GeneCollection gc2, int posStart1, int posEnd1, int posStart2, int posEnd2, final Cigar cigar1, final Cigar cigar2, boolean firstInPair) { int readBaseLength = cigar1.getCigarElements().stream() @@ -297,25 +330,18 @@ public static Read[] createSupplementaryReadPair(final int id, final GeneCollect Read read1 = createMappedRead(id, gc1, posStart1, posEnd1, cigar1, readBases); Read read2 = createMappedRead(id, gc2, posStart2, posEnd2, cigar2, readBases); read1.setFlag(FIRST_OF_PAIR, firstInPair); - read2.setFlag(SECOND_OF_PAIR, !firstInPair); + read2.setFlag(FIRST_OF_PAIR, firstInPair); + read2.setFlag(SUPPLEMENTARY_ALIGNMENT, true); // note: strand is not currently set correctly - SupplementaryReadData suppData1 = new SupplementaryReadData( - read2.Chromosome, read2.PosStart, '+', read2.cigarStr(), 255); - - read1.setSuppAlignment(suppData1.asDelimStr()); - // read1.setSuppAlignment(String.format("%s;%d;%s", read2.Chromosome, read2.PosStart, read2.Cigar.toString())); - - SupplementaryReadData suppData2 = new SupplementaryReadData( - read1.Chromosome, read1.PosStart, '+', read1.cigarStr(), 255); - - read2.setSuppAlignment(suppData2.asDelimStr()); - // read2.setSuppAlignment(String.format("%s;%d;%s", read1.Chromosome, read1.PosStart, read1.Cigar.toString())); + read1.setSuppAlignment(suppDataFromRead(read2).asSamTag()); + read2.setSuppAlignment(suppDataFromRead(read1).asSamTag()); return new Read[] { read1, read2 }; } - public static Read[] createReadPair(final int id, final GeneCollection gc1, final GeneCollection gc2, + public static Read[] createReadPair( + final int id, final GeneCollection gc1, final GeneCollection gc2, int posStart1, int posEnd1, int posStart2, int posEnd2, final Cigar cigar1, final Cigar cigar2, byte orient1, byte orient2) { int readBaseLength = cigar1.getCigarElements().stream() @@ -335,6 +361,12 @@ public static Read[] createReadPair(final int id, final GeneCollection gc1, fina return new Read[] { read1, read2 }; } + public static void setReadFirstSecondInPair(final Read read, boolean isFirst) + { + read.setFlag(FIRST_OF_PAIR, isFirst); + read.setFlag(SECOND_OF_PAIR, !isFirst); + } + public static Read createMappedRead(final int id, final GeneCollection geneCollection, int posStart, int posEnd, final Cigar cigar) { int readBaseLength = cigar.getCigarElements().stream() @@ -351,7 +383,7 @@ public static Read createMappedRead( { Read read = createReadRecord(id, geneCollection.chromosome(), posStart, posEnd, readBases, cigar); - read.processOverlappingRegions(findOverlappingRegions(geneCollection.getExonRegions(), read)); + processOverlappingRegions(read, findOverlappingRegions(geneCollection.getExonRegions(), read)); if(read.getMappedRegions().isEmpty()) read.addIntronicTranscriptRefs(geneCollection.getTranscripts()); @@ -425,8 +457,6 @@ public static FusionFinder createFusionFinder(final IsofoxConfig config, final E public static FusionFinder createFusionFinder( final IsofoxConfig config, final EnsemblDataCache geneTransCache, final RacFragmentCache racFragmentCache) { - config.Filters.buildGeneRegions(geneTransCache); - return new FusionFinder( "FF", config, geneTransCache, racFragmentCache, new PassingFusions(config.Fusions.KnownFusions, null), new FusionWriter(config)); @@ -448,7 +478,7 @@ public static void addRacJunction( public static void addRacReadGroup( final RacFragmentCache racFragmentCache, final ChimericReadGroup readGroup, byte juncOrient, int juncPosition) { - String chromosome = readGroup.reads().get(0).Chromosome; + String chromosome = readGroup.reads().get(0).chromosome(); int gcId = readGroup.reads().get(0).getGeneCollectons()[SE_START]; JunctionRacFragments juncRacFragments = racFragmentCache.getRacFragments(chromosome, gcId); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/TransClassificationTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/TransClassificationTest.java index 9d5ab713f2d..329d72783f6 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/TransClassificationTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/TransClassificationTest.java @@ -1,6 +1,7 @@ package com.hartwig.hmftools.isofox; -import static com.hartwig.hmftools.isofox.FragmentAllocator.calcFragmentLength; +import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_FWD; +import static com.hartwig.hmftools.common.genome.region.Orientation.ORIENT_REV; import static com.hartwig.hmftools.isofox.IsofoxFunction.TRANSCRIPT_COUNTS; import static com.hartwig.hmftools.isofox.ReadCountsTest.REF_BASE_STR_1; import static com.hartwig.hmftools.isofox.TestUtils.ALT_SJ_COHORT_CACHE; @@ -11,9 +12,12 @@ import static com.hartwig.hmftools.isofox.TestUtils.createCigar; import static com.hartwig.hmftools.isofox.TestUtils.createGeneReadData; import static com.hartwig.hmftools.isofox.TestUtils.createIsofoxConfig; +import static com.hartwig.hmftools.isofox.TestUtils.createReadPair; import static com.hartwig.hmftools.isofox.TestUtils.createReadRecord; import static com.hartwig.hmftools.isofox.TestUtils.createRegion; import static com.hartwig.hmftools.isofox.common.FragmentType.CHIMERIC; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.calcFragmentLength; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.processOverlappingRegions; import static com.hartwig.hmftools.isofox.common.TransMatchType.ALT; import static com.hartwig.hmftools.isofox.common.TransMatchType.EXONIC; import static com.hartwig.hmftools.isofox.common.TransMatchType.SPLICE_JUNCTION; @@ -53,7 +57,7 @@ public void testReadTranscriptClassification() Read read = createReadRecord(1, "1", 90, 110, REF_BASE_STR_1, createCigar(0, 21, 0)); List regions = Lists.newArrayList(region); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(UNSPLICED, read.getTranscriptClassification(trans1)); @@ -61,7 +65,7 @@ public void testReadTranscriptClassification() read = createReadRecord(1, "1", 120, 140, REF_BASE_STR_1, createCigar(0, 21, 0)); regions = Lists.newArrayList(region); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(EXONIC, read.getTranscriptClassification(trans1)); @@ -74,7 +78,7 @@ public void testReadTranscriptClassification() read = createReadRecord(1, "1", 110, 200, REF_BASE_STR_1, createCigar(0, 11, 59, 21, 0)); regions = Lists.newArrayList(region1, region2, region3); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(ALT, read.getTranscriptClassification(trans1)); @@ -88,7 +92,7 @@ public void testReadTranscriptClassification() read = createReadRecord(1, "1", 110, 200, REF_BASE_STR_1, cigar); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(ALT, read.getTranscriptClassification(trans1)); @@ -102,7 +106,7 @@ public void testReadTranscriptClassification() read = createReadRecord(1, "1", 110, 200, REF_BASE_STR_1, cigar); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(UNSPLICED, read.getTranscriptClassification(trans1)); @@ -117,7 +121,7 @@ public void testReadTranscriptClassification() region3 = createRegion("GEN01", trans2, 1, "1", 100, 220); regions = Lists.newArrayList(region1, region2, region3); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(SPLICE_JUNCTION, read.getTranscriptClassification(trans1)); assertEquals(ALT, read.getTranscriptClassification(trans2)); @@ -143,31 +147,54 @@ public void testSoftClippedReadTranscriptClassification() Read read = createReadRecord(1, CHR_1, 200, 309, readBases, createCigar(1, 20, 80, 10, 0)); List regions = Lists.newArrayList(region2, region3); - read.processOverlappingRegions(regions); + processOverlappingRegions(read, regions); assertEquals(SPLICE_JUNCTION, read.getTranscriptClassification(TRANS_1)); // any soft-clipped read which cannot be mapped is classified as ALT readBases = REF_BASE_STR_1 + "AAAAA"; read = createReadRecord(1, CHR_1, 100, 119, readBases, createCigar(0, 20, 5)); - read.setFragmentInsertSize(200); - read.processOverlappingRegions(Lists.newArrayList(region1)); + read.bamRecord().setInferredInsertSize(200); + processOverlappingRegions(read, Lists.newArrayList(region1)); assertEquals(ALT, read.getTranscriptClassification(TRANS_1)); readBases = "AAAAA" + REF_BASE_STR_1; read = createReadRecord(1, CHR_1, 300, 319, readBases, createCigar(5, 20, 0)); - read.setFragmentInsertSize(200); - read.processOverlappingRegions(Lists.newArrayList(region3)); + read.bamRecord().setInferredInsertSize(200); + processOverlappingRegions(read, Lists.newArrayList(region3)); assertEquals(ALT, read.getTranscriptClassification(TRANS_1)); // likely adapter sequences are permitted read = createReadRecord(1, CHR_1, 300, 319, readBases, createCigar(5, 20, 0)); - read.setFragmentInsertSize(20); - read.processOverlappingRegions(Lists.newArrayList(region3)); + read.bamRecord().setInferredInsertSize(20); + processOverlappingRegions(read, Lists.newArrayList(region3)); assertEquals(EXONIC, read.getTranscriptClassification(TRANS_1)); + + // short terminal soft clip whose bases stay within the exon is left trans-supporting, not demoted to ALT + readBases = REF_BASE_STR_1.substring(0, 18) + "A"; + read = createReadRecord(1, CHR_1, 300, 317, readBases, createCigar(0, 18, 1)); + read.bamRecord().setInferredInsertSize(200); + processOverlappingRegions(read, Lists.newArrayList(region3)); + + assertEquals(EXONIC, read.getTranscriptClassification(TRANS_1)); + + readBases = "AA" + REF_BASE_STR_1.substring(0, 18); + read = createReadRecord(1, CHR_1, 102, 119, readBases, createCigar(2, 18, 0)); + read.bamRecord().setInferredInsertSize(200); + processOverlappingRegions(read, Lists.newArrayList(region1)); + + assertEquals(EXONIC, read.getTranscriptClassification(TRANS_1)); + + // clip at the realignment window length stays ALT + readBases = REF_BASE_STR_1.substring(0, 17) + "AAA"; + read = createReadRecord(1, CHR_1, 300, 316, readBases, createCigar(0, 17, 3)); + read.bamRecord().setInferredInsertSize(200); + processOverlappingRegions(read, Lists.newArrayList(region3)); + + assertEquals(ALT, read.getTranscriptClassification(TRANS_1)); } @Test @@ -185,35 +212,43 @@ public void testFragmentLengthCalcs() // within 1st exon Read read1 = createReadRecord(1, CHR_1, 1010, 1029, REF_BASE_STR_1, createCigar(0, 20, 0)); - Read read2 = createReadRecord(1, CHR_1, 1170, 1199, REF_BASE_STR_1, createCigar(0, 20, 0)); + Read read2 = createReadRecord(1, CHR_1, 1170, 1199, REF_BASE_STR_1, createCigar(0, 30, 0)); int fragLength = calcFragmentLength(transData, read1, read2); assertEquals(190, fragLength); // spanning 2 exons, both exonic read1 = createReadRecord(1, CHR_1, 1170, 1189, REF_BASE_STR_1, createCigar(0, 20, 0)); - read2 = createReadRecord(1, CHR_1, 2010, 2019, REF_BASE_STR_1, createCigar(0, 20, 0)); + read2 = createReadRecord(1, CHR_1, 2010, 2019, REF_BASE_STR_1, createCigar(0, 10, 0)); fragLength = calcFragmentLength(transData, read1, read2); assertEquals(31 + 20, fragLength); // spanning 3 exons, both exonic read1 = createReadRecord(1, CHR_1, 1170, 1189, REF_BASE_STR_1, createCigar(0, 20, 0)); - read2 = createReadRecord(1, CHR_1, 4510, 4519, REF_BASE_STR_1, createCigar(0, 20, 0)); + read2 = createReadRecord(1, CHR_1, 4510, 4519, REF_BASE_STR_1, createCigar(0, 10, 0)); fragLength = calcFragmentLength(transData, read1, read2); assertEquals(31 + 501 + 20, fragLength); // with 2 split reads - read1 = createReadRecord(1, CHR_1, 1191, 2009, REF_BASE_STR_1, createCigar(0, 10, 799, 10, 0)); - read2 = createReadRecord(1, CHR_1, 2491, 4509, REF_BASE_STR_1, createCigar(0, 10, 1999, 10, 0)); + read1 = createReadRecord( + 1, CHR_1, 1191, 2009, REF_BASE_STR_1, createCigar(0, 10, 799, 10, 0)); + read2 = createReadRecord( + 1, CHR_1, 2491, 4509, REF_BASE_STR_1, createCigar(0, 10, 1999, 10, 0)); fragLength = calcFragmentLength(transData, read1, read2); assertEquals(10 + 501 + 10, fragLength); // with 2 split reads skipping an exon - read1 = createReadRecord(1, CHR_1, 1191, 2009, REF_BASE_STR_1, createCigar(0, 10, 799, 10, 0)); - read2 = createReadRecord(1, CHR_1, 4991, 5509, REF_BASE_STR_1, createCigar(0, 10, 1999, 10, 0)); + + // 1191-1200, 2000-2009 + read1 = createReadRecord( + 1, CHR_1, 1191, 2009, REF_BASE_STR_1, createCigar(0, 10, 799, 10, 0)); + + // 4991-5000, 5500-5509 + read2 = createReadRecord( + 1, CHR_1, 4991, 5509, REF_BASE_STR_1, createCigar(0, 10, 499, 10, 0)); fragLength = calcFragmentLength(transData, read1, read2); assertEquals(10 + 501 + 501 + 10, fragLength); @@ -226,7 +261,7 @@ public void testFragmentReadPairs() IsofoxConfig config = createIsofoxConfig(); config.Functions.clear(); config.Functions.add(TRANSCRIPT_COUNTS); - FragmentAllocator bamReader = new FragmentAllocator(config, ALT_SJ_COHORT_CACHE, new ResultsWriter(config)); + FragmentAllocator bamReader = new FragmentAllocator(config, null, ALT_SJ_COHORT_CACHE, new ResultsWriter(config)); String transName1 = "TRANS01"; @@ -240,8 +275,8 @@ public void testFragmentReadPairs() GeneReadData geneReadData = createGeneReadData(GENE_NAME_1, "1", (byte) 1, 1000, 5000); geneReadData.setTranscripts(Lists.newArrayList(transData1)); - Read read1 = createReadRecord(1, CHR_1, 100, 200, REF_BASE_STR_1, createCigar(0, 10, 0)); - Read read2 = createReadRecord(1, CHR_1, 1050, 1150, REF_BASE_STR_1, createCigar(0, 20, 0)); + Read read1 = createReadRecord(1, CHR_1, 100, 200, null, createCigar(0, 101, 0)); + Read read2 = createReadRecord(1, CHR_1, 1050, 1150, null, createCigar(0, 101, 0)); List reads = Lists.newArrayList(read1, read2); @@ -249,77 +284,68 @@ public void testFragmentReadPairs() bamReader.processReadRecords(geneSet, reads); FragmentTypeCounts fragTypeCounts = geneSet.fragmentTypeCounts(); - // assertEquals(1, geneCounts[typeAsInt(TOTAL)]); assertEquals(1, fragTypeCounts.typeCount(CHIMERIC)); // exon to intronic read - read1 = createReadRecord(1, CHR_1, 1300, 1350, REF_BASE_STR_1, createCigar(0, 50, 0)); - read1.setFragmentInsertSize(1100); - read2 = createReadRecord(1, CHR_1, 2300, 2400, REF_BASE_STR_1, createCigar(0, 100, 0)); - read2.setFragmentInsertSize(-1100); + Read[] readPair = createReadPair( + 1 , geneSet, geneSet, 1300, 1350, 2300, 2400, + createCigar(0, 51, 0), createCigar(0, 101, 0), ORIENT_FWD, ORIENT_REV); - reads = Lists.newArrayList(read1, read2); + reads = Lists.newArrayList(readPair[0], readPair[1]); bamReader.processReadRecords(geneSet, reads); - // assertEquals(2, geneCounts[typeAsInt(TOTAL)]); assertEquals(1, fragTypeCounts.typeCount(FragmentType.UNSPLICED)); // fully intronic - read1 = createReadRecord(1, CHR_1, 2600, 2650, REF_BASE_STR_1, createCigar(0, 50, 0)); - read1.setFragmentInsertSize(300); - read2 = createReadRecord(1, CHR_1, 2800, 2900, REF_BASE_STR_1, createCigar(0, 100, 0)); - read2.setFragmentInsertSize(-300); + readPair = createReadPair( + 1 , geneSet, geneSet, 2600, 2650, 2800, 2900, + createCigar(0, 51, 0), createCigar(0, 101, 0), ORIENT_FWD, ORIENT_REV); geneSet.clearCounts(); - reads = Lists.newArrayList(read1, read2); + reads = Lists.newArrayList(readPair[0], readPair[1]); bamReader.processReadRecords(geneSet, reads); - // assertEquals(1, geneCounts[typeAsInt(TOTAL)]); assertEquals(1, fragTypeCounts.typeCount(FragmentType.UNSPLICED)); // alternative splicing - first from reads with splits geneSet.clearCounts(); - read1 = createReadRecord(1, CHR_1, 1050, 6100, REF_BASE_STR_1, createCigar(0, 50, 5000, 100, 0)); - read1.setFragmentInsertSize(500); - read2 = createReadRecord(1, CHR_1, 3100, 3300, REF_BASE_STR_1, createCigar(0, 100, 0)); - read2.setFragmentInsertSize(-500); + // 1050-1100, 6000-6100 + readPair = createReadPair( + 1 , geneSet, geneSet, 1050, 6100, 3100, 330, + createCigar(0, 51, 4899, 101, 0), + createCigar(0, 201, 0), ORIENT_FWD, ORIENT_REV); - reads = Lists.newArrayList(read1, read2); + reads = Lists.newArrayList(readPair[0], readPair[1]); bamReader.processReadRecords(geneSet, reads); - // assertEquals(1, geneCounts[typeAsInt(TOTAL)]); assertEquals(1, fragTypeCounts.typeCount(CHIMERIC)); - int longInsertSize = config.MaxFragmentLength + 100; - // long exon to exon read, treated as supporting geneSet.clearCounts(); - read1 = createReadRecord(1, CHR_1, 1050, 1099, REF_BASE_STR_1, createCigar(0, 50, 0)); - read1.setFragmentInsertSize(longInsertSize); - read2 = createReadRecord(1, CHR_1, 2100, 2199, REF_BASE_STR_1, createCigar(0, 100, 0)); - read2.setFragmentInsertSize(-longInsertSize); + readPair = createReadPair( + 1 , geneSet, geneSet, 1050, 1099, 2100, 2199, + createCigar(0, 50, 0), + createCigar(0, 100, 0), ORIENT_FWD, ORIENT_REV); - reads = Lists.newArrayList(read1, read2); + reads = Lists.newArrayList(readPair[0], readPair[1]); bamReader.processReadRecords(geneSet, reads); - // assertEquals(1, geneCounts[typeAsInt(TOTAL)]); assertEquals(1, fragTypeCounts.typeCount(FragmentType.TRANS_SUPPORTING)); // alt splicing - exon to exon read skipping an exon and long, currently not detected geneSet.clearCounts(); - read1 = createReadRecord(1, CHR_1, 1050, 1100, REF_BASE_STR_1, createCigar(0, 50, 0)); - read1.setFragmentInsertSize(longInsertSize); - read2 = createReadRecord(1, CHR_1, 4550, 4650, REF_BASE_STR_1, createCigar(0, 100, 0)); - read2.setFragmentInsertSize(-longInsertSize); + readPair = createReadPair( + 1 , geneSet, geneSet, 1050, 1100, 4550, 4650, + createCigar(0, 51, 0), + createCigar(0, 101, 0), ORIENT_FWD, ORIENT_REV); - reads = Lists.newArrayList(read1, read2); + reads = Lists.newArrayList(readPair[0], readPair[1]); bamReader.processReadRecords(geneSet, reads); - // assertEquals(1, geneCounts[typeAsInt(TOTAL)]); assertEquals(1, fragTypeCounts.typeCount(FragmentType.ALT)); } diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTest.java index 466ee9ceac3..03cc496d77d 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/ChimericReadTest.java @@ -22,6 +22,7 @@ import static com.hartwig.hmftools.isofox.TestUtils.createMappedRead; import static com.hartwig.hmftools.isofox.TestUtils.createReadRecord; import static com.hartwig.hmftools.isofox.TestUtils.createRegion; +import static com.hartwig.hmftools.isofox.common.ReadTranscriptUtils.processOverlappingRegions; import static com.hartwig.hmftools.isofox.common.TransMatchType.ALT; import static com.hartwig.hmftools.isofox.common.TransMatchType.SPLICE_JUNCTION; @@ -50,14 +51,14 @@ public class ChimericReadTest @Test public void testBasicReads() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); int gcId = 0; - final GeneCollection gc1 = + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); IsofoxConfig config = createIsofoxConfig(); @@ -81,7 +82,7 @@ public void testBasicReads() chimericRT.postProcessChimericReads(baseDepth, fragTracker); assertEquals(1, chimericRT.fusionReadGroupMap().size()); - assertEquals(2, chimericRT.fusionReadGroupMap().get(read1.Id).size()); + assertEquals(2, chimericRT.fusionReadGroupMap().get(read1.id()).size()); assertEquals(1, chimericRT.getJunctionRacGroups().junctionCount()); assertTrue(chimericRT.getJunctionRacGroups().getJunctionGroups(ORIENT_FWD).containsKey(1100)); @@ -112,7 +113,7 @@ public void testBasicReads() @Test public void testSameGeneCollection() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -123,7 +124,7 @@ public void testSameGeneCollection() List geneDataList = Lists.newArrayList( geneTransCache.getGeneDataById(GENE_ID_1), geneTransCache.getGeneDataById(GENE_ID_2)); - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, geneDataList); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, geneDataList); IsofoxConfig config = createIsofoxConfig(); config.Functions.add(FUSIONS); @@ -189,7 +190,7 @@ public void testSameGeneCollection() geneDataList = Lists.newArrayList( geneTransCache.getGeneDataById(GENE_ID_5), geneTransCache.getGeneDataById(GENE_ID_6)); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, geneDataList); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, geneDataList); read1 = createMappedRead(++readId, gc2, 10481, 10500, createCigar(0, 20, 20)); read1.setFlag(FIRST_OF_PAIR, true); @@ -235,15 +236,15 @@ public void testSameGeneCollection() public void testPrePosGeneReads() { // 2 gene collections, testing reads before, inside and after the genes - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); IsofoxConfig config = createIsofoxConfig(); config.Functions.add(FUSIONS); @@ -287,11 +288,11 @@ public void testPrePosGeneReads() read1 = createMappedRead(++readId, gc1, 481, 500, createCigar(0, 20, 20)); read1.setFlag(FIRST_OF_PAIR, true); - read1.setSuppAlignment("supp"); + read1.setSuppAlignment(TEST_SUPP_DATA); read2 = createMappedRead(++readId, gc1, 2000, 2019, createCigar(20, 20, 0)); read2.setStrand(true, false); - read2.setSuppAlignment("supp"); + read2.setSuppAlignment(TEST_SUPP_DATA); // these post-gene reads will be skipped in this gene collection Read read3 = createMappedRead(readId, gc1, 2010, 2049, createCigar(0, 40, 0)); @@ -311,10 +312,10 @@ public void testPrePosGeneReads() chimericRT.postProcessChimericReads(baseDepth, fragTracker); assertEquals(1, chimericRT.fusionReadGroupMap().size()); - assertTrue(chimericRT.fusionReadGroupMap().containsKey(read1.Id)); - assertFalse(chimericRT.fusionReadGroupMap().containsKey(read2.Id)); - assertFalse(chimericRT.fusionReadGroupMap().containsKey(read4.Id)); - assertFalse(chimericRT.fusionReadGroupMap().containsKey(read5.Id)); + assertTrue(chimericRT.fusionReadGroupMap().containsKey(read1.id())); + assertFalse(chimericRT.fusionReadGroupMap().containsKey(read2.id())); + assertFalse(chimericRT.fusionReadGroupMap().containsKey(read4.id())); + assertFalse(chimericRT.fusionReadGroupMap().containsKey(read5.id())); assertEquals(1, chimericRT.getLocalChimericReads().size()); assertTrue(chimericRT.getLocalChimericReads().get(0).contains(read5)); @@ -334,24 +335,24 @@ public void testPrePosGeneReads() chimericRT.postProcessChimericReads(baseDepth, fragTracker); assertEquals(2, chimericRT.fusionReadGroupMap().size()); - assertTrue(chimericRT.fusionReadGroupMap().containsKey(read2.Id)); - assertTrue(chimericRT.fusionReadGroupMap().containsKey(read4.Id)); + assertTrue(chimericRT.fusionReadGroupMap().containsKey(read2.id())); + assertTrue(chimericRT.fusionReadGroupMap().containsKey(read4.id())); assertTrue(chimericRT.getLocalChimericReads().isEmpty()); } @Test public void testJunctionPositionTracking() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); - final GeneCollection gc3 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc3 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); IsofoxConfig config = createIsofoxConfig(); config.Functions.add(FUSIONS); @@ -457,19 +458,19 @@ public void testMultiGeneChimericRead() createCigar(0, 10, 99, 10, 0)); List allRegions = Lists.newArrayList(region1, region2, region3, region4, region5); - read.processOverlappingRegions(Read.findOverlappingRegions(allRegions, read)); + processOverlappingRegions(read, Read.findOverlappingRegions(allRegions, read)); assertEquals(SPLICE_JUNCTION, read.getTranscriptClassification(trans1)); assertEquals(SPLICE_JUNCTION, read.getTranscriptClassification(trans2)); - final List knownPairGeneIds = Lists.newArrayList(); + List knownPairGeneIds = Lists.newArrayList(); assertFalse(ChimericUtils.setHasMultipleKnownSpliceGenes(Lists.newArrayList(read), knownPairGeneIds)); // now a ready which doesn't support any known junction but is still within just one gene read = createReadRecord(1, CHR_1, 191, 509, REF_BASE_STR_1, createCigar(0, 10, 299, 10, 0)); - read.processOverlappingRegions(Read.findOverlappingRegions(allRegions, read)); + processOverlappingRegions(read, Read.findOverlappingRegions(allRegions, read)); assertEquals(ALT, read.getTranscriptClassification(trans1)); assertFalse(ChimericUtils.setHasMultipleKnownSpliceGenes(Lists.newArrayList(read), knownPairGeneIds)); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionDataTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionDataTest.java index dbb3e5adcff..8873df53a74 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionDataTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionDataTest.java @@ -1,5 +1,6 @@ package com.hartwig.hmftools.isofox.fusion; +import static com.hartwig.hmftools.common.codon.Nucleotides.reverseComplementBases; import static com.hartwig.hmftools.common.fusion.KnownFusionType.KNOWN_PAIR; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_END; import static com.hartwig.hmftools.common.sv.StartEndIterator.SE_START; @@ -35,6 +36,7 @@ import static htsjdk.samtools.SAMFlag.FIRST_OF_PAIR; import static htsjdk.samtools.SAMFlag.SECOND_OF_PAIR; +import static htsjdk.samtools.SAMFlag.SUPPLEMENTARY_ALIGNMENT; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; @@ -44,6 +46,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.hartwig.hmftools.common.bam.SupplementaryReadData; import com.hartwig.hmftools.common.ensemblcache.EnsemblDataCache; import com.hartwig.hmftools.common.fusion.KnownFusionData; import com.hartwig.hmftools.isofox.IsofoxConfig; @@ -58,7 +61,7 @@ public class FusionDataTest @Test public void testInvalidFusions() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -69,13 +72,13 @@ public void testInvalidFusions() int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); // a DEL within the same gene collection is invalid Read read1 = createMappedRead(1, gc1, 1081, 1100, createCigar(0, 20, 20)); Read read2 = createMappedRead(1, gc1, 1200, 1219, createCigar(20, 20, 0)); - final List chimericReadGroups = Lists.newArrayList(); + List chimericReadGroups = Lists.newArrayList(); chimericReadGroups.add(createGroup(read1, read2)); finder.processLocalReadGroups(chimericReadGroups); @@ -87,7 +90,7 @@ public void testFusionFragmentAssignment() { // Configurator.setRootLevel(Level.DEBUG); - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -97,9 +100,9 @@ public void testFusionFragmentAssignment() int gcId = 0; - final GeneCollection gc1 = + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); // 2 spliced fragments @@ -111,11 +114,11 @@ public void testFusionFragmentAssignment() readPair[1].setStrand(true, false); - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); readPair = createSupplementaryReadPair(++readId, gc1, gc2, 1081, 1100, 10200, 10219, createCigar(0, 20, 20), createCigar(20, 20, 0), true); @@ -125,8 +128,8 @@ public void testFusionFragmentAssignment() readPair[1].setStrand(true, false); read3.setStrand(true, false); - readGroups1.put(read3.Id, createGroup(readPair[0])); - readGroups2.put(read3.Id, createGroup(readPair[1], read3)); + readGroups1.put(read3.id(), createGroup(readPair[0])); + readGroups2.put(read3.id(), createGroup(readPair[1], read3)); // 1 unspliced fragment - will remain its own fusion read1 = createMappedRead(++readId, gc1, 1110, 1149, createCigar(0, 40, 0)); @@ -136,24 +139,24 @@ public void testFusionFragmentAssignment() readPair[1].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); // 1 discordant fragment supporting the spliced fusion read1 = createMappedRead(++readId, gc1, 1055, 1084, createCigar(0, 40, 0)); Read read2 = createMappedRead(readId, gc2, 10220, 10259, createCigar(0, 40, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); // and 1 discordant fragment supporting the unspliced fusion read1 = createMappedRead(++readId, gc1, 1110, 1149, createCigar(0, 40, 0)); read2 = createMappedRead(readId, gc2, 10160, 10199, createCigar(0, 40, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); BaseDepth baseDepth = new BaseDepth(); List completeGroups = finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1); @@ -194,24 +197,24 @@ public void testFusionFragmentAssignment() readPair[1].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); // 1 discordant read in a valid location read1 = createMappedRead(++readId, gc1, 1020, 1059, createCigar(0, 40, 0)); read2 = createMappedRead(readId, gc2, 10220, 10259, createCigar(0, 40, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); // and another too many exons away read1 = createMappedRead(++readId, gc1, 1020, 1059, createCigar(0, 40, 0)); read2 = createMappedRead(readId, gc2, 10820, 10859, createCigar(0, 40, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); // and another too far away config.MaxFragmentLength = 200; @@ -219,8 +222,8 @@ public void testFusionFragmentAssignment() read2 = createMappedRead(readId, gc2, 10720, 10759, createCigar(0, 40, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); completeGroups = finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1); assertEquals(4, finder.getChimericPartialReadGroups().size()); @@ -244,7 +247,7 @@ public void testInterChromosomalFusion() { // test with 2 -ve strand genes across chromosomes - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -254,9 +257,9 @@ public void testInterChromosomalFusion() int gcId = 0; - final GeneCollection gc3 = + GeneCollection gc3 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); - final GeneCollection gc5 = + GeneCollection gc5 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_5))); FusionTaskManager fusionTaskManager = new FusionTaskManager(config, geneTransCache); @@ -265,8 +268,8 @@ public void testInterChromosomalFusion() // 2 spliced fragments int readId = 0; - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); Read read1 = createMappedRead(readId, gc5, 10210, 10249, createCigar(0, 40, 20)); @@ -275,8 +278,8 @@ public void testInterChromosomalFusion() readPair[0].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); // RAC fragment for GC3 String junctionBases = config.RefGenome.getBaseString(gc3.chromosome(), 20264, 20300) @@ -302,15 +305,15 @@ public void testInterChromosomalFusion() Read[] discordantReads = createReadPair(++readId, gc3, gc5, 20150, 20189, 10320, 10359, createCigar(0, 40, 0), createCigar(0, 40, 0), POS_STRAND, NEG_STRAND); - readGroups1.put(discordantReads[0].Id, createGroup(discordantReads[1])); - readGroups2.put(discordantReads[0].Id, createGroup(discordantReads[0])); + readGroups1.put(discordantReads[0].id(), createGroup(discordantReads[1])); + readGroups2.put(discordantReads[0].id(), createGroup(discordantReads[0])); // 1 exonic discordant read discordantReads = createReadPair(++readId, gc3, gc5, 20250, 20289, 10210, 10249, createCigar(0, 40, 0), createCigar(0, 40, 0), POS_STRAND, NEG_STRAND); - readGroups1.put(discordantReads[0].Id, createGroup(discordantReads[1])); - readGroups2.put(discordantReads[0].Id, createGroup(discordantReads[0])); + readGroups1.put(discordantReads[0].id(), createGroup(discordantReads[1])); + readGroups2.put(discordantReads[0].id(), createGroup(discordantReads[0])); config.MaxFragmentLength = 500; @@ -360,7 +363,7 @@ public void testInterChromosomalFusion() @Test public void testSoftClippedFragmentRealignment() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -373,8 +376,8 @@ public void testSoftClippedFragmentRealignment() int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); // a spliced fragment to establish the fusion int readId = 0; @@ -385,11 +388,11 @@ public void testSoftClippedFragmentRealignment() readPair[1].setStrand(true, false); - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); // a soft-clipped read matching the other side of the fusion junction String junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1071, 1100) @@ -431,7 +434,7 @@ public void testSoftClippedFragmentRealignment() public void testLocalDelFusion() { // split reads at known junctions but too short to have supplementary data - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -446,11 +449,11 @@ public void testLocalDelFusion() int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); // a simple DEL, supported by 2 split fragments - the discordant read forming the known-pair fusion int readId = 0; @@ -458,16 +461,16 @@ public void testLocalDelFusion() Read read2 = createMappedRead(readId, gc2, 10200, 10219, createCigar(20, 20, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); // a second one read1 = createMappedRead(++readId, gc1, 1081, 1100, createCigar(0, 20, 20)); read2 = createMappedRead(readId, gc2, 10200, 10219, createCigar(20, 20, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read1.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read1.id(), createGroup(read2)); // realigned and discordant reads which support it @@ -479,7 +482,7 @@ public void testLocalDelFusion() Read read4 = createMappedRead(readId, gc1, 1051, 1090, createCigar(0, 40, 0)); read4.setStrand(true, false); - // readGroups1.put(read3.Id, new ReadGroup(read3, read4)); + // readGroups1.put(read3.id(), new ReadGroup(read3, read4)); addRacReadGroup(racFragmentCache, new ChimericReadGroup(read3, read4), ORIENT_FWD, 1100); @@ -490,7 +493,7 @@ public void testLocalDelFusion() Read read6 = createMappedRead(readId, gc2, 10210, 10249, createCigar(0, 40, 0)); read6.setStrand(true, false); - readGroups1.put(read5.Id, createGroup(read5, read6)); + readGroups1.put(read5.id(), createGroup(read5, read6)); addRacReadGroup(racFragmentCache, new ChimericReadGroup(read5, read6), ORIENT_REV, 10200); @@ -499,8 +502,8 @@ public void testLocalDelFusion() read4 = createMappedRead(readId, gc2, 10210, 10249, createCigar(0, 40, 0)); read4.setStrand(true, false); - readGroups1.put(read3.Id, createGroup(read3)); - readGroups2.put(read4.Id, createGroup(read4)); + readGroups1.put(read3.id(), createGroup(read3)); + readGroups2.put(read4.id(), createGroup(read4)); BaseDepth baseDepth = new BaseDepth(); List completeGroups = finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1); @@ -527,7 +530,7 @@ public void testLocalDelFusion() public void testLocalSplitReadDelFusions() { // a split read (DEL) spanning 2 genes - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -540,8 +543,8 @@ public void testLocalSplitReadDelFusions() int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); // handle reads from GC 1 and then 2 as the BamFragmentReader would @@ -584,11 +587,11 @@ public void testLocalSplitReadDelFusions() Read read10 = createMappedRead(readId, gc1, 1051, 1090, createCigar(0, 40, 0)); read10.setStrand(true, false); - final Map chimericReadGroups = Maps.newHashMap(); + Map chimericReadGroups = Maps.newHashMap(); - chimericReadGroups.put(read1.Id, createGroup(read1)); - chimericReadGroups.put(read7.Id, createGroup(read7)); - chimericReadGroups.put(read9.Id, createGroup(read9, read10)); + chimericReadGroups.put(read1.id(), createGroup(read1)); + chimericReadGroups.put(read7.id(), createGroup(read7)); + chimericReadGroups.put(read9.id(), createGroup(read9, read10)); BaseDepth baseDepth = new BaseDepth(); @@ -599,8 +602,8 @@ public void testLocalSplitReadDelFusions() // GC 2 read handling chimericReadGroups.clear(); - chimericReadGroups.put(read2.Id, createGroup(read2)); - chimericReadGroups.put(read8.Id, createGroup(read8)); + chimericReadGroups.put(read2.id(), createGroup(read2)); + chimericReadGroups.put(read8.id(), createGroup(read8)); completeGroups = finder.processNewChimericReadGroups(gc2, baseDepth, chimericReadGroups); assertEquals(0, finder.getSpanningReadGroups().size()); @@ -624,7 +627,7 @@ public void testLocalSplitReadDelFusions() public void testNonGenicFusions() { // fragments spanning from pre-gene to the following gene - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -637,11 +640,11 @@ public void testNonGenicFusions() int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); // a simple DEL, supported by a split-read int readId = 0; @@ -649,8 +652,8 @@ public void testNonGenicFusions() Read read2 = createMappedRead(readId, gc2, 10205, 10224, createCigar(0, 20, 0)); read2.setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1)); - readGroups2.put(read2.Id, createGroup(read2)); + readGroups1.put(read1.id(), createGroup(read1)); + readGroups2.put(read2.id(), createGroup(read2)); // realigned and discordant reads which support it @@ -662,7 +665,6 @@ public void testNonGenicFusions() Read read4 = createMappedRead(readId, gc1, 551, 590, createCigar(0, 40, 0)); read4.setStrand(true, false); - // readGroups1.put(read3.Id, new ReadGroup(read3, read4)); addRacReadGroup(racFragmentCache, new ChimericReadGroup(read3, read4), ORIENT_FWD, 600); junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 591, 600) @@ -672,7 +674,7 @@ public void testNonGenicFusions() Read read6 = createMappedRead(readId, gc2, 10210, 10249, createCigar(0, 40, 0)); read6.setStrand(true, false); - readGroups1.put(read5.Id, createGroup(read5 ,read6)); + readGroups1.put(read5.id(), createGroup(read5 ,read6)); addRacReadGroup(racFragmentCache, new ChimericReadGroup(read5, read6), ORIENT_REV, 10200); // and a discordant fragment @@ -680,8 +682,8 @@ public void testNonGenicFusions() read4 = createMappedRead(readId, gc2, 10210, 10249, createCigar(0, 40, 0)); read4.setStrand(true, false); - readGroups1.put(read3.Id, createGroup(read3)); - readGroups2.put(read4.Id, createGroup(read4)); + readGroups1.put(read3.id(), createGroup(read3)); + readGroups2.put(read4.id(), createGroup(read4)); BaseDepth baseDepth = new BaseDepth(); List completeGroups = finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1); @@ -701,12 +703,180 @@ public void testNonGenicFusions() assertEquals(2, fusion.getFragmentTypeCount(REALIGNED)); } + @Test + public void testJunctionOverlapMatch() + { + EnsemblDataCache geneTransCache = createGeneDataCache(); + + addTestGenes(geneTransCache); + addTestTranscripts(geneTransCache); + + IsofoxConfig config = createIsofoxConfig(); + populateRefGenome(config.RefGenome); + + FusionFinder finder = createFusionFinder(config, geneTransCache); + + int gcId = 0; + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + + int readId = 0; + Read read1 = createMappedRead(readId, gc1, 1050, 1089, createCigar(0, 40, 0)); + + String junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1071, 1100) + + config.RefGenome.getBaseString(gc1.chromosome(), 10200, 10209); + + Read read2 = createMappedRead(readId, gc1, 1071, 1100, createCigar(0, 30, 10), junctionBases); + + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1091, 1100) + + config.RefGenome.getBaseString(gc1.chromosome(), 10200, 10229); + + // the upper read brings the position in due to a differing alignment + Read read3 = createMappedRead(readId, gc2, 10199, 10228, createCigar(10, 30, 0), junctionBases); + + read3.setStrand(true, false); + + FusionReadData fusion = callJunctionFusion(finder, gc1, gc2, read1, read2, read3); + assertEquals(1100, fusion.junctionPositions()[SE_START]); + assertEquals(10200, fusion.junctionPositions()[SE_END]); + assertEquals(ORIENT_FWD, fusion.junctionOrientations()[SE_START]); + assertEquals(ORIENT_REV, fusion.junctionOrientations()[SE_END]); + assertEquals(1, fusion.getFragments(MATCHED_JUNCTION).size()); + } + + @Test + public void testSameOrientationJunctionOverlap() + { + EnsemblDataCache geneTransCache = createGeneDataCache(); + + addTestGenes(geneTransCache); + addTestTranscripts(geneTransCache); + + IsofoxConfig config = createIsofoxConfig(); + populateRefGenome(config.RefGenome); + + FusionFinder finder = createFusionFinder(config, geneTransCache); + + int gcId = 0; + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + + int readId = 0; + Read read1 = createMappedRead(readId, gc1, 1050, 1089, createCigar(0, 40, 0)); + + String junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1071, 1100) + + reverseComplementBases(config.RefGenome.getBaseString(gc1.chromosome(), 10291, 10300)); + + Read read2 = createMappedRead(readId, gc1, 1071, 1100, createCigar(0, 30, 10), junctionBases); + + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 10272, 10301) + + reverseComplementBases(config.RefGenome.getBaseString(gc1.chromosome(), 1091, 1100)); + + Read read3 = createMappedRead(readId, gc2, 10272, 10301, createCigar(0, 30, 10), junctionBases); + + FusionReadData fusion = callJunctionFusion(finder, gc1, gc2, read1, read2, read3); + assertEquals(ORIENT_FWD, fusion.junctionOrientations()[SE_START]); + assertEquals(ORIENT_FWD, fusion.junctionOrientations()[SE_END]); + assertEquals(1100, fusion.junctionPositions()[SE_START]); + assertEquals(10300, fusion.junctionPositions()[SE_END]); + } + + @Test + public void testFusionKnownExonBoundaryPositionAdjustments() + { + // test 1: lower position can be shifted to match a know exon boundary + EnsemblDataCache geneTransCache = createGeneDataCache(); + + addTestGenes(geneTransCache); + addTestTranscripts(geneTransCache); + + IsofoxConfig config = createIsofoxConfig(); + populateRefGenome(config.RefGenome); + + FusionFinder finder = createFusionFinder(config, geneTransCache); + + int gcId = 0; + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + + int readId = 0; + Read read1 = createMappedRead(readId, gc1, 1050, 1089, createCigar(0, 40, 0)); + + String junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1071, 1100) + + config.RefGenome.getBaseString(gc1.chromosome(), 10220, 10229); + + Read read2 = createMappedRead(readId, gc1, 1071, 1100, createCigar(0, 30, 10), junctionBases); + + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1091, 1100) + + config.RefGenome.getBaseString(gc1.chromosome(), 10220, 10249); + + Read read3 = createMappedRead(readId, gc2, 10219, 10228, createCigar(10, 30, 0), junctionBases); + + read3.setStrand(true, false); + read3.setFlag(SUPPLEMENTARY_ALIGNMENT, true); + read3.setSuppAlignment(suppDataFromRead(read2).asSamTag()); + + FusionReadData fusion = callJunctionFusion(finder, gc1, gc2, read1, read2, read3); + assertEquals(1100, fusion.junctionPositions()[SE_START]); + assertEquals(10220, fusion.junctionPositions()[SE_END]); + assertEquals(ORIENT_FWD, fusion.junctionOrientations()[SE_START]); + assertEquals(ORIENT_REV, fusion.junctionOrientations()[SE_END]); + assertEquals(1, fusion.splitJunctionOverlap()); + assertEquals(FusionJunctionType.KNOWN, fusion.junctionTypes()[SE_START]); + assertEquals(FusionJunctionType.UNKNOWN, fusion.junctionTypes()[SE_END]); + assertEquals(1, fusion.getFragments(MATCHED_JUNCTION).size()); + + // test 2: the lower positions needs adjusting to match a know exon boundary + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1072, 1101) + + config.RefGenome.getBaseString(gc1.chromosome(), 10220, 10229); + + read2 = createMappedRead(readId, gc1, 1072, 1101, createCigar(0, 30, 10), junctionBases); + + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1090, 1099) + + config.RefGenome.getBaseString(gc1.chromosome(), 10220, 10249); + + read3 = createMappedRead(readId, gc2, 10219, 10248, createCigar(10, 30, 0), junctionBases); + + read3.setStrand(true, false); + + fusion = callJunctionFusion(finder, gc1, gc2, read1, read2, read3); + assertEquals(1100, fusion.junctionPositions()[SE_START]); // adjusted to exon + assertEquals(10219, fusion.junctionPositions()[SE_END]); // remains unch + assertEquals(ORIENT_FWD, fusion.junctionOrientations()[SE_START]); + assertEquals(ORIENT_REV, fusion.junctionOrientations()[SE_END]); + assertEquals(1, fusion.splitJunctionOverlap()); + assertEquals(FusionJunctionType.KNOWN, fusion.junctionTypes()[SE_START]); + assertEquals(FusionJunctionType.UNKNOWN, fusion.junctionTypes()[SE_END]); + assertEquals(1, fusion.getFragments(MATCHED_JUNCTION).size()); + + // test 3: the upper position needs shifting to the exon boundary + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1052, 1081) + + config.RefGenome.getBaseString(gc1.chromosome(), 10200, 10209); + + read2 = createMappedRead(readId, gc1, 1052, 1081, createCigar(0, 30, 10), junctionBases); + + junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1080, 1081) + + config.RefGenome.getBaseString(gc1.chromosome(), 10199, 10228); + + read3 = createMappedRead(readId, gc2, 10199, 10228, createCigar(10, 30, 0), junctionBases); + + read3.setStrand(true, false); + + fusion = callJunctionFusion(finder, gc1, gc2, read1, read2, read3); + assertEquals(1081, fusion.junctionPositions()[SE_START]); // remains unch + assertEquals(10200, fusion.junctionPositions()[SE_END]); // adjusted to exon + assertEquals(1, fusion.splitJunctionOverlap()); + assertEquals(FusionJunctionType.CANONICAL, fusion.junctionTypes()[SE_START]); + assertEquals(FusionJunctionType.KNOWN, fusion.junctionTypes()[SE_END]); + assertEquals(1, fusion.getFragments(MATCHED_JUNCTION).size()); + } + @Test public void testHomologyMerging() { // Configurator.setRootLevel(Level.DEBUG); - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); @@ -717,11 +887,11 @@ public void testHomologyMerging() FusionFinder finder = createFusionFinder(config, geneTransCache); int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); // a spliced fragment to establish the fusion int readId = 0; @@ -739,16 +909,11 @@ public void testHomologyMerging() junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1091, 1100) + config.RefGenome.getBaseString(gc1.chromosome(), 10200, 10229); - Read read3 = createMappedRead(readId, gc2, 10200, 102929, createCigar(10, 30, 0), junctionBases); + Read read3 = createMappedRead(readId, gc2, 10200, 10229, createCigar(10, 30, 0), junctionBases); - read2.setFlag(FIRST_OF_PAIR, true); - read3.setFlag(SECOND_OF_PAIR, false); read3.setStrand(true, false); - read2.setSuppAlignment(String.format("%s;%d;%s", read3.Chromosome, read3.PosStart, read3.cigarStr())); - read3.setSuppAlignment(String.format("%s;%d;%s", read2.Chromosome, read2.PosStart, read2.cigarStr())); - readGroups1.put(read1.Id, createGroup(read1, read2)); - readGroups2.put(read1.Id, createGroup(read3)); + addSuppReadGroups(readGroups1, readGroups2, read1, read2, read3); // a second fragment with 2 bases difference due to homology read1 = createMappedRead(++readId, gc1, 1050, 1089, createCigar(0, 40, 0)); @@ -761,16 +926,11 @@ public void testHomologyMerging() junctionBases = config.RefGenome.getBaseString(gc1.chromosome(), 1093, 1102) + config.RefGenome.getBaseString(gc1.chromosome(), 10202, 10231); - read3 = createMappedRead(readId, gc2, 10202, 102931, createCigar(10, 30, 0), junctionBases); + read3 = createMappedRead(readId, gc2, 10202, 10231, createCigar(10, 30, 0), junctionBases); - read2.setFlag(FIRST_OF_PAIR, true); - read3.setFlag(SECOND_OF_PAIR, false); read3.setStrand(true, false); - read2.setSuppAlignment(String.format("%s;%d;%s", read3.Chromosome, read3.PosStart, read3.cigarStr())); - read3.setSuppAlignment(String.format("%s;%d;%s", read2.Chromosome, read2.PosStart, read2.cigarStr())); - readGroups1.put(read1.Id, createGroup(read1, read2)); - readGroups2.put(read1.Id, createGroup(read3)); + addSuppReadGroups(readGroups1, readGroups2, read1, read2, read3); BaseDepth baseDepth = new BaseDepth(); List completeGroups = finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1); @@ -791,7 +951,7 @@ public void testHomologyMerging() @Test public void testCloseMatchFiltering() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); IsofoxConfig config = createIsofoxConfig(); @@ -799,11 +959,11 @@ public void testCloseMatchFiltering() int gcId = 0; - final GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); - final GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc2 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_2))); - final Map readGroups1 = Maps.newHashMap(); - final Map readGroups2 = Maps.newHashMap(); + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); // 3 different but close spliced fragments, one of them with more support than the others int readId = 0; @@ -814,8 +974,8 @@ public void testCloseMatchFiltering() readPair[1].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); // more support - needs to be 5x or more for(int i = 0; i < 4; ++i) @@ -827,8 +987,8 @@ public void testCloseMatchFiltering() readPair[1].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); } // another close by @@ -839,8 +999,8 @@ public void testCloseMatchFiltering() readPair[1].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); // and another read1 = createMappedRead(++readId, gc1, 1050, 1089, createCigar(0, 40, 0)); @@ -850,8 +1010,8 @@ public void testCloseMatchFiltering() readPair[1].setStrand(true, false); - readGroups1.put(read1.Id, createGroup(read1, readPair[0])); - readGroups2.put(read1.Id, createGroup(readPair[1])); + readGroups1.put(read1.id(), createGroup(read1, readPair[0])); + readGroups2.put(read1.id(), createGroup(readPair[1])); BaseDepth baseDepth = new BaseDepth(); List completeGroups = finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1); @@ -871,4 +1031,45 @@ public void testCloseMatchFiltering() assertEquals(1100, fusion.junctionPositions()[SE_START]); assertEquals(10200, fusion.junctionPositions()[SE_END]); } + + private static void addSuppReadGroups( + final Map readGroups1, final Map readGroups2, final Read mate, + final Read donorRead, final Read acceptorRead) + { + // the donor and acceptor reads are a pair of primary and supplementary + donorRead.setFlag(FIRST_OF_PAIR, true); + acceptorRead.setFlag(FIRST_OF_PAIR, true); + acceptorRead.setFlag(SUPPLEMENTARY_ALIGNMENT, true); + + donorRead.setSuppAlignment(suppDataFromRead(acceptorRead).asSamTag()); + acceptorRead.setSuppAlignment(suppDataFromRead(donorRead).asSamTag()); + + readGroups1.put(mate.id(), createGroup(mate, donorRead)); + readGroups2.put(mate.id(), createGroup(acceptorRead)); + } + + public static SupplementaryReadData suppDataFromRead(final Read read) + { + return new SupplementaryReadData( + read.chromosome(), read.alignmentStart(), read.orientation().asChar(), read.cigarStr(), read.mapQuality()); + } + + private static FusionReadData callJunctionFusion( + final FusionFinder finder, final GeneCollection gc1, final GeneCollection gc2, final Read mate, + final Read donorRead, final Read acceptorRead) + { + Map readGroups1 = Maps.newHashMap(); + Map readGroups2 = Maps.newHashMap(); + addSuppReadGroups(readGroups1, readGroups2, mate, donorRead, acceptorRead); + + BaseDepth baseDepth = new BaseDepth(); + finder.processLocalReadGroups(finder.processNewChimericReadGroups(gc1, baseDepth, readGroups1)); + finder.processLocalReadGroups(finder.processNewChimericReadGroups(gc2, baseDepth, readGroups2)); + + assertEquals(1, finder.getFusionCandidates().size()); + List fusions = finder.getFusionCandidates().values().iterator().next(); + assertEquals(1, fusions.size()); + + return fusions.get(0); + } } diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFiltersTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFiltersTest.java index 534b6fe6b04..dd7234c6d7f 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFiltersTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFiltersTest.java @@ -14,6 +14,7 @@ import static com.hartwig.hmftools.isofox.TestUtils.createMappedRead; import static com.hartwig.hmftools.isofox.TestUtils.createSupplementaryReadPair; import static com.hartwig.hmftools.isofox.TestUtils.populateRefGenome; +import static com.hartwig.hmftools.isofox.TestUtils.setReadFirstSecondInPair; import static com.hartwig.hmftools.isofox.fusion.FusionTestUtils.createGeneDataCache; import static junit.framework.TestCase.assertEquals; @@ -35,6 +36,8 @@ import org.junit.Test; +import htsjdk.samtools.SAMFlag; + public class FusionFiltersTest { @Test @@ -182,12 +185,10 @@ public void testFusionHardFilters() int gcId = 0; - final GeneCollection gc3 = - createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); - final GeneCollection gc5 = - createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_5))); + GeneCollection gc3 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); + GeneCollection gc5 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_5))); - FragmentAllocator bamReader1 = new FragmentAllocator(config, ALT_SJ_COHORT_CACHE, new ResultsWriter(config)); + FragmentAllocator bamReader1 = new FragmentAllocator(config, geneTransCache, ALT_SJ_COHORT_CACHE, new ResultsWriter(config)); FusionTaskManager fusionTaskManager = new FusionTaskManager(config, geneTransCache); @@ -197,19 +198,25 @@ public void testFusionHardFilters() int readId2 = 2; int readId3 = 3; - Read read1 = createMappedRead(readId1, gc5, 10210, 10249, createCigar(0, 40, 20)); + // the mate read + Read read1 = createMappedRead(readId1, gc5, 10250, 10289, createCigar(0, 40, 0)); + setReadFirstSecondInPair(read1, false); - Read[] readPair1 = createSupplementaryReadPair(readId1, gc5, gc3, 10200, 10219, 20281, 20300, + Read[] readPair1 = createSupplementaryReadPair( + readId1, gc5, gc3, 10200, 10219, 20281, 20300, createCigar(20, 20, 0), createCigar(0, 20, 20), true); + // TOD): should be setting both primary and supp to have the same strandedness readPair1[0].setStrand(true, false); bamReader1.processReadRecords(gc5, Lists.newArrayList(read1, readPair1[0])); // supporting the first junction and enough to avoid being hard-filtered - Read read2 = createMappedRead(readId2, gc5, 10210, 10249, createCigar(0, 40, 20)); + Read read2 = createMappedRead(readId2, gc5, 10250, 10289, createCigar(0, 40, 0)); + setReadFirstSecondInPair(read2, false); - Read[] readPair2 = createSupplementaryReadPair(readId2, gc5, gc3, 10200, 10219, 20281, 20300, + Read[] readPair2 = createSupplementaryReadPair( + readId2, gc5, gc3, 10200, 10219, 20281, 20300, createCigar(20, 20, 0), createCigar(0, 20, 20), true); readPair2[0].setStrand(true, false); @@ -217,9 +224,11 @@ public void testFusionHardFilters() bamReader1.processReadRecords(gc5, Lists.newArrayList(read2, readPair2[0])); // single read for a new junction, hard-filtered - cannot be at a known splice site - Read read3 = createMappedRead(readId3, gc5, 10410, 10449, createCigar(0, 40, 20)); + Read read3 = createMappedRead(readId3, gc5, 10450, 10489, createCigar(0, 40, 0)); + setReadFirstSecondInPair(read3, false); - Read[] readPair3 = createSupplementaryReadPair(readId3, gc5, gc3, 10401, 10420, 20480, 20499, + Read[] readPair3 = createSupplementaryReadPair( + readId3, gc5, gc3, 10401, 10420, 20480, 20499, createCigar(20, 20, 0), createCigar(0, 20, 20), true); readPair3[0].setStrand(true, false); @@ -255,7 +264,7 @@ public void testFusionHardFilters() assertTrue(finderChr1.getFusionCandidates().isEmpty()); // now chromosome 2 - FragmentAllocator bamReader2 = new FragmentAllocator(config, ALT_SJ_COHORT_CACHE, new ResultsWriter(config)); + FragmentAllocator bamReader2 = new FragmentAllocator(config, geneTransCache, ALT_SJ_COHORT_CACHE, new ResultsWriter(config)); bamReader2.processReadRecords(gc3, Lists.newArrayList(readPair1[1])); bamReader2.processReadRecords(gc3, Lists.newArrayList(readPair2[1])); bamReader2.processReadRecords(gc3, Lists.newArrayList(readPair3[1])); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentsTest.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentsTest.java index 1c1e1b44536..8f92f3ab2b4 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentsTest.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionFragmentsTest.java @@ -23,6 +23,7 @@ import static com.hartwig.hmftools.isofox.TestUtils.createMappedRead; import static com.hartwig.hmftools.isofox.TestUtils.createSupplementaryReadPair; import static com.hartwig.hmftools.isofox.common.TransExonRef.hasMatchWithinRange; +import static com.hartwig.hmftools.isofox.fusion.FusionDataTest.suppDataFromRead; import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.DISCORDANT_JUNCTION; import static com.hartwig.hmftools.isofox.fusion.FusionFragmentType.MATCHED_JUNCTION; import static com.hartwig.hmftools.isofox.fusion.FusionTestUtils.fromReads; @@ -32,6 +33,7 @@ import static org.junit.Assert.assertTrue; import static htsjdk.samtools.SAMFlag.FIRST_OF_PAIR; +import static htsjdk.samtools.SAMFlag.SECOND_OF_PAIR; import static junit.framework.TestCase.assertFalse; import java.util.List; @@ -257,19 +259,17 @@ public void testDupFragments() @Test public void testInvFragment() { - final EnsemblDataCache geneTransCache = createGeneDataCache(); + EnsemblDataCache geneTransCache = createGeneDataCache(); addTestGenes(geneTransCache); addTestTranscripts(geneTransCache); int gcId = 0; - final GeneCollection gc1 = - createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); + GeneCollection gc1 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_1))); // INV being +1/+1 - final GeneCollection gc3 = - createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); + GeneCollection gc3 = createGeneCollection(geneTransCache, gcId++, Lists.newArrayList(geneTransCache.getGeneDataById(GENE_ID_3))); int readId = 0; @@ -281,8 +281,9 @@ public void testInvFragment() read1.setFlag(FIRST_OF_PAIR, true); read2.setFlag(FIRST_OF_PAIR, true); - read1.setSuppAlignment("supp"); - read2.setSuppAlignment("supp"); + read3.setFlag(SECOND_OF_PAIR, true); + read1.setSuppAlignment(suppDataFromRead(read2).asSamTag()); + read2.setSuppAlignment(suppDataFromRead(read1).asSamTag()); read1.setStrand(false, false); read1.setStrand(false, false); read3.setStrand(false, false); diff --git a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionTestUtils.java b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionTestUtils.java index 4b0b280b717..1fcfae98b61 100644 --- a/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionTestUtils.java +++ b/isofox/src/test/java/com/hartwig/hmftools/isofox/fusion/FusionTestUtils.java @@ -28,7 +28,7 @@ public static EnsemblDataCache createGeneDataCache() public static FusionFragment fromReads(final List reads) { setReadJunctions(reads); - return new FusionFragment(new FusionReadGroup(reads.get(0).Id, convertReads(reads))); + return new FusionFragment(new FusionReadGroup(reads.get(0).id(), convertReads(reads))); } public static void setReadJunctions(final List reads) @@ -50,18 +50,18 @@ public static void setReadJunctions(final List reads) public static FusionReadGroup createGroup(final Read read) { - final List reads = Lists.newArrayList(read); + List reads = Lists.newArrayList(read); setReadJunctions(reads); List fusionReads = FusionRead.convertReads(reads); - return new FusionReadGroup(read.Id, fusionReads); + return new FusionReadGroup(read.id(), fusionReads); } public static FusionReadGroup createGroup(final Read read1, final Read read2) { - final List reads = Lists.newArrayList(read1, read2); + List reads = Lists.newArrayList(read1, read2); setReadJunctions(reads); List fusionReads = FusionRead.convertReads(reads); - return new FusionReadGroup(read1.Id, fusionReads); + return new FusionReadGroup(read1.id(), fusionReads); } public static List[] getFragmentGeneIds(final EnsemblDataCache geneTransCache, final FusionFragment fragment) diff --git a/orange/src/test/java/com/hartwig/hmftools/orange/algo/isofox/IsofoxTestFactory.java b/orange/src/test/java/com/hartwig/hmftools/orange/algo/isofox/IsofoxTestFactory.java index 3c174c17aa0..3f8ac1cd087 100644 --- a/orange/src/test/java/com/hartwig/hmftools/orange/algo/isofox/IsofoxTestFactory.java +++ b/orange/src/test/java/com/hartwig/hmftools/orange/algo/isofox/IsofoxTestFactory.java @@ -41,7 +41,6 @@ public static ImmutableRnaStatistics.Builder rnaStatisticsBuilder() .fragmentLength5thPercent(0D) .fragmentLength50thPercent(0D) .fragmentLength95thPercent(0D) - .enrichedGenePercent(0D) .medianGCRatio(0D) .forwardStrandPercent(0D); } diff --git a/patient-db/src/main/java/com/hartwig/hmftools/patientdb/dao/IsofoxDAO.java b/patient-db/src/main/java/com/hartwig/hmftools/patientdb/dao/IsofoxDAO.java index dff2a5f6d2b..52839b41abb 100644 --- a/patient-db/src/main/java/com/hartwig/hmftools/patientdb/dao/IsofoxDAO.java +++ b/patient-db/src/main/java/com/hartwig/hmftools/patientdb/dao/IsofoxDAO.java @@ -17,9 +17,9 @@ import com.hartwig.hmftools.common.rna.RnaFusion; import com.hartwig.hmftools.common.rna.RnaStatistics; -import org.jetbrains.annotations.NotNull; import org.jooq.DSLContext; import org.jooq.InsertValuesStep10; +import org.jooq.InsertValuesStep14; import org.jooq.InsertValuesStep15; import org.jooq.InsertValuesStep20; @@ -47,7 +47,7 @@ public void writeRnaStatistics(final String sampleId, final RnaStatistics statis Timestamp timestamp = new Timestamp(new Date().getTime()); - InsertValuesStep15 inserter = context.insertInto(RNASTATISTICS, + InsertValuesStep14 inserter = context.insertInto(RNASTATISTICS, RNASTATISTICS.MODIFIED, RNASTATISTICS.SAMPLEID, RNASTATISTICS.QCSTATUS, @@ -61,7 +61,6 @@ public void writeRnaStatistics(final String sampleId, final RnaStatistics statis RNASTATISTICS.FRAGMENTLENGTHPCT05, RNASTATISTICS.FRAGMENTLENGTHPCT50, RNASTATISTICS.FRAGMENTLENGTHPCT95, - RNASTATISTICS.ENRICHEDGENEPERCENT, RNASTATISTICS.MEDIANGCRATIO); inserter.values( @@ -78,7 +77,6 @@ public void writeRnaStatistics(final String sampleId, final RnaStatistics statis DatabaseUtil.decimal(statistics.fragmentLength5thPercent()), DatabaseUtil.decimal(statistics.fragmentLength50thPercent()), DatabaseUtil.decimal(statistics.fragmentLength95thPercent()), - DatabaseUtil.decimal(statistics.enrichedGenePercent()), DatabaseUtil.decimal(statistics.medianGCRatio())); inserter.execute(); } diff --git a/patient-db/src/main/resources/generate_database.sql b/patient-db/src/main/resources/generate_database.sql index 236b7b47f49..aca48970f43 100644 --- a/patient-db/src/main/resources/generate_database.sql +++ b/patient-db/src/main/resources/generate_database.sql @@ -958,7 +958,6 @@ CREATE TABLE `rnaStatistics` `fragmentLengthPct05` DOUBLE PRECISION NOT NULL, `fragmentLengthPct50` DOUBLE PRECISION NOT NULL, `fragmentLengthPct95` DOUBLE PRECISION NOT NULL, - `enrichedGenePercent` DOUBLE PRECISION NOT NULL, `medianGCRatio` DOUBLE PRECISION NOT NULL, PRIMARY KEY (`id`) ); diff --git a/patient-db/src/main/resources/patches/patientdb/patientdb_pipeline_3.1.sql b/patient-db/src/main/resources/patches/patientdb/patientdb_pipeline_3.1.sql new file mode 100644 index 00000000000..4b2ccf3985b --- /dev/null +++ b/patient-db/src/main/resources/patches/patientdb/patientdb_pipeline_3.1.sql @@ -0,0 +1,6 @@ +#### +# SQL updates for Pipeline release 3.1 + +ALTER TABLE rnaStatistics + DROP COLUMN enrichedGenePercent; +