1 package org.broadinstitute.hellbender.engine; 2 3 import htsjdk.samtools.SAMFileHeader; 4 import htsjdk.samtools.SAMReadGroupRecord; 5 import htsjdk.samtools.util.Locatable; 6 import org.broadinstitute.hellbender.utils.HasGenomeLocation; 7 import org.broadinstitute.hellbender.utils.Utils; 8 import org.broadinstitute.hellbender.utils.pileup.ReadPileup; 9 10 import java.util.Collection; 11 import java.util.Collections; 12 import java.util.LinkedHashMap; 13 import java.util.Map; 14 15 /** 16 * Bundles together a pileup and a location. 17 */ 18 public final class AlignmentContext implements Locatable, HasGenomeLocation { 19 // Definitions: 20 // COMPLETE = full alignment context 21 // FORWARD = reads on forward strand 22 // REVERSE = reads on reverse strand 23 // 24 public enum ReadOrientation { COMPLETE, FORWARD, REVERSE } 25 26 private final Locatable loc; 27 private final ReadPileup basePileup; 28 29 private boolean hasPileupBeenDownsampled; 30 AlignmentContext(final Locatable loc, final ReadPileup basePileup)31 public AlignmentContext(final Locatable loc, final ReadPileup basePileup) { 32 this(loc, basePileup, false); 33 } 34 AlignmentContext(final Locatable loc, final ReadPileup basePileup, final boolean hasPileupBeenDownsampled )35 public AlignmentContext(final Locatable loc, final ReadPileup basePileup, final boolean hasPileupBeenDownsampled ) { 36 Utils.nonNull(loc, "BUG: GenomeLoc in Alignment context is null"); 37 Utils.nonNull(basePileup, "BUG: ReadBackedPileup in Alignment context is null"); 38 39 this.loc = loc; 40 this.basePileup = basePileup; 41 this.hasPileupBeenDownsampled = hasPileupBeenDownsampled; 42 } 43 44 /** 45 * How many reads cover this locus? 46 * @return 47 */ size()48 public int size() { 49 return basePileup.size(); 50 } 51 52 @Override getContig()53 public String getContig() { 54 return getLocation().getContig(); 55 } 56 57 @Override getStart()58 public int getStart() { 59 return getLocation().getStart(); 60 } 61 62 @Override getEnd()63 public int getEnd() { 64 return getLocation().getEnd(); 65 } 66 getPosition()67 public long getPosition() { return getStart(); } 68 69 @Override getLocation()70 public Locatable getLocation() { return loc; } 71 72 /** 73 * Returns true if any reads have been filtered out of the pileup due to excess DoC. 74 * @return True if reads have been filtered out. False otherwise. 75 */ hasPileupBeenDownsampled()76 public boolean hasPileupBeenDownsampled() { return hasPileupBeenDownsampled; } 77 getBasePileup()78 public ReadPileup getBasePileup() { 79 return basePileup; 80 } 81 82 /** 83 * Returns a potentially derived subcontext containing only forward, reverse, or in fact all reads 84 * in alignment context context. 85 */ stratify(final ReadOrientation type)86 public AlignmentContext stratify(final ReadOrientation type) { 87 switch(type) { 88 case COMPLETE: 89 return this; 90 case FORWARD: 91 return new AlignmentContext(loc, basePileup.makeFilteredPileup(pe -> !pe.getRead().isReverseStrand())); 92 case REVERSE: 93 return new AlignmentContext(loc, basePileup.makeFilteredPileup(pe -> pe.getRead().isReverseStrand())); 94 default: 95 throw new IllegalArgumentException("Unable to get alignment context for type = " + type); 96 } 97 } 98 splitContextBySampleName(final SAMFileHeader header)99 public Map<String, AlignmentContext> splitContextBySampleName(final SAMFileHeader header) { 100 return this.splitContextBySampleName((String) null, header); 101 } 102 103 /** 104 * Splits the given AlignmentContext into a StratifiedAlignmentContext per sample, but referenced by sample name instead 105 * of sample object. 106 * 107 * @param assumedSingleSample If non-null, assume this is the only sample in our pileup and return immediately. 108 * If null, get the list of samples from the provided header and do the work of splitting by sample. 109 * @return a Map of sample name to StratifiedAlignmentContext; samples without coverage are not included 110 **/ splitContextBySampleName(final String assumedSingleSample, final SAMFileHeader header)111 public Map<String, AlignmentContext> splitContextBySampleName(final String assumedSingleSample, final SAMFileHeader header) { 112 if (assumedSingleSample != null){ 113 return Collections.singletonMap(assumedSingleSample, this); 114 } 115 final Locatable loc = this.getLocation(); 116 // this will throw an user error if there are samples without RG/sampleName 117 final Map<String, ReadPileup> pileups = this.getBasePileup().splitBySample(header, assumedSingleSample); 118 final Map<String, AlignmentContext> contexts = new LinkedHashMap<>(pileups.size()); 119 for (final Map.Entry<String, ReadPileup> entry : pileups.entrySet()) { 120 // Don't add empty pileups to the split context. 121 if (entry.getValue().isEmpty()) { 122 continue; 123 } 124 contexts.put(entry.getKey(), new AlignmentContext(loc, entry.getValue())); 125 } 126 return contexts; 127 } 128 splitContextBySampleName(final ReadPileup pileup, final SAMFileHeader header)129 public static Map<String, AlignmentContext> splitContextBySampleName(final ReadPileup pileup, final SAMFileHeader header) { 130 return new AlignmentContext(pileup.getLocation(), pileup).splitContextBySampleName(header); 131 } 132 133 @Override toString()134 public String toString() { 135 return "AlignmentContext{" + 136 "loc=" + loc + 137 ", basePileup=" + basePileup + 138 ", hasPileupBeenDownsampled=" + hasPileupBeenDownsampled + 139 '}'; 140 } 141 } 142