Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend TrimPrimers to work amplicons with only a single primer #679

59 changes: 41 additions & 18 deletions src/main/scala/com/fulcrumgenomics/util/Amplicon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,49 @@ case class Amplicon
private val left_start: Int,
private val left_end: Int,
private val right_start: Int,
private val right_end: Int, private val
id: Option[String] = None
private val right_end: Int,
private val id: Option[String] = None
) extends GenomicSpan with Metric {
require(leftStart <= leftEnd, f"leftStart is > leftEnd: $this")
require(rightStart <= rightEnd, f"rightStart is > rightEnd: $this")
require(leftStart <= rightStart, f"leftStart is > rightStart: $this")

@inline def leftStart: Int = left_start
@inline def leftEnd: Int = left_end
@inline def rightStart: Int = right_start
@inline def rightEnd: Int = right_end
@inline def contig: String = chrom
@inline def start: Int = leftStart
@inline def end: Int = rightEnd
@inline def leftStart: Option[Int] = if (left_start > -1) Some(left_start) else None
@inline def leftEnd: Option[Int] = if (left_end > -1) Some(left_end) else None
@inline def rightStart: Option[Int] = if (right_start > -1) Some(right_start) else None
@inline def rightEnd: Option[Int] = if (right_end > -1) Some(right_end) else None
val (s, e, longest_primer_length, left_primer_length, right_primer_length, left_primer_location, right_primer_location) = (leftStart, leftEnd, rightStart, rightEnd) match {
case (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) =>
require(left_start <= left_end, f"leftStart is > leftEnd: $this")
require(right_start <= right_end, f"rightStart is > rightEnd: $this")
require(left_start <= right_start, f"leftStart is > rightStart: $this")

def leftPrimerLength: Int = CoordMath.getLength(left_start, left_end)
def rightPrimerLength: Int = CoordMath.getLength(right_start, right_end)
def longestPrimerLength: Int = Math.max(leftPrimerLength, rightPrimerLength)
def leftPrimerLocation: Option[String] = Some(f"$chrom:$left_start-$left_end")
def rightPrimerLocation: Option[String] = Some(f"$chrom:$right_start-$right_end")
def ampliconLocation: String = f"$chrom:$left_start-$right_end"
def identifier: String = this.id.getOrElse(ampliconLocation)
(left_start, right_end, Math.max(leftPrimerLength, rightPrimerLength), leftPrimerLength, rightPrimerLength, leftPrimerLocation, rightPrimerLocation)
case (Some(left_start), Some(left_end), None, None) =>
require(left_start <= left_end, f"leftStart is > leftEnd: $this")
def leftPrimerLength: Int = CoordMath.getLength(left_start, left_end)
def leftPrimerLocation: Option[String] = Some(f"$chrom:$left_start-$left_end")
(left_start, left_end, leftPrimerLength, leftPrimerLength, 0, leftPrimerLocation, None)
case (None, None, Some(right_start), Some(right_end)) =>
require(right_start <= right_end, f"rightStart is > rightEnd: $this")

def leftPrimerLength: Int = CoordMath.getLength(leftStart, leftEnd)
def rightPrimerLength: Int = CoordMath.getLength(rightStart, rightEnd)
def longestPrimerLength: Int = Math.max(leftPrimerLength, rightPrimerLength)
def leftPrimerLocation: String = f"$chrom:$leftStart-$leftEnd"
def rightPrimerLocation: String = f"$chrom:$rightStart-$rightEnd"
def ampliconLocation: String = f"$chrom:$leftStart-$rightEnd"
def rightPrimerLength: Int = CoordMath.getLength(right_start, right_end)
def rightPrimerLocation: Option[String] = Some(f"$chrom:$right_start-$right_end")
(right_start, right_end, rightPrimerLength, 0, rightPrimerLength, None, rightPrimerLocation)
case _ =>
throw new Exception(s"At least (left_start and left_end) or (right_start and right_end) need to be set in every row of the primer file.")
}
@inline def start: Int = s
@inline def end: Int = e
def leftPrimerLength: Int = left_primer_length
def rightPrimerLength: Int = right_primer_length
def longestPrimerLength: Int = longest_primer_length
def leftPrimerLocation: Option[String] = left_primer_location
def rightPrimerLocation: Option[String] = right_primer_location
def ampliconLocation: String = f"$chrom:$start-$end"
def identifier: String = this.id.getOrElse(ampliconLocation)
}
21 changes: 18 additions & 3 deletions src/main/scala/com/fulcrumgenomics/util/AmpliconDetector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,16 @@ class AmpliconDetector(val detector: OverlapDetector[Amplicon],
private[util] def findPrimer(refName: String, start: Int, end: Int, positiveStrand: Boolean): Option[Amplicon] = {
val interval = new Interval(refName, start, end)
val hits: Iterator[(Amplicon, Int)] = {
if (positiveStrand) detector.getOverlaps(interval).map(amp => (amp, abs(amp.leftStart - start)))
else detector.getOverlaps(interval).map(amp => (amp, abs(amp.rightEnd - end)))
if (positiveStrand) detector.getOverlaps(interval).map(amp => amp.leftStart match {
case Some(leftStart) => (amp, abs(leftStart - start))
case None => throw new Exception(s"Left primer positions need to be defined.") //TODO Can this be reached/will getOverlap() find any results if no left primer exists?
}
)
else detector.getOverlaps(interval).map(amp => amp.rightEnd match {
case Some(rightEnd) => (amp, abs(rightEnd - end))
case None => throw new Exception(s"Right primer positions need to be defined.") //TODO Can this be reached/will getOverlap() find any results if no right primer exists?
}
)
}
hits.minByOption(_._2).find(_._2 <= slop).map(_._1)
}
Expand Down Expand Up @@ -146,7 +154,14 @@ class AmpliconDetector(val detector: OverlapDetector[Amplicon],
val (start, end) = (coordinates.start(left), coordinates.end(right))
val insert = new Interval(left.refName, start, end)
detector.getOverlaps(insert)
.map(amp => (amp, abs(amp.leftStart - start), abs(amp.rightEnd - end)))
.map(amp =>
(amp.leftStart, amp.rightEnd) match {
case (Some(left_start), Some(right_end)) => (amp, abs(left_start - start), abs(right_end - end))
case (Some(left_start), None) => (amp, abs(left_start - start), 0)
case (None, Some(right_end)) => (amp, 0, abs(right_end - end))
case (None, None) => throw new Exception(s"Either left or right primer position need to be defined.")
}
)
.filter(hit => hit._2 <= slop && hit._3 <= slop)
.minByOption(hit => (hit._2 + hit._3))
.map(_._1)
Expand Down
4 changes: 2 additions & 2 deletions src/test/scala/com/fulcrumgenomics/util/AmpliconTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class AmpliconTest extends UnitSpec {
amplicon.leftPrimerLength shouldBe 2
amplicon.rightPrimerLength shouldBe 5
amplicon.longestPrimerLength shouldBe 5
amplicon.leftPrimerLocation shouldBe "chr1:1-2"
amplicon.rightPrimerLocation shouldBe "chr1:4-8"
amplicon.leftPrimerLocation shouldBe Some("chr1:1-2")
amplicon.rightPrimerLocation shouldBe Some("chr1:4-8")
amplicon.ampliconLocation shouldBe "chr1:1-8"
amplicon.identifier shouldBe "id"
}
Expand Down