///|
/// An annotation interval follows reference top-strand coordinates.
pub(all) struct Annotation {
id : String
start : Int
length : Int
} derive(Eq, Debug, ToJson)
///|
/// One contiguous annotation piece in fragment-local coordinates.
pub(all) struct AnnotationPiece {
id : String
fragment_index : Int
local_start : Int
length : Int
source_start : Int
} derive(Eq, Debug, ToJson)
///|
/// Split intervals on fragment and circular-origin boundaries. No biological labels inferred.
pub fn annotate_fragments(
dna : Dna,
enzymes : Array[Enzyme],
report : Digest,
annotations : Array[Annotation],
) -> Array[AnnotationPiece] raise RestrictError {
verify_digest(dna, enzymes, report)
if annotations.length() > 128 || annotations.length() * dna.length() > 4000000 {
raise LimitExceeded("ANNOTATION_WORK")
}
let names : Map[String, Bool] = Map([])
let result : Array[AnnotationPiece] = []
for a in annotations {
if a.id.is_empty() || a.id.length() > 128 || names.contains(a.id) {
raise InvalidInput("ANNOTATION_ID")
}
names[a.id] = true
if a.start < 0 ||
a.start >= dna.length() ||
a.length < 1 ||
a.length > dna.length() {
raise InvalidInput("ANNOTATION_RANGE")
}
if dna.topology == Linear && a.start + a.length > dna.length() {
raise InvalidInput("ANNOTATION_RANGE")
}
for fi in 0..= a.start && position < a.start + a.length
})
let breaks_origin = j > 0 && position == 0
if run >= 0 && (!inside || breaks_origin) {
result.push({
id: a.id,
fragment_index: fi,
local_start: run,
length: j - run,
source_start: (f.start + run) % dna.length(),
})
run = -1
}
if inside && run < 0 {
run = j
}
}
}
}
result
}