From 9628c1e6fe0e6299f76c772b265f2d171afc0fbc Mon Sep 17 00:00:00 2001
From: michaelgatzen <52426291+michaelgatzen@users.noreply.github.com>
Date: Wed, 21 Aug 2019 17:23:03 -0400
Subject: [PATCH] Added alignment type to RecordAndOffset class (#1411)
* Added alignment type to RecordAndOffset class
- This was suggested as part of https://github.com/broadinstitute/picard/pull/1370
- necessary to determine whether a RecordAndOffset object is an
(alignment) match, insertion, or deletion when queried by a
SamLocusIterator
- default behavior is preserved by overloading the default constructor
* Added tests for including the alignment type in RecordAndOffset
---
.../util/AbstractRecordAndOffset.java | 37 ++++++++
.../samtools/util/SamLocusIterator.java | 14 ++-
.../samtools/util/SamLocusIteratorTest.java | 88 +++++++++++++++++++
3 files changed, 137 insertions(+), 2 deletions(-)
diff --git a/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java b/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java
index e76b666839..fd609148a7 100644
--- a/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java
+++ b/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java
@@ -37,6 +37,16 @@
*/
public class AbstractRecordAndOffset {
+ /**
+ * Classifies whether the given event is a match, insertion, or deletion. This is deliberately not expressed
+ * as CIGAR operators, since there is no knowledge of the CIGAR string at the time that this is determined.
+ */
+ public enum AlignmentType {
+ Match,
+ Insertion,
+ Deletion,
+ }
+
/**
* A SAMRecord aligned to reference position
*/
@@ -46,6 +56,12 @@ public class AbstractRecordAndOffset {
*/
protected final int offset;
+ /**
+ * The {@link AlignmentType} of this object, which classifies whether the given event is a match, insertion, or
+ * deletion when queried from a {@link SamLocusIterator}.
+ */
+ protected final AlignmentType alignmentType;
+
/**
* @param record inner SAMRecord
* @param offset from the start of the read
@@ -53,6 +69,19 @@ public class AbstractRecordAndOffset {
public AbstractRecordAndOffset(final SAMRecord record, final int offset) {
this.offset = offset;
this.record = record;
+ this.alignmentType = AlignmentType.Match;
+ }
+
+ /**
+ * @param record inner SAMRecord
+ * @param offset from the start of the read
+ * @param alignmentType The {@link AlignmentType} of this object, which is used when queried in
+ * a {@link SamLocusIterator}.
+ */
+ public AbstractRecordAndOffset(final SAMRecord record, final int offset, final AlignmentType alignmentType) {
+ this.offset = offset;
+ this.record = record;
+ this.alignmentType = alignmentType;
}
/**
@@ -69,6 +98,14 @@ public SAMRecord getRecord() {
return record;
}
+ /**
+ * The {@link AlignmentType} of this object, which classifies whether the given event is a match, insertion, or
+ * deletion when queried from a {@link SamLocusIterator}.
+ */
+ public AlignmentType getAlignmentType() {
+ return alignmentType;
+ }
+
/**
* @return the read base according to offset
.
*/
diff --git a/src/main/java/htsjdk/samtools/util/SamLocusIterator.java b/src/main/java/htsjdk/samtools/util/SamLocusIterator.java
index 028d99dce0..1034f145f3 100644
--- a/src/main/java/htsjdk/samtools/util/SamLocusIterator.java
+++ b/src/main/java/htsjdk/samtools/util/SamLocusIterator.java
@@ -206,6 +206,16 @@ public static class RecordAndOffset extends AbstractRecordAndOffset {
public RecordAndOffset(final SAMRecord record, final int offset) {
super(record, offset);
}
+
+ /**
+ * @param record inner SAMRecord
+ * @param offset 0-based offset from the start of SAMRecord
+ * @param alignmentType The {@link AlignmentType} of this object, which is used when queried in
+ * a {@link SamLocusIterator}.
+ */
+ public RecordAndOffset(final SAMRecord record, final int offset, final AlignmentType alignmentType) {
+ super(record, offset, alignmentType);
+ }
}
/**
@@ -234,7 +244,7 @@ public void addDeleted(final SAMRecord read, int previousPosition) {
if (deletedInRecord == null) {
deletedInRecord = new ArrayList<>();
}
- deletedInRecord.add(new RecordAndOffset(read, previousPosition));
+ deletedInRecord.add(new RecordAndOffset(read, previousPosition, AbstractRecordAndOffset.AlignmentType.Deletion));
}
/**
@@ -247,7 +257,7 @@ public void addInserted(final SAMRecord read, int firstPosition) {
if (insertedInRecord == null) {
insertedInRecord = new ArrayList<>();
}
- insertedInRecord.add(new RecordAndOffset(read, firstPosition));
+ insertedInRecord.add(new RecordAndOffset(read, firstPosition, AbstractRecordAndOffset.AlignmentType.Insertion));
}
public List getDeletedInRecord() {
diff --git a/src/test/java/htsjdk/samtools/util/SamLocusIteratorTest.java b/src/test/java/htsjdk/samtools/util/SamLocusIteratorTest.java
index 64770f7de7..2b8ea58c5f 100644
--- a/src/test/java/htsjdk/samtools/util/SamLocusIteratorTest.java
+++ b/src/test/java/htsjdk/samtools/util/SamLocusIteratorTest.java
@@ -62,6 +62,10 @@ public void testBasicIterator() {
for (final SamLocusIterator.LocusInfo li : sli) {
Assert.assertEquals(li.getPosition(), pos++);
Assert.assertEquals(li.getRecordAndOffsets().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), coverage);
// make sure that we are not accumulating indels
Assert.assertEquals(li.getDeletedInRecord().size(), 0);
@@ -87,6 +91,10 @@ public void testMissingQualityString() {
for (final SamLocusIterator.LocusInfo li : sli) {
Assert.assertEquals(li.getPosition(), pos++);
Assert.assertEquals(li.getRecordAndOffsets().size(), 2);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), 2);
}
}
@@ -124,6 +132,10 @@ public void testEmitUncoveredLoci() {
expectedReads = 0;
}
Assert.assertEquals(li.getRecordAndOffsets().size(), expectedReads);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), expectedReads);
// make sure that we are not accumulating indels
Assert.assertEquals(li.getDeletedInRecord().size(), 0);
@@ -162,6 +174,10 @@ public void testQualityFilter() {
int pos = startPosition;
for (final SamLocusIterator.LocusInfo li : sli) {
Assert.assertEquals(li.getRecordAndOffsets().size(), (pos % 2 == 0) ? coverage / 2 : coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), (pos % 2 == 0) ? coverage / 2 : coverage);
Assert.assertEquals(li.getPosition(), pos++);
// make sure that we are not accumulating indels
@@ -205,10 +221,18 @@ public void testSimpleDeletion() {
// make sure that we are accumulating indels
Assert.assertEquals(li.getDeletedInRecord().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getDeletedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Deletion);
+ }
Assert.assertEquals(li.getInsertedInRecord().size(), 0);
} else {
// make sure we are accumulating normal coverage
Assert.assertEquals(li.getRecordAndOffsets().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), coverage);
// make sure that we are not accumulating indels
@@ -242,12 +266,20 @@ public void testSimpleInsertion() {
Assert.assertEquals(li.getPosition(), pos++);
// make sure we are accumulating normal coverage
Assert.assertEquals(li.getRecordAndOffsets().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), coverage);
// make sure that we are not accumulating deletions
Assert.assertEquals(li.getDeletedInRecord().size(), 0);
if (incIndels && li.getPosition() == insStart) {
Assert.assertEquals(li.getInsertedInRecord().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getInsertedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Insertion);
+ }
} else {
Assert.assertEquals(li.getInsertedInRecord().size(), 0);
}
@@ -281,12 +313,20 @@ public void testStartWithInsertion() {
Assert.assertEquals(li.getPosition(), pos);
// accumulation of coverage
Assert.assertEquals(li.getRecordAndOffsets().size(), (indelPosition) ? 0 : coverage + 1);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), (indelPosition) ? 0 : coverage + 1);
// no accumulation of deletions
Assert.assertEquals(li.getDeletedInRecord().size(), 0);
// accumulation of insertion
Assert.assertEquals(li.getInsertedInRecord().size(), (indelPosition) ? coverage : 0);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getInsertedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Insertion);
+ }
// check offsets of the insertion
if (indelPosition) {
Assert.assertEquals(li.getInsertedInRecord().get(0).getOffset(), 0);
@@ -322,11 +362,19 @@ public void testStartWithSoftClipAndInsertion() {
Assert.assertEquals(li.getPosition(), pos);
// accumulation of coverage
Assert.assertEquals(li.getRecordAndOffsets().size(), (indelPosition) ? 0 : coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), (indelPosition) ? 0 : coverage);
// no accumulation of deletions
Assert.assertEquals(li.getDeletedInRecord().size(), 0);
// accumulation of insertion
Assert.assertEquals(li.getInsertedInRecord().size(), (indelPosition) ? coverage : 0);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getInsertedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Insertion);
+ }
// check offsets of the insertion
if (indelPosition) {
Assert.assertEquals(li.getInsertedInRecord().get(0).getOffset(), 1);
@@ -367,11 +415,19 @@ public void testNBeforeInsertion() {
Assert.assertEquals(li.getPosition(), pos);
// accumulation of coverage
Assert.assertEquals(li.getRecordAndOffsets().size(), (pos == endN) ? 0 : coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), (pos == endN) ? 0 : coverage);
// no accumulation of deletions
Assert.assertEquals(li.getDeletedInRecord().size(), 0);
// accumulation of insertion
Assert.assertEquals(li.getInsertedInRecord().size(), (pos == endN) ? coverage : 0);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getInsertedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Insertion);
+ }
// check offsets of the insertion
if (pos == endN) {
Assert.assertEquals(li.getInsertedInRecord().get(0).getOffset(), 2);
@@ -419,9 +475,17 @@ public void testNBeforeDeletion() {
Assert.assertEquals(li.getPosition(), pos);
// accumulation of coverage
Assert.assertEquals(li.getRecordAndOffsets().size(), (insideDeletion) ? 0 : coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), coverage); // either will be all deletions, or all non-deletions, but always of size `coverage`.
// accumulation of deletions
Assert.assertEquals(li.getDeletedInRecord().size(), (insideDeletion) ? coverage : 0);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getDeletedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Deletion);
+ }
// no accumulation of insertion
Assert.assertEquals(li.getInsertedInRecord().size(), 0);
// check offsets of the insertion
@@ -498,6 +562,10 @@ public void testSimpleGappedAlignment() {
if (incIndels && li.getPosition() == expectedInsertionPosition) {
// check the accumulated coverage
Assert.assertEquals(li.getInsertedInRecord().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getInsertedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Insertion);
+ }
// check the record offset
Assert.assertEquals(li.getInsertedInRecord().get(0).getOffset(), expectedInsertionOffset);
Assert.assertEquals(li.getInsertedInRecord().get(1).getOffset(), expectedInsertionOffset);
@@ -508,6 +576,10 @@ public void testSimpleGappedAlignment() {
if (inDelRange) {
// check the coverage for insertion and normal records
Assert.assertEquals(li.getDeletedInRecord().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getDeletedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Deletion);
+ }
Assert.assertEquals(li.getRecordAndOffsets().size(), 0);
Assert.assertEquals(li.size(), coverage); // includes deletions
// check the offset for the deletion
@@ -516,6 +588,10 @@ public void testSimpleGappedAlignment() {
} else {
// if it is not a deletion, perform the same test as before
Assert.assertEquals(li.getRecordAndOffsets().size(), coverage);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
Assert.assertEquals(li.size(), coverage);
// Assert.assertEquals(li.getDeletedInRecord().size(), 0);
Assert.assertEquals(li.getRecordAndOffsets().get(0).getOffset(), expectedReadOffsets[i]);
@@ -582,6 +658,10 @@ public void testOverlappingGappedAlignmentsWithoutIndels() {
Assert.assertEquals(li.size(), expectedDepths[i]);
Assert.assertEquals(li.getPosition(), expectedReferencePositions[i]);
Assert.assertEquals(li.getRecordAndOffsets().size(), expectedReadOffsets[i].length);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
for (int j = 0; j < expectedReadOffsets[i].length; ++j) {
Assert.assertEquals(li.getRecordAndOffsets().get(j).getOffset(), expectedReadOffsets[i][j]);
}
@@ -658,11 +738,19 @@ public void testOverlappingGappedAlignmentsWithIndels() {
Assert.assertEquals(li.size(), expectedDepths[i] + expectedDelDepths[i]); // include deletions
Assert.assertEquals(li.getPosition(), expectedReferencePositions[i]);
Assert.assertEquals(li.getRecordAndOffsets().size(), expectedReadOffsets[i].length);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getRecordAndOffsets()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Match);
+ }
for (int j = 0; j < expectedReadOffsets[i].length; ++j) {
Assert.assertEquals(li.getRecordAndOffsets().get(j).getOffset(), expectedReadOffsets[i][j]);
}
// check the deletions
Assert.assertEquals(li.getDeletedInRecord().size(), expectedDelDepths[i]);
+ // Check the correct assignment of the alignment type
+ for (final SamLocusIterator.RecordAndOffset rao : li.getDeletedInRecord()) {
+ Assert.assertEquals(rao.getAlignmentType(), SamLocusIterator.RecordAndOffset.AlignmentType.Deletion);
+ }
if (expectedDelDepths[i] != 0) {
Assert.assertEquals(li.getDeletedInRecord().get(0).getOffset(), expectedDelOffset);
}