///|
pub fn split_multi_allelic(
record : VcfRecord,
header~ : VcfHeader,
) -> Array[VcfRecord] {
if record.alt_alleles.length() <= 1 {
[record]
} else {
let records : Array[VcfRecord] = []
for alt_index, alt in record.alt_alleles {
records.push(copy_record_with_alt(record, header~, alt, alt_index~))
}
records
}
}
///|
pub fn normalize_basic(record : VcfRecord) -> VcfRecord {
if record.alt_alleles.length() != 1 {
record
} else {
let (pos, ref_allele, alt_allele) = @normalize.trim_common(
record.ref_allele,
record.alt_alleles[0],
pos=record.pos,
)
{
chrom: record.chrom,
pos,
ids: record.ids,
ref_allele,
alt_alleles: [alt_allele],
qual: record.qual,
filters: record.filters,
info: record.info,
format_keys: record.format_keys,
samples: record.samples,
}
}
}
///|
fn copy_record_with_alt(
record : VcfRecord,
header~ : VcfHeader,
alt : String,
alt_index~ : Int,
) -> VcfRecord {
{
chrom: record.chrom,
pos: record.pos,
ids: record.ids,
ref_allele: record.ref_allele,
alt_alleles: [alt],
qual: record.qual,
filters: record.filters,
info: split_info_for_alt(
record.info,
header~,
alt_index~,
alt_count=record.alt_alleles.length(),
),
format_keys: record.format_keys,
samples: remap_samples(
record.samples,
header~,
alt_index~,
alt_count=record.alt_alleles.length(),
),
}
}
///|
fn remap_samples(
samples : Array[SampleCall],
header~ : VcfHeader,
alt_index~ : Int,
alt_count~ : Int,
) -> Array[SampleCall] {
samples.map(fn(sample) {
let ploidy = sample_ploidy(sample)
{
name: sample.name,
values: sample.values.map(fn(pair) {
let (key, value) = pair
(
key,
remap_format_value(
key,
value,
header~,
alt_index~,
alt_count~,
ploidy,
),
)
}),
}
})
}
///|
fn sample_ploidy(sample : SampleCall) -> Int? {
for pair in sample.values {
let (key, value) = pair
if key == "GT" {
match value {
VcfValue::String(raw) =>
if raw == "." || raw == "" {
return None
} else if raw.contains("/") {
return Some(@text.split_char(raw, '/').length())
} else if raw.contains("|") {
return Some(@text.split_char(raw, '|').length())
} else {
return Some(1)
}
_ => return None
}
}
}
None
}
///|
fn remap_format_value(
key : String,
value : VcfValue,
header~ : VcfHeader,
alt_index~ : Int,
alt_count~ : Int,
ploidy : Int?,
) -> VcfValue {
if key == "GT" {
match value {
VcfValue::String(raw) => VcfValue::String(remap_genotype(raw, alt_index~))
_ => VcfValue::Missing
}
} else {
match find_format_cardinality(header, key) {
Some(cardinality) =>
project_cardinality_value(
value,
cardinality,
alt_index~,
alt_count~,
ploidy,
)
None => value
}
}
}
///|
fn find_format_cardinality(header : VcfHeader, key : String) -> Cardinality? {
for item in header.meta {
match item {
MetaLine::Format(def) if def.id == key => return Some(def.number)
_ => ()
}
}
None
}
///|
fn project_cardinality_value(
value : VcfValue,
cardinality : Cardinality,
alt_index~ : Int,
alt_count~ : Int,
ploidy : Int?,
) -> VcfValue {
match cardinality {
Cardinality::PerAlt => project_list_item(value, alt_index)
Cardinality::PerAllele => project_reference_and_alt(value, alt_index~)
Cardinality::PerGenotype =>
project_genotype_likelihoods(value, alt_index~, alt_count~, ploidy)
_ => value
}
}
///|
fn project_list_item(value : VcfValue, index : Int) -> VcfValue {
match value {
VcfValue::List(values) if index < values.length() => values[index]
VcfValue::List(_) => VcfValue::Missing
_ => value
}
}
///|
fn project_reference_and_alt(value : VcfValue, alt_index~ : Int) -> VcfValue {
match value {
VcfValue::List(values) if alt_index + 1 < values.length() =>
VcfValue::List([values[0], values[alt_index + 1]])
VcfValue::List(_) => VcfValue::Missing
_ => value
}
}
///|
fn project_genotype_likelihoods(
value : VcfValue,
alt_index~ : Int,
alt_count~ : Int,
ploidy : Int?,
) -> VcfValue {
match value {
VcfValue::List(values) => {
let target = alt_index + 1
let first = diploid_genotype_index(0, target)
let second = diploid_genotype_index(target, target)
if ploidy == Some(2) && second < values.length() && target <= alt_count {
VcfValue::List([values[0], values[first], values[second]])
} else {
VcfValue::Missing
}
}
_ => value
}
}
///|
fn diploid_genotype_index(left : Int, right : Int) -> Int {
let low = if left <= right { left } else { right }
let high = if left <= right { right } else { left }
high * (high + 1) / 2 + low
}
///|
fn remap_genotype(raw : String, alt_index~ : Int) -> String {
let target = alt_index + 1
if raw.contains("/") {
remap_genotype_parts(raw, '/', target)
} else if raw.contains("|") {
remap_genotype_parts(raw, '|', target)
} else {
remap_genotype_allele(raw, target)
}
}
///|
fn remap_genotype_parts(raw : String, separator : Char, target : Int) -> String {
let parts = @text.split_char(raw, separator)
if parts.length() == 0 {
"."
} else {
let delimiter = if separator == '/' { "/" } else { "|" }
parts.map(fn(part) { remap_genotype_allele(part, target) }).join(delimiter)
}
}
///|
fn remap_genotype_allele(raw : String, target : Int) -> String {
if raw == "." || raw == "" {
"."
} else {
match parse_non_negative_int(raw) {
Some(index) => if index == target { "1" } else { "0" }
None => "."
}
}
}
///|
fn split_info_for_alt(
info : Array[(String, VcfValue)],
header~ : VcfHeader,
alt_index~ : Int,
alt_count~ : Int,
) -> Array[(String, VcfValue)] {
let split : Array[(String, VcfValue)] = []
for pair in info {
let (key, value) = pair
split.push(
(
key,
match find_info_cardinality(header, key) {
Some(cardinality) =>
project_cardinality_value(
value,
cardinality,
alt_index~,
alt_count~,
None,
)
None => value
},
),
)
}
split
}
///|
fn find_info_cardinality(header : VcfHeader, key : String) -> Cardinality? {
for item in header.meta {
match item {
MetaLine::Info(def) if def.id == key => return Some(def.number)
_ => ()
}
}
None
}