|
@@ -0,0 +1,157 @@
|
|
|
|
|
+use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
|
+
|
|
|
|
|
+use bitcode::{Decode, Encode};
|
|
|
|
|
+use log::{error, info, warn};
|
|
|
|
|
+use rayon::prelude::*;
|
|
|
|
|
+use serde::{Deserialize, Serialize};
|
|
|
|
|
+
|
|
|
|
|
+use crate::{
|
|
|
|
|
+ annotation::{Annotation, Annotations},
|
|
|
|
|
+ io::{readers::TabixReader, vcf::fetch_vcf_with_reader},
|
|
|
|
|
+ positions::GenomeRange,
|
|
|
|
|
+ variant::vcf_variant::{Info, Infos, VcfVariant},
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Encode, Decode)]
|
|
|
|
|
+pub struct ColorsDbEntry {
|
|
|
|
|
+ pub ac: u32,
|
|
|
|
|
+ pub an: u32,
|
|
|
|
|
+ pub ns: u32,
|
|
|
|
|
+ pub ac_hom: u32,
|
|
|
|
|
+ pub ac_het: u32,
|
|
|
|
|
+ pub ac_hemi: u32,
|
|
|
|
|
+ pub af: f32,
|
|
|
|
|
+ pub hwe: f32,
|
|
|
|
|
+ pub exchet: f32,
|
|
|
|
|
+ pub nhomalt: u32,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl TryFrom<&Infos> for ColorsDbEntry {
|
|
|
|
|
+ type Error = String;
|
|
|
|
|
+
|
|
|
|
|
+ fn try_from(infos: &Infos) -> Result<Self, Self::Error> {
|
|
|
|
|
+ let mut ac = None;
|
|
|
|
|
+ let mut an = None;
|
|
|
|
|
+ let mut ns = None;
|
|
|
|
|
+ let mut ac_hom = None;
|
|
|
|
|
+ let mut ac_het = None;
|
|
|
|
|
+ let mut ac_hemi = None;
|
|
|
|
|
+ let mut af = None;
|
|
|
|
|
+ let mut hwe = None;
|
|
|
|
|
+ let mut exchet = None;
|
|
|
|
|
+ let mut nhomalt = None;
|
|
|
|
|
+
|
|
|
|
|
+ for info in &infos.0 {
|
|
|
|
|
+ match info {
|
|
|
|
|
+ Info::AC(v) => ac = Some(*v),
|
|
|
|
|
+ Info::AN(v) => an = Some(*v),
|
|
|
|
|
+ Info::NS(v) => ns = Some(*v),
|
|
|
|
|
+ Info::AC_Hom(v) => ac_hom = Some(*v),
|
|
|
|
|
+ Info::AC_Het(v) => ac_het = Some(*v),
|
|
|
|
|
+ Info::AC_Hemi(v) => ac_hemi = Some(*v),
|
|
|
|
|
+ Info::AF(v) => af = Some(*v),
|
|
|
|
|
+ Info::HWE(v) => hwe = Some(*v),
|
|
|
|
|
+ Info::ExcHet(v) => exchet = Some(*v),
|
|
|
|
|
+ Info::Nhomalt(v) => nhomalt = Some(*v),
|
|
|
|
|
+ _ => {}
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(ColorsDbEntry {
|
|
|
|
|
+ ac: ac.ok_or("missing INFO/AC")?,
|
|
|
|
|
+ an: an.ok_or("missing INFO/AN")?,
|
|
|
|
|
+ ns: ns.ok_or("missing INFO/NS")?,
|
|
|
|
|
+ ac_hom: ac_hom.ok_or("missing INFO/AC_Hom")?,
|
|
|
|
|
+ ac_het: ac_het.ok_or("missing INFO/AC_Het")?,
|
|
|
|
|
+ ac_hemi: ac_hemi.ok_or("missing INFO/AC_Hemi")?,
|
|
|
|
|
+ af: af.ok_or("missing INFO/AF")?,
|
|
|
|
|
+ hwe: hwe.ok_or("missing INFO/HWE")?,
|
|
|
|
|
+ exchet: exchet.ok_or("missing INFO/ExcHet")?,
|
|
|
|
|
+ nhomalt: nhomalt.ok_or("missing INFO/Nhomalt")?,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn annotate_colorsdb(
|
|
|
|
|
+ variants: &[VcfVariant],
|
|
|
|
|
+ annotations: &Annotations,
|
|
|
|
|
+ colorsdb_path: &str,
|
|
|
|
|
+) -> anyhow::Result<()> {
|
|
|
|
|
+ if variants.is_empty() {
|
|
|
|
|
+ return Ok(());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let n_threads = rayon::current_num_threads();
|
|
|
|
|
+ let chunk_size = variants.len().div_ceil(n_threads).max(500);
|
|
|
|
|
+
|
|
|
|
|
+ let annotated_count = AtomicUsize::new(0);
|
|
|
|
|
+
|
|
|
|
|
+ variants.par_chunks(chunk_size).for_each_init(
|
|
|
|
|
+ || TabixReader::open(colorsdb_path),
|
|
|
|
|
+ |reader_res, chunk| {
|
|
|
|
|
+ let Ok(ref mut reader) = reader_res else {
|
|
|
|
|
+ error!("Failed to open CoLoRSdb tabix reader for chunk");
|
|
|
|
|
+ return;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for variant in chunk {
|
|
|
|
|
+ if let Some(anns) = annotations.store.get(&variant.hash) {
|
|
|
|
|
+ if anns
|
|
|
|
|
+ .iter()
|
|
|
|
|
+ .any(|a| matches!(a, Annotation::GnomAD(_) | Annotation::DbSnp(_, _)))
|
|
|
|
|
+ {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let region = GenomeRange::new(
|
|
|
|
|
+ &variant.position.contig(),
|
|
|
|
|
+ variant.position.position,
|
|
|
|
|
+ variant.position.position + 1,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ let colorsdb_records = match fetch_vcf_with_reader(reader, ®ion) {
|
|
|
|
|
+ Ok(v) => v,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ warn!(
|
|
|
|
|
+ "CoLoRSdb fetch failed at {}:{}: {e}",
|
|
|
|
|
+ variant.position.contig(),
|
|
|
|
|
+ variant.position.position
|
|
|
|
|
+ );
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for dv in &colorsdb_records {
|
|
|
|
|
+ if dv != variant {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ match (&dv.infos).try_into() {
|
|
|
|
|
+ Ok(colors) => {
|
|
|
|
|
+ annotations
|
|
|
|
|
+ .insert_update(variant.hash, &[Annotation::CoLoRSdb(colors)]);
|
|
|
|
|
+ annotated_count.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
+ }
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ warn!(
|
|
|
|
|
+ "CoLoRSdb parse failed at {}:{}: {e}",
|
|
|
|
|
+ variant.position.contig(),
|
|
|
|
|
+ variant.position.position
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ info!(
|
|
|
|
|
+ "Annotated {} variants with CoLoRSdb",
|
|
|
|
|
+ annotated_count.load(Ordering::Relaxed)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+}
|