package picard.analysis.artifacts; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.util.ListMap; import htsjdk.samtools.util.SequenceUtil; import picard.PicardException; import picard.analysis.artifacts.SequencingArtifactMetrics.BaitBiasDetailMetrics; import picard.analysis.artifacts.SequencingArtifactMetrics.DetailPair; import picard.analysis.artifacts.SequencingArtifactMetrics.PreAdapterDetailMetrics; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * Keeps track of the AlignmentAccumulators for each artifact / context of interest. */ class ContextAccumulator { // are the PE reads expected to face the same direction? private final boolean expectedTandemReads; // mapping from contexts to the accumulators private final Map<String, AlignmentAccumulator[]> artifactMap; public ContextAccumulator(final Set<String> contexts, final boolean expectedTandemReads) { this.expectedTandemReads = expectedTandemReads; this.artifactMap = new HashMap<>(); for (final String context : contexts) { // sanity check that the context length is odd if ((context.length() & 1) == 0) throw new PicardException("Contexts cannot have an even number of bases: " + context); final AlignmentAccumulator[] accumulators = new AlignmentAccumulator[Transition.Base.values().length]; for (int i = 0; i < Transition.Base.values().length; i++) { accumulators[i] = new AlignmentAccumulator(); } this.artifactMap.put(context, accumulators); } } public void countRecord(final String refContext, final char calledBase, final SAMRecord rec) { artifactMap.get(refContext)[Transition.baseIndexMap[calledBase]].countRecord(rec); } /** * Core method to compute detailed (i.e. context-by-context) metrics from this accumulator. */ public ListMap<Transition, DetailPair> calculateMetrics(final String sampleAlias, final String library) { final ListMap<Transition, DetailPair> detailMetricsMap = new ListMap<>(); for (final String context : new TreeSet<>(artifactMap.keySet())) { // sanity check that the context length is odd if ((context.length() & 1) == 0) throw new PicardException("Contexts cannot have an even number of bases: " + context + ". This should never happen here!"); final char refBase = context.charAt(context.length() / 2); for (final Transition.Base altBase : Transition.Base.values()) { final Transition transition = Transition.transitionOf(refBase, (char)altBase.base); // each combination of artifact + context represents a single metric row final PreAdapterDetailMetrics preAdapterDetailMetrics = new PreAdapterDetailMetrics(); final BaitBiasDetailMetrics baitBiasDetailMetrics = new BaitBiasDetailMetrics(); // populate basic fields preAdapterDetailMetrics.SAMPLE_ALIAS = sampleAlias; preAdapterDetailMetrics.LIBRARY = library; preAdapterDetailMetrics.CONTEXT = context; preAdapterDetailMetrics.REF_BASE = transition.ref(); preAdapterDetailMetrics.ALT_BASE = transition.call(); baitBiasDetailMetrics.SAMPLE_ALIAS = sampleAlias; baitBiasDetailMetrics.LIBRARY = library; baitBiasDetailMetrics.CONTEXT = context; baitBiasDetailMetrics.REF_BASE = transition.ref(); baitBiasDetailMetrics.ALT_BASE = transition.call(); // retrieve all the necessary alignment counters. final AlignmentAccumulator[] accumulators = artifactMap.get(context); final AlignmentAccumulator[] reverseCompAccumulators = artifactMap.get(SequenceUtil.reverseComplement(context)); final AlignmentAccumulator fwdRefAlignments = accumulators[Transition.baseIndexMap[transition.ref()]]; final AlignmentAccumulator fwdAltAlignments = accumulators[Transition.baseIndexMap[transition.call()]]; final AlignmentAccumulator revRefAlignments = reverseCompAccumulators[Transition.baseIndexMap[transition.complement().ref()]]; final AlignmentAccumulator revAltAlignments = reverseCompAccumulators[Transition.baseIndexMap[transition.complement().call()]]; // categorize observations of pre-adapter artifacts if (expectedTandemReads) { // if both ends are sequenced on the same strand, then read1/read2 should exhibit the same bias preAdapterDetailMetrics.PRO_REF_BASES = fwdRefAlignments.R1_POS + fwdRefAlignments.R2_POS + revRefAlignments.R1_NEG + revRefAlignments.R2_NEG; preAdapterDetailMetrics.PRO_ALT_BASES = fwdAltAlignments.R1_POS + fwdAltAlignments.R2_POS + revAltAlignments.R1_NEG + revAltAlignments.R2_NEG; preAdapterDetailMetrics.CON_REF_BASES = fwdRefAlignments.R1_NEG + fwdRefAlignments.R2_NEG + revRefAlignments.R1_POS + revRefAlignments.R2_POS; preAdapterDetailMetrics.CON_ALT_BASES = fwdAltAlignments.R1_NEG + fwdAltAlignments.R2_NEG + revAltAlignments.R1_POS + revAltAlignments.R2_POS; } else { // if ends are sequenced on opposite strands, then read1/read2 should exhibit opposite biases preAdapterDetailMetrics.PRO_REF_BASES = fwdRefAlignments.R1_POS + fwdRefAlignments.R2_NEG + revRefAlignments.R1_NEG + revRefAlignments.R2_POS; preAdapterDetailMetrics.PRO_ALT_BASES = fwdAltAlignments.R1_POS + fwdAltAlignments.R2_NEG + revAltAlignments.R1_NEG + revAltAlignments.R2_POS; preAdapterDetailMetrics.CON_REF_BASES = fwdRefAlignments.R1_NEG + fwdRefAlignments.R2_POS + revRefAlignments.R1_POS + revRefAlignments.R2_NEG; preAdapterDetailMetrics.CON_ALT_BASES = fwdAltAlignments.R1_NEG + fwdAltAlignments.R2_POS + revAltAlignments.R1_POS + revAltAlignments.R2_NEG; } // categorize observations of bait bias artifacts baitBiasDetailMetrics.FWD_CXT_REF_BASES = fwdRefAlignments.R1_POS + fwdRefAlignments.R1_NEG + fwdRefAlignments.R2_POS + fwdRefAlignments.R2_NEG; baitBiasDetailMetrics.FWD_CXT_ALT_BASES = fwdAltAlignments.R1_POS + fwdAltAlignments.R1_NEG + fwdAltAlignments.R2_POS + fwdAltAlignments.R2_NEG; baitBiasDetailMetrics.REV_CXT_REF_BASES = revRefAlignments.R1_POS + revRefAlignments.R1_NEG + revRefAlignments.R2_POS + revRefAlignments.R2_NEG; baitBiasDetailMetrics.REV_CXT_ALT_BASES = revAltAlignments.R1_POS + revAltAlignments.R1_NEG + revAltAlignments.R2_POS + revAltAlignments.R2_NEG; // calculate error rates + Q-scores preAdapterDetailMetrics.calculateDerivedStatistics(); baitBiasDetailMetrics.calculateDerivedStatistics(); // add the finalized metrics to the map detailMetricsMap.add(transition, new DetailPair(preAdapterDetailMetrics, baitBiasDetailMetrics)); } } return detailMetricsMap; } /** * Little class for breaking down alignments by read1/read2 and positive/negative strand. */ private static class AlignmentAccumulator { private long R1_POS = 0; private long R1_NEG = 0; private long R2_POS = 0; private long R2_NEG = 0; private void countRecord(final SAMRecord rec) { final boolean isNegativeStrand = rec.getReadNegativeStrandFlag(); final boolean isReadTwo = rec.getReadPairedFlag() && rec.getSecondOfPairFlag(); if (isReadTwo) { if (isNegativeStrand) this.R2_NEG++; else this.R2_POS++; } else { if (isNegativeStrand) this.R1_NEG++; else this.R1_POS++; } } } }