lib.rs 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627
  1. //! # 🧬 Long-read Somatic Variant Calling and Analysis Framework
  2. //!
  3. //! This Rust library provides a modular, parallelizable framework for somatic variant calling, annotation, and interpretation from long-read sequencing data. It is designed to support full pipelines for research and clinical workflows across multiple variant callers and analysis stages.
  4. //!
  5. //! The library also serves as an extensible platform that developers can leverage to add custom features, integrate new tools, and tailor workflows to specific use cases.
  6. //!
  7. //! ## 🧩 Key Features
  8. //!
  9. //! - **POD5 Demultiplexing and Alignment**: End-to-end support for processing ONT POD5 files:
  10. //! - Barcode-aware demultiplexing using metadata CSVs
  11. //! - POD5 subsetting and organization by case
  12. //! - Integration with basecallers (e.g., Dorado) for read alignment
  13. //! - **Pipeline Management**: Full orchestration of Dockerized execution pipelines for tools such as ClairS, Nanomonsv, DeepVariant, Savana, Modkit, and Severus.
  14. //! - **Flexible Configuration**: Centralized configuration system (`Config`, `CollectionsConfig`) for all modules and pipelines.
  15. //! - **Input Abstraction**: Unified handling of BAM, POD5, and VCF file collections across cohorts and directories.
  16. //! - **Variant Processing**: Modular loading, filtering, statistical analysis, and annotation of somatic and germline variants.
  17. //! - **Haplotype Phasing and Methylation**: Support for LongPhase-based phasing and Modkit methylation pileups with support for multi-threaded pileup and aggregation.
  18. //! - **Parallel Execution**: Uses `rayon` for efficient multicore parallelization over large cohorts and tasks.
  19. //!
  20. //! ## 📚 Module Highlights
  21. //!
  22. //! - `callers`: Interfaces to variant calling tools (ClairS, DeepVariant, Nanomonsv, Savana, etc...)
  23. //! - `runners`: Pipeline runners (e.g. `Somatic`, `SeverusSolo`, `LongphasePhase`) that manage end-to-end execution.
  24. //! - `collection`: Organizes input data across BAMs, VCFs, and POD5 files with auto-detection of completed runs.
  25. //! - `annotation`: VEP line parsing and high-level annotation aggregation.
  26. //! - `pipes`: Composition modules for executing pipelines across callers and post-processing steps.
  27. //! - `functions`: Custom logic for genome assembly, entropy estimation, and internal tooling.
  28. //! - `positions`, `variant`, `helpers`: Utilities for SV modeling, variant filtering, position overlap logic, and helper methods.
  29. //!
  30. //! ## ⚡ Workflow Overview
  31. //!
  32. //! ### 1. 📦 From POD5 to BAM Alignment
  33. //!
  34. //! - **Demultiplexing**: POD5 files are subset and demuxed using barcodes (via CSV metadata).
  35. //! - **Flowcell Case Management**: Each sample is identified by a [`collection::pod5::FlowCellCase`] containing its ID, time point, and POD5 directory.
  36. //! - **Alignment**: The [`commands::dorado::Dorado`] module handles alignment of POD5 reads to reference genome, producing BAMs.
  37. //!
  38. //! ```rust
  39. //! let case = FlowCellCase { id: "PATIENT1", time_point: "diag", barcode: "01", pod_dir: "...".into() };
  40. //! Dorado::init(case, Config::default())?.run_pipe()?;
  41. //! ```
  42. //!
  43. //! ### 2. 🧬 Variant Calling (BAM ➝ VCF)
  44. //!
  45. //! Using the aligned BAMs, multiple variant callers can be run in parallel. The [`callers`] and [`runners`] modules support:
  46. //!
  47. //! - **ClairS** – somatic small variant calling with LongPhase haplotagging
  48. //! - **Nanomonsv** – structural variants (SV)
  49. //! - **DeepVariant** – germline small variants
  50. //! - **Savana** – SVs and copy number variations (CNV)
  51. //! - **Modkit** – methylation pileups
  52. //! - **LongPhase** – phasing and modcalling
  53. //!
  54. //! All workflows can be triggered per-case or per-cohort using `Collections` or `Somatic` runners.
  55. //!
  56. //! ```rust
  57. //! ClairS::initialize("PATIENT1", Config::default())?.run()?;
  58. //! NanomonSV::initialize("PATIENT1", Config::default())?.run()?;
  59. //! ```
  60. //!
  61. //! ### 3. 📈 Aggregation & Statistics (VCF ➝ JSON / Stats)
  62. //!
  63. //! After variant calling:
  64. //!
  65. //! - Annotate with VEP ([`annotation`] module)
  66. //! - Load and filter with [`variant::variant_collection`]
  67. //! - Compute variant and region-level stats (e.g., mutation rates, alteration categories, coding overlaps)
  68. //!
  69. //! ```rust
  70. //! let variants = Variants::load_from_json("/path/to/somatic_variants.json.gz")?;
  71. //! let stats = VariantsStats::new(&variants, "PATIENT1", &config)?;
  72. //! stats.save_to_json("/output/path/stats.json.gz")?;
  73. //! ```
  74. //!
  75. //! ### 4. 🧠 Intelligent Task Management (`collection` module)
  76. //!
  77. //! - Auto-discovers available samples, POD5s, BAMs, and VCFs
  78. //! - Detects missing outputs and creates task lists
  79. //! - Tasks are parallelizable using Rayon and can be run on-demand
  80. //!
  81. //! ```rust
  82. //! let mut collections = Collections::new(CollectionsConfig::default())?;
  83. //! collections.todo()?; // Identify missing steps
  84. //! collections.run()?; // Run them automatically
  85. //! ```
  86. //!
  87. //! ## 🔬 Testing
  88. //!
  89. //! Integration tests demonstrate the entire pipeline. Run with logging enabled:
  90. //!
  91. //! ```bash
  92. //! export RUST_LOG=debug
  93. //! cargo test -- --nocapture
  94. //! ```
  95. //!
  96. //! ## 🧪 Example Use Cases
  97. //!
  98. //! - Full somatic variant calling pipeline on matched tumor/normal samples
  99. //! - POD5-based pipeline from raw signal to variants
  100. //! - Aggregation and annotation of SVs across a clinical cohort
  101. //! - Methylation analysis using nanopore-specific tools
  102. //! - Variant calling and analysis in large-scale longitudinal studies
  103. //!
  104. //! ## 🚀 Getting Started
  105. //!
  106. //! All workflows are initialized from `Config` and driven by the `Collections` structure:
  107. //!
  108. //! ```rust
  109. //! let collections = Collections::new(CollectionsConfig::default())?;
  110. //! collections.todo()?;
  111. //! collections.run()?;
  112. //! ```
  113. //!
  114. //! ## 🔗 References
  115. //!
  116. //! **Basecalling and alignment**
  117. //! - Dorado: <https://github.com/nanoporetech/dorado>
  118. //!
  119. //! **Variants Callers**
  120. //! - ClairS: <https://github.com/HKU-BAL/ClairS>
  121. //! - Nanomonsv: <https://github.com/friend1ws/nanomonsv>
  122. //! - Savana: <https://github.com/cortes-ciriano-lab/savana>
  123. //! - DeepVariant: <https://github.com/google/deepvariant>
  124. //! - DeepSomatic: <https://github.com/google/deepsomatic>
  125. //! - LongPhase: <https://github.com/PorubskyResearch/LongPhase>
  126. //! - Modkit: <https://github.com/nanoporetech/modkit>
  127. //!
  128. //! **Variants annotation**
  129. //! - VEP: <https://www.ensembl.org/info/docs/tools/vep/index.html>
  130. //!
  131. //! ---
  132. use std::sync::{Arc, Mutex};
  133. pub mod aligner;
  134. pub mod annotation;
  135. pub mod callers;
  136. pub mod collection;
  137. pub mod commands;
  138. pub mod config;
  139. pub mod de_novo;
  140. pub mod functions;
  141. pub mod helpers;
  142. pub mod io;
  143. pub mod locker;
  144. pub mod math;
  145. pub mod pipes;
  146. pub mod positions;
  147. pub mod runners;
  148. pub mod scan;
  149. pub mod slurm_helpers;
  150. pub mod uniqueness;
  151. pub mod variant;
  152. #[macro_use]
  153. extern crate lazy_static;
  154. // Define DOCKER_ID lock for handling Docker kill when ctrlc is pressed
  155. lazy_static! {
  156. static ref DOCKER_ID: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
  157. static ref TEST_DIR: String = "/mnt/beegfs02/scratch/t_steimle/test_data".to_string();
  158. }
  159. #[cfg(test)]
  160. mod tests {
  161. use std::{collections::HashMap, path::Path};
  162. use annotation::{
  163. vep::{VepLine, VEP},
  164. Annotations,
  165. };
  166. use collection::bam::{counts_at, counts_ins_at, nt_pileup, WGSBam};
  167. use functions::assembler::{Assembler, AssemblerConfig};
  168. use helpers::estimate_shannon_entropy;
  169. use io::bed::read_bed;
  170. use log::{error, info};
  171. use positions::{overlaps_par, GenomePosition, GenomeRange};
  172. use rayon::prelude::*;
  173. use variant::{variant_collection, vcf_variant::VcfVariant};
  174. use self::{/* collection::pod5::{FlowCellCase, Pod5Collection}, */ config::Config};
  175. use super::*;
  176. use crate::{
  177. annotation::{Annotation, colorscb::annotate_colorsdb},
  178. collection::{
  179. bam::{self},
  180. flowcells::{FlowCells, scan_archive},
  181. vcf::VcfCollection,
  182. },
  183. helpers::find_files,
  184. io::{bed::bedrow_overlaps_par, dict::read_dict, gff::features_ranges},
  185. positions::{merge_overlapping_genome_ranges, range_intersection_par, sort_ranges},
  186. scan::scan::somatic_scan,
  187. variant::{
  188. variant_collection::{
  189. ExternalAnnotation, VariantCollection, group_variants_by_bnd_desc, group_variants_by_bnd_rc
  190. },
  191. variants_stats::{self, VariantsStats, somatic_depth_quality_ranges},
  192. vcf_variant::{AlterationCategory, BNDDesc, BNDGraph, ToBNDGraph},
  193. },
  194. };
  195. // export RUST_LOG="debug"
  196. fn init() {
  197. let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
  198. .try_init();
  199. }
  200. // #[test]
  201. // fn run_dorado() -> anyhow::Result<()> {
  202. // init();
  203. // let case = FlowCellCase {
  204. // id: "CONSIGNY".to_string(),
  205. // time_point: "mrd".to_string(), barcode: "07".to_string(), pod_dir: "/mnt/beegfs01/ data/run_data/20240326-CL/CONSIGNY-MRD-NB07_RICCO-DIAG-NB08/20240326_1355_1E_PAU78333_bc25da25/pod5_pass/barcode07".into()
  206. // };
  207. // dorado::Dorado::init(case, Config::default())?.run_pipe()
  208. // }
  209. // #[test]
  210. // fn pod5() -> anyhow::Result<()> {
  211. // let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
  212. // .build();
  213. //
  214. // let coll = Pod5Collection::new(
  215. // "/data/run_data",
  216. // "/data/flow_cells.tsv",
  217. // "/data/longreads_basic_pipe",
  218. // )?;
  219. // println!("{coll:#?}");
  220. // // let runs = Runs::import_dir("/home/prom/store/banana-pool/run_data", "/data/flow_cells.tsv")?;
  221. // Ok(())
  222. // }
  223. #[test]
  224. fn bam() -> anyhow::Result<()> {
  225. init();
  226. let bam_collection = bam::load_bam_collection("/data/longreads_basic_pipe");
  227. bam_collection
  228. .bams
  229. .iter()
  230. // .filter(|b| matches!(b.bam_type, BamType::Panel(_)))
  231. .for_each(|b| println!("{b:#?}"));
  232. let u = bam_collection.get("PARACHINI", "mrd");
  233. println!("{u:#?}");
  234. Ok(())
  235. }
  236. #[test]
  237. fn vcf() -> anyhow::Result<()> {
  238. init();
  239. let mut vcf_collection = VcfCollection::new("/data/longreads_basic_pipe");
  240. vcf_collection.sort_by_id();
  241. vcf_collection
  242. .vcfs
  243. .iter()
  244. .for_each(|v| v.println().unwrap());
  245. Ok(())
  246. }
  247. // pod5 view -I /data/run_data/20240903-CL/ARMEM-DG-N02_ASSJU-DG-N03/20240903_1428_1B_PAW47629_fc24c3cf/pod5/PAW47629_fc24c3cf_77b07847_0.pod5 | head -5000 | awk '{if(NR==1){print "target,"$0}else{print "subset_1.pod5,"$0}}' > /tmp/subset_ids.csv
  248. // pod5 subset /data/run_data/20240903-CL/ARMEM-DG-N02_ASSJU-DG-N03/20240903_1428_1B_PAW47629_fc24c3cf/pod5/PAW47629_fc24c3cf_77b07847_0.pod5 --csv /tmp/subset_ids.csv -o /data/test_suite/pod5/muxed/
  249. // #[test]
  250. // fn mux() -> anyhow::Result<()> {
  251. // init();
  252. // let result_dir = "/data/test_suite/results".to_string();
  253. // let cases = vec![
  254. // FlowCellCase { id: "test_02".to_string(), time_point: "diag".to_string(), barcode: "02".to_string(), pod_dir: "/data/test_suite/pod5/muxed".into() },
  255. // FlowCellCase { id: "test_03".to_string(), time_point: "diag".to_string(), barcode: "03".to_string(), pod_dir: "/data/test_suite/pod5/muxed".into() },
  256. // ];
  257. //
  258. // cases.iter().for_each(|c| {
  259. // let dir = format!("{result_dir}/{}", c.id);
  260. // if Path::new(&dir).exists() {
  261. // fs::remove_dir_all(dir).unwrap();
  262. // }
  263. // });
  264. // let config = Config { result_dir, ..Default::default() };
  265. // Dorado::from_mux(cases, config)
  266. // }
  267. // #[test_log::test]
  268. // fn clairs() -> anyhow::Result<()> {
  269. // let config = ClairSConfig {
  270. // result_dir: "/data/test".to_string(),
  271. // ..ClairSConfig::default()
  272. // };
  273. // ClairS::new("test_a", "/data/test_data/subset.bam", "/data/test_data/subset_mrd.bam", config).run()
  274. // }
  275. // #[test]
  276. // fn nanomonsv() -> anyhow::Result<()> {
  277. // init();
  278. // let id = "HAMROUNE";
  279. // NanomonSV::initialize(id, Config::default())?.run()
  280. // }
  281. //
  282. // #[test]
  283. // fn nanomonsv_version() -> anyhow::Result<()> {
  284. // init();
  285. // let v = NanomonSV::version(&Config::default())?;
  286. // println!("NanomonSV version: {v}");
  287. // let v = DeepVariant::version(&Config::default())?;
  288. // println!("DeepVariant version: {v}");
  289. // let v = Savana::version(&Config::default())?;
  290. // println!("Savana version: {v}");
  291. // let v = DeepSomatic::version(&Config::default())?;
  292. // println!("DeepSomatic version: {v}");
  293. // let v = ClairS::version(&Config::default())?;
  294. // println!("ClairS version: {v}");
  295. // Ok(())
  296. // }
  297. //
  298. // #[test]
  299. // fn nanomonsv_solo() -> anyhow::Result<()> {
  300. // init();
  301. // NanomonSVSolo::initialize("LAKHDHAR", "diag", Config::default())?.run()
  302. // }
  303. #[test]
  304. fn run_assemblers() -> anyhow::Result<()> {
  305. Assembler::new(
  306. "CAMEL".to_string(),
  307. "diag".to_string(),
  308. AssemblerConfig::default(),
  309. )
  310. .run()
  311. }
  312. // #[test]
  313. // fn run_dmr_par() -> anyhow::Result<()> {
  314. // init();
  315. // let collections = Collections::new(
  316. // CollectionsConfig::default()
  317. // )?;
  318. // let tasks = collections.todo_dmr_c_diag_mrd();
  319. // tasks.iter().for_each(|t| info!("{t}"));
  320. // let len = tasks.len();
  321. // // let pool = ThreadPoolBuilder::new().num_threads(10).build().unwrap();
  322. // // pool.install(|| {
  323. // // tasks.par_iter().enumerate().for_each(|(i, t)| {
  324. // // let config = ModkitConfig {threads: 2, ..Default::default() };
  325. // // if let collection::CollectionsTasks::DMRCDiagMrd { id, .. } = t { let _ = dmr_c_mrd_diag(id, &config); }
  326. // // println!("⚡ {i}/{len}");
  327. // // });
  328. // // });
  329. // Ok(())
  330. // }
  331. // #[test]
  332. // fn run_severus() -> anyhow::Result<()> {
  333. // init();
  334. // Severus::initialize("BANGA", Config::default())?.run()
  335. // }
  336. //
  337. // #[test]
  338. // fn run_severus_solo() -> anyhow::Result<()> {
  339. // init();
  340. // SeverusSolo::initialize("LAKHDHAR", "diag", Config::default())?.run()
  341. // }
  342. //
  343. // #[test]
  344. // fn check_versions() -> anyhow::Result<()> {
  345. // init();
  346. // let config = Config::default();
  347. // let v = Savana::version(&config)?;
  348. // info!("Savanna version {v}");
  349. // let v = Severus::version(&config)?;
  350. // info!("Severus version {v}");
  351. // Ok(())
  352. // }
  353. // #[test]
  354. // fn run_deepvariant() -> anyhow::Result<()> {
  355. // init();
  356. // DeepVariant::initialize("HAMROUNE", "diag", Config::default())?.run()
  357. // }
  358. // #[test]
  359. // fn run_clairs() -> anyhow::Result<()> {
  360. // init();
  361. // ClairS::initialize("ADJAGBA", Config::default())?.run()
  362. // }
  363. // #[test]
  364. // fn run_longphase() -> anyhow::Result<()> {
  365. // init();
  366. // let id = "BECERRA";
  367. // let diag_bam = format!("/data/longreads_basic_pipe/{id}/diag/{id}_diag_hs1.bam");
  368. // let vcf = format!("/data/longreads_basic_pipe/{id}/diag/ClairS/clair3_normal_tumoral_germline_output.vcf.gz");
  369. // let mrd_bam = format!("/data/longreads_basic_pipe/{id}/mrd/{id}_mrd_hs1.bam");
  370. //
  371. // LongphaseHap::new(id, &diag_bam, &vcf, LongphaseConfig::default()).run()?;
  372. // LongphaseHap::new(id, &mrd_bam, &vcf, LongphaseConfig::default()).run()
  373. // }
  374. // #[test]
  375. // fn run_longphase_modcall() -> anyhow::Result<()> {
  376. // init();
  377. // let id = "ADJAGBA";
  378. // let time = "diag";
  379. // LongphaseModcallSolo::initialize(id, time, Config::default())?.run()
  380. // }
  381. //
  382. // #[test]
  383. // fn run_longphase_phase() -> anyhow::Result<()> {
  384. // init();
  385. // let id = "ADJAGBA";
  386. // LongphasePhase::initialize(id, Config::default())?.run()
  387. // }
  388. #[test]
  389. fn snv_parse() -> anyhow::Result<()> {
  390. init();
  391. let config = Config::default();
  392. // ClairS
  393. let row = "chr1\t10407\t.\tA\tG\t10.1\tPASS\tF\tGT:GQ:DP:AD:AF\t0/1:10:31:10,20:0.6452";
  394. let variant: VcfVariant = row.parse()?;
  395. let var_string = variant.into_vcf_row();
  396. assert_eq!(row, &var_string);
  397. let mut variant_col = VariantCollection {
  398. variants: vec![variant],
  399. vcf: collection::vcf::Vcf::new(
  400. "/data/longreads_basic_pipe/ACHITE/diag/ClairS/ACHITE_diag_clairs_PASSED.vcf.gz"
  401. .into(),
  402. )?,
  403. caller: Annotation::Callers(annotation::Caller::ClairS, annotation::Sample::Somatic),
  404. };
  405. let annotations = Annotations::default();
  406. variant_col.annotate_with_constit_bam(
  407. &annotations,
  408. "/data/longreads_basic_pipe/ACHITE/mrd/ACHITE_mrd_hs1.bam",
  409. &config,
  410. )?;
  411. // DeepVariant
  412. let row =
  413. "chr1\t10407\t.\tA\tG\t7.4\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t1/1:5:9:1,8:0.888889:5,5,0";
  414. variant_col.variants.push(row.parse()?);
  415. let anns: Vec<Vec<Annotation>> = variant_col
  416. .variants
  417. .iter()
  418. .filter_map(|e| {
  419. annotations
  420. .store
  421. .get(&e.hash())
  422. .map(|v| v.value().to_owned())
  423. })
  424. .collect();
  425. assert_eq!(anns[0], anns[1]);
  426. Ok(())
  427. }
  428. #[test]
  429. fn deletion_parse() -> anyhow::Result<()> {
  430. init();
  431. // Clairs
  432. let row = "chr1\t16760\t.\tAaag\tA\t8.48\tPASS\tF\tGT:GQ:DP:AD:AF\t0/1:8:39:22,17:0.4359";
  433. let variant: VcfVariant = row.parse()?;
  434. let var_string = variant.into_vcf_row();
  435. // case are not keeped
  436. assert_eq!(
  437. &var_string,
  438. "chr1\t16760\t.\tAAAG\tA\t8.48\tPASS\tF\tGT:GQ:DP:AD:AF\t0/1:8:39:22,17:0.4359"
  439. );
  440. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  441. let mut variant_col = VariantCollection {
  442. variants: vec![variant],
  443. vcf: collection::vcf::Vcf::new(
  444. "/data/longreads_basic_pipe/ACHITE/diag/ClairS/ACHITE_diag_clairs_PASSED.vcf.gz"
  445. .into(),
  446. )?,
  447. caller: Annotation::Callers(annotation::Caller::ClairS, annotation::Sample::Somatic),
  448. };
  449. let annotations = Annotations::default();
  450. // DeepVariant, VAF is not an f32 at it should be, sometimes too long removed the last number for eq
  451. let row = "chr12\t326911\t.\tGTGTA\tG\t4.7\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t0/1:5:8:5,3:0.37500:2,0,21";
  452. let variant: VcfVariant = row.parse()?;
  453. let var_string = variant.into_vcf_row();
  454. assert_eq!(row, &var_string);
  455. variant_col.variants.push(row.parse()?);
  456. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  457. // Severus, 0000 added to VAF and hVAF
  458. let row = "chr10\t108974982\tseverus_DEL885\tN\t<DEL>\t60\tPASS\tPRECISE;SVTYPE=DEL;SVLEN=474;END=108975456;STRANDS=+-;INSIDE_VNTR=TRUE;MAPQ=60\tGT:GQ:VAF:hVAF:DR:DV\t0/1:61:0.30000:0.30000,0.00000,0.00000:7:3";
  459. let variant: VcfVariant = row.parse()?;
  460. let var_string = variant.into_vcf_row();
  461. assert_eq!(row, &var_string);
  462. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  463. variant_col.variants.push(row.parse()?);
  464. // Nanomonsv dont parse last format remove: \t22:0 and nt putted in uppercase
  465. let row = "chr14\t5247080\td_106\tG\t<DEL>\t.\tPASS\tEND=5247255;SVTYPE=DEL;SVLEN=-175;SVINSLEN=39;SVINSSEQ=ATAACCCAGGTGATATAACACTTCTTTAGGCTCTGCCTA\tTR:VR\t18:3";
  466. let variant: VcfVariant = row.parse()?;
  467. let var_string = variant.into_vcf_row();
  468. assert_eq!(row, &var_string);
  469. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  470. variant_col.variants.push(row.parse()?);
  471. let row = "chr1\t20654667\t.\tTG\tT\t3.5\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t0/1:3:23:20,3:0.13044:0,0,16";
  472. let variant: VcfVariant = row.parse()?;
  473. let var_string = variant.into_vcf_row();
  474. assert_eq!(row, &var_string);
  475. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  476. variant_col.variants.push(row.parse()?);
  477. let row = "chr1\t2160094\t.\tTCTGACAGCCTGGAACAGCACCCACAACCGCAGGTGAGCATCTGACAGCCCGCAGCAGCACCCACACGCACAGGTGAGAATCTGACAGCCCGGAGCAGCACCCACACAGGCAGGGGAGCATCTGACATCCTGGAGCAGCACCGACAACCCCAGGTGAGCAACTGAGAGCCTGGAACAGCACCCACACCCCCAGGTGAGAATCTGACAGCCTGGAAGAGCACCCCACATCCCCGGGTGAGCATCTGACAGCCTGGAACAGCATCAACACCCCCAGGTGAGCATCCGATAGCCTGGAGCAGCACCCACACCCTCAGGTGAGCATCTGACAGCCTGGAACAGCAACCACACCCCCAGGTGAACATCTGACAGCCCGGAGCAGCACCCACACCCCCAGGTGAGCATCTGACAGCCTGGAACAGCACCCACACCCCCAGGAGAGCATCCGGTAGCCTGGAGCAGAACCCACACCCACAGGCGAGCATCTGACAGCCTGGGTTGGCACCCACACCCCCAGGTGAGCATCTGATGGTCTGGAGCAGCACCCACACCTACAGGAGAGCATCTGACAACCTGCAACAGAACCCAAACCCCCAGGTGAGCATCTGACAGACTGGAACAGCACCCTGCACCCCCAGGTGAGTATCTGACGGCCTGGAACAGAACACACAAGCCCAGGTGAGCATCCGACAGCCTGGAGCAGCACCCACACCCCCAGGTGAGCATCAGACAGCCTGGAGTAGCACCCCACACCCCCAGGTGAGCATCCGACAGCCTGGAGCAGCACCCACACTCACCAGGTGAGCATGTGACATGCTGGAACAGCACCCACACCCCCAGGCGAGCATCTGACAGCCTGGAGCGGCACCCCACACCCCCAGGTGAGCATCGGACAGCCTGGAGCAGTACCCACACCCCCAGGTGAGCATCCGACAGCCTGGAACAGCACCCACACCCCCAGGTGAGCATCTGACAGACAGGAACAGCACCCACATGTCCAGGTGAGCCTCTGACAGACTGGAACAGCACGCGCACCCCCAGGTGAGCATCTGACAGGCTGGAACAGCACCCACACCCCCAGGAGAGCATCTTACAGCATGTAACAGCACCCACACACCCACGTGAGCATCTGACAGCCTGGAACAGCACCCTGCACCCCCAGGTGCGCACGTGACAGCCTGGAACAGCACCCACACCCCCAGGCGAGCATCGGACAGCCTGGAGCAGCACCCCACACCCGCAGGTGAGCATCCGACAGCCTGGAGCAGCACCCACACCCCCAGGTGAGCATGTGACAGCCTGGAACAGCACCCACACCCCCAGGCGAGCATCTGACAGCCTGGAGCAACACCCCACACCCCCAGGTGAGCATCGAACTGTCTGGAGCAGCACCCACAACCACAGGTGGGCATCGGAGAGAGTGGAGCAGCGCCCAGACCACCAGGCAAGCATCTGACAGCCTGGAGCAGTGCCCACACCCCCATGTGAGCATCTGACAGTATGGAGCAGCACCCACAGCCCAAGGTGAGCATCTGACAACCTGGAGCAGCACCCACACCCCCAGGCGAGCATCTGAACGCACAGAGCAGCACCCACACCCCCAGGCGAGCATCCGACAGCGTGGAGCAGCACCCACACTCCCAGGTGCGCGTGTGATGGTCTGGGGCAGCACCCACACACACAGGTGAGCCTGCGACAGCCTGGAGCAGCACCCACAGCCCCAGGTGAGCATCTGATGGTCTGGAGCAGCACCCACAACCACAGGTGAGCATCGGAGAGACTGGAGCAGCGCCGAAACCCCCAGGCGAACATTTGAGAGCCTGGAGCAGTGCCGACACCCCCAGGTGAGCATCTGACACCGTGGAGCAGCACCCACAGCCCAAGGTGAGCATCTGACAACCTGGAGCAGCACCCACAGCCCCAGGCGAGCATTTGAACGCACGGAGCAGGACCTACAGCCCCAGGCGAGCATCCGACAGCCTGGAGCAGCACCCACACACCCAGGTGAGCATCTGACAGCCTGGAGCAGCACCCACAACCCCAGGGGAGCATCTGACCGCATGGAATGGCATCCTCACCCGTAGGTGAACGTCCGACAGCCTGGAGCAGCACCCACACCCCCAGGTGAGCATCTGACAGCCTGGAACAGCACCTGCACCCCCGGGTGAGGATCAGATAGCCTGGAGCAGCACCCACACTCCAGGTGAGCATCTGACAGCCTGAAGCAGCACCCACACCAACAGGTGAGCATCTGACAGCCTGGAAGAGCACCCACAACCCCACGTGAGCATCTGACAGCCTGGAACAGGACCCTGCACCCCAAAGTGAGCATCTGACAACCTGGAGCAGGAACCACAACCCCAGGTGAGCATCTGATAGCCTGGAATAGCACCCACACACCCAGGTGAGCATCTGAGAGCCTGGAGCAGCACCCACACCCCCAGGTGAGCATCCGACAGCCTGGAACAGTACCCACACACCCAGGCGAGCATCTGACAGCCTGAAACAGCACACACACTCCCAGGTGAGCATCTCATATGCTGGAACAGCACCCACACCCCCAGGTGAGCATCTGACTGCCCGGAGCAGCACGCACACCCCCGGGTGAGCATCTGATAGCCTGGAACAGCACCCACACCCCCAGATGAGCATCCGACAGCCTGGAGCGGAGCCCACAGCCCCAGGCGAGCATCTGACAGCCTGGAACAGCACCCTGCATCCCCGGGTGAGGATCAGACAGCCTGGAGCAGCACCCACACTCCAGGTGAGCATCTGACAGCCTGAAGCAGCACCCACACCAACAGGTGAGCCTCTGACAGCCTGCAACAGCACCCACACCCCAAGGTGAGCATCTGACAGCCTGGAAGAGGACCCTGCAGCCCCAAGTGAGCATCTGACAACCTGAAGCAGCAACCACACTCCAAGGTGAGCATCCAACAGCCTGGAACAGCACCCACACACCCAGGTGAGCATCTGACAGCCTGGAGCAGCACCACACCCCCAGGTGAGCATCCGACAGCCTGGAACAGCACCTACAAACCCAGGAGAGCATCCGACAGCCTGGAGCAGCACCCACACCCCCAGGCGAGCATCTGACAGCCCGGAGCAGCACGCAAACCCCCAGGTGAGCATCTGATACCCTGGAACAGCACCCACACACCCAGGTGAGCATCCGACAGCCTAGAGCTGAACCCACACCAACAGGAGAGCATCTGACAGCCTGGGTCGGCACCCACACCCCCAGGTGAGCATCTGACAGCCTGGAACAGCACCCTGCACCCTCAGGTGCGCACGTGACAGCCTGGAACAGCACCCACACACCCAGGCGAGCATCTGACGGCCTGGAACGGCACCCACACCCCGAGGCGAGCATGGGACAGCCTGGAGAGCAGCCACACCCTCAGATGAGCATCTGACAGCCTGGAACAGCACCCTGCACCCCCAGGTGAGCATCTGACAGCCTGGAACAGGACCCACGTCCCCAGGCGAGCAA\tT\t3.1\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t0/1:3:15:11,4:0.26667:0,0,22";
  478. let variant: VcfVariant = row.parse()?;
  479. let var_string = variant.into_vcf_row();
  480. assert_eq!(row, &var_string);
  481. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  482. variant_col.variants.push(row.parse()?);
  483. let row = "chr1\t27073397\t.\tTGGGG\tT\t28.9\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t./.:2:13:1,3:0.23077:24,2,26";
  484. let variant: VcfVariant = row.parse()?;
  485. let var_string = variant.into_vcf_row();
  486. assert_eq!(row, &var_string);
  487. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  488. variant_col.variants.push(row.parse()?);
  489. let row = "chrY\t639648\t.\tCTTTTTT\tC\t10.4\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t0/1:1:7:0,2:0.28571:4,0,2";
  490. let variant: VcfVariant = row.parse()?;
  491. let var_string = variant.into_vcf_row();
  492. assert_eq!(row, &var_string);
  493. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  494. variant_col.variants.push(row.parse()?);
  495. let row = "chr11\t31260259\tseverus_BND1077_1\tN\t]chr11:36214171]N\t60\tPASS\tPRECISE;SVTYPE=BND;SVLEN=4953912;MATE_ID=severus_BND1077_2;STRANDS=+-;MAPQ=60\tGT:GQ:VAF:hVAF:DR:DV\t0/1:68:0.67000:0.67000,0.00000,0.00000:4:8";
  496. let variant: VcfVariant = row.parse()?;
  497. let var_string = variant.into_vcf_row();
  498. assert_eq!(row, &var_string);
  499. assert_eq!(AlterationCategory::DEL, variant.alteration_category());
  500. println!("{:#?}", variant.deletion_desc());
  501. variant_col.variants.push(row.parse()?);
  502. variant_col.annotate_with_constit_bam(
  503. &annotations,
  504. "/data/longreads_basic_pipe/ACHITE/mrd/ACHITE_mrd_hs1.bam",
  505. &Config::default(),
  506. )?;
  507. Ok(())
  508. }
  509. #[test]
  510. fn insertion_parse() -> anyhow::Result<()> {
  511. init();
  512. // Clairs
  513. let row = "chr1\t23005\t.\tT\tTCAC\t3.1\tPASS\tF\tGT:GQ:DP:AD:A\t0/1:3:13:9,4:0.3077";
  514. let variant: VcfVariant = row.parse()?;
  515. let var_string = variant.into_vcf_row();
  516. assert_eq!(&var_string, row);
  517. assert_eq!(AlterationCategory::INS, variant.alteration_category());
  518. let mut variant_col = VariantCollection {
  519. variants: vec![variant],
  520. vcf: collection::vcf::Vcf::new(
  521. "/data/longreads_basic_pipe/ACHITE/diag/ClairS/ACHITE_diag_clairs_PASSED.vcf.gz"
  522. .into(),
  523. )?,
  524. caller: Annotation::Callers(annotation::Caller::ClairS, annotation::Sample::Somatic),
  525. };
  526. let annotations = Annotations::default();
  527. // DeepVariant, VAF is not an f32 at it should be, sometimes too long removed the last number for eq
  528. let row = "chrX\t152732993\t.\tG\tGTTTTTTTTT\t18.9\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t1/1:3:6:0,5:0.50000:15,7,3";
  529. let variant: VcfVariant = row.parse()?;
  530. let var_string = variant.into_vcf_row();
  531. assert_eq!(row, &var_string);
  532. // variant_col.variants.push(row.parse()?);
  533. assert_eq!(AlterationCategory::INS, variant.alteration_category());
  534. // Severus, 0000 added to VAF and hVAF
  535. let row = "chr11\t26669932\tseverus_INS9403\tN\tT\t60\tPASS\tPRECISE;SVTYPE=INS;SVLEN=460;INSIDE_VNTR=TRUE;MAPQ=60\tGT:GQ:VAF:hVAF:DR:DV\t0/1:71:0.27000:0.27000,0.00000,0.00000:8:3";
  536. let variant: VcfVariant = row.parse()?;
  537. let var_string = variant.into_vcf_row();
  538. assert_eq!(row, &var_string);
  539. assert_eq!(AlterationCategory::INS, variant.alteration_category());
  540. // variant_col.variants.push(row.parse()?);
  541. // Nanomonsv dont parse last format remove: \t22:0 and nt putted in uppercase
  542. let row = "chr8\t87940084\td_333\tT\t<INS>\t.\tPASS\tEND=87940201;SVTYPE=INS;SVINSLEN=172;SVINSSEQ=TA\tTR:VR\t9:5";
  543. let variant: VcfVariant = row.parse()?;
  544. let var_string = variant.into_vcf_row();
  545. assert_eq!(row, &var_string);
  546. assert_eq!(AlterationCategory::INS, variant.alteration_category());
  547. // variant_col.variants.push(row.parse()?);
  548. //
  549. let row = "chr5\t36122736\t.\tT\tTGCTCCG\t3.9\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t0/1:4:28:11,16:0.57143:1,0,37";
  550. let variant: VcfVariant = row.parse()?;
  551. let var_string = variant.into_vcf_row();
  552. assert_eq!(row, &var_string);
  553. assert_eq!(AlterationCategory::INS, variant.alteration_category());
  554. variant_col.variants = Vec::new();
  555. variant_col.variants.push(row.parse()?);
  556. variant_col.annotate_with_constit_bam(
  557. &annotations,
  558. "/data/longreads_basic_pipe/PASSARD/mrd/PASSARD_mrd_hs1.bam",
  559. &Config::default(),
  560. )?;
  561. println!("{variant_col:?}");
  562. println!("{annotations:?}");
  563. Ok(())
  564. }
  565. #[test]
  566. fn trl_parse() -> anyhow::Result<()> {
  567. init();
  568. let row = "chr2\t207968575\tID_16420_1\ta\ta]chr11:41497080]\t.\tPASS\tSVTYPE=BND;MATEID=ID_16420_2;TUMOUR_READ_SUPPORT=4;TUMOUR_ALN_SUPPORT=4;NORMAL_READ_SUPPORT=0;NORMAL_ALN_SUPPORT=0;SVLEN=0;BP_NOTATION=++;SOURCE=SUPPLEMENTARY;CLUSTERED_READS_TUMOUR=4;CLUSTERED_READS_NORMAL=0;ORIGIN_STARTS_STD_DEV=0.433;ORIGIN_MAPQ_MEAN=56.25;ORIGIN_EVENT_SIZE_STD_DEV=0;ORIGIN_EVENT_SIZE_MEDIAN=0;ORIGIN_EVENT_SIZE_MEAN=0;END_STARTS_STD_DEV=17.754;END_MAPQ_MEAN=56.25;END_EVENT_SIZE_STD_DEV=0;END_EVENT_SIZE_MEDIAN=0;END_EVENT_SIZE_MEAN=0;TUMOUR_DP_BEFORE=8,11;TUMOUR_DP_AT=4,11;TUMOUR_DP_AFTER=4,11;NORMAL_DP_BEFORE=7,16;NORMAL_DP_AT=7,16;NORMAL_DP_AFTER=7,16;TUMOUR_AF=1,0.364;NORMAL_AF=0,0;TUMOUR_TOTAL_HP_AT=1,3,0;NORMAL_TOTAL_HP_AT=4,3,0;TUMOUR_ALT_HP=2,1,1;TUMOUR_PS=207946665;NORMAL_ALT_HP=0,0,0;CLASS=PREDICTED_SOMATIC\tGT\t0/1";
  569. let variant: VcfVariant = row.parse()?;
  570. // let var_string = variant.into_vcf_row();
  571. let u = variant.n_alt_depth();
  572. println!("{u:?}");
  573. Ok(())
  574. }
  575. #[test]
  576. fn dup_parse() -> anyhow::Result<()> {
  577. init();
  578. let row = "chr9\t91352364\tID_14667_1\tC\t]chr9:98584394]C\t.\tPASS\tSVTYPE=BND;MATEID=ID_14667_2;TUMOUR_READ_SUPPORT=3;TUMOUR_ALN_SUPPORT=3;NORMAL_READ_SUPPORT=0;NORMAL_ALN_SUPPORT=0;SVLEN=7232030;BP_NOTATION=-+;SOURCE=SUPPLEMENTARY;CLUSTERED_READS_TUMOUR=3;CLUSTERED_READS_NORMAL=0;ORIGIN_STARTS_STD_DEV=0.471;ORIGIN_MAPQ_MEAN=60;ORIGIN_EVENT_SIZE_STD_DEV=8.524;ORIGIN_EVENT_SIZE_MEDIAN=7232030;ORIGIN_EVENT_SIZE_MEAN=7232020;END_STARTS_STD_DEV=8.731;END_MAPQ_MEAN=60;END_EVENT_SIZE_STD_DEV=8.524;END_EVENT_SIZE_MEDIAN=7232030;END_EVENT_SIZE_MEAN=7232020;TUMOUR_DP_BEFORE=16,38;TUMOUR_DP_AT=20,33;TUMOUR_DP_AFTER=20,33;NORMAL_DP_BEFORE=26,22;NORMAL_DP_AT=26,22;NORMAL_DP_AFTER=26,22;TUMOUR_AF=0.15,0.091;NORMAL_AF=0,0;TUMOUR_TOTAL_HP_AT=7,12,1;NORMAL_TOTAL_HP_AT=13,13,0;TUMOUR_ALT_HP=0,2,1;TUMOUR_PS=90796185;NORMAL_ALT_HP=0,0,0;CLASS=PREDICTED_SOMATIC\tGT\t0/1";
  579. let variant: VcfVariant = row.parse()?;
  580. assert_eq!(AlterationCategory::DUP, variant.alteration_category());
  581. let row = "chr9\t98584394\tID_14667_2\tC\tC[chr9:91352364[\t.\tPASS\tSVTYPE=BND;MATEID=ID_14667_1;TUMOUR_READ_SUPPORT=3;TUMOUR_ALN_SUPPORT=3;NORMAL_READ_SUPPORT=0;NORMAL_ALN_SUPPORT=0;SVLEN=7232030;BP_NOTATION=-+;SOURCE=SUPPLEMENTARY;CLUSTERED_READS_TUMOUR=3;CLUSTERED_READS_NORMAL=0;ORIGIN_STARTS_STD_DEV=0.471;ORIGIN_MAPQ_MEAN=60;ORIGIN_EVENT_SIZE_STD_DEV=8.524;ORIGIN_EVENT_SIZE_MEDIAN=7232030;ORIGIN_EVENT_SIZE_MEAN=7232020;END_STARTS_STD_DEV=8.731;END_MAPQ_MEAN=60;END_EVENT_SIZE_STD_DEV=8.524;END_EVENT_SIZE_MEDIAN=7232030;END_EVENT_SIZE_MEAN=7232020;TUMOUR_DP_BEFORE=38,16;TUMOUR_DP_AT=33,20;TUMOUR_DP_AFTER=33,20;NORMAL_DP_BEFORE=22,26;NORMAL_DP_AT=22,26;NORMAL_DP_AFTER=22,26;TUMOUR_AF=0.091,0.15;NORMAL_AF=0,0;TUMOUR_TOTAL_HP_AT=15,18,0;NORMAL_TOTAL_HP_AT=11,10,1;TUMOUR_ALT_HP=1,0,2;TUMOUR_PS=98176815;NORMAL_ALT_HP=0,0,0;CLASS=PREDICTED_SOMATIC\tGT\t0/1";
  582. let variant: VcfVariant = row.parse()?;
  583. assert_eq!(AlterationCategory::DUP, variant.alteration_category());
  584. let row = "chr1\t9218455\tr_0\tC\t<DUP>\t.\tPASS\tSVTYPE=DUP;SVLEN=12572;END=9231027\tTR:VR\t37:9";
  585. let variant: VcfVariant = row.parse()?;
  586. println!("{:#?}", variant.alteration_category());
  587. println!("{:#?}", variant.bnd_desc());
  588. let row = "chr1\t47110295\tseverus_BND178_1\tN\t]chr1:47192169]N\t60.00\tPASS\tPRECISE;SVTYPE=BND;SVLEN=81874;MATE_ID=severus_BND178_2;STRANDS=+-;MAPQ=60\tGT:GQ:VAF:hVAF:DR:DV\t0/1:66:0.38000:0.38000,0.00000,0.00000:8:5";
  589. let variant: VcfVariant = row.parse()?;
  590. println!("{:#?}", variant.alteration_category());
  591. println!("{:#?}", variant.bnd_desc());
  592. Ok(())
  593. }
  594. #[test]
  595. fn variant_parse() -> anyhow::Result<()> {
  596. let row =
  597. "chr1\t1366\t.\tC\tCCCT\t8.2\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t1/1:4:6:1,4:0.66667:6,4,0";
  598. let variant: VcfVariant = row.parse()?;
  599. let var_string = variant.into_vcf_row();
  600. assert_eq!(row, &var_string);
  601. let row = "chr1\t1366\t.\tC\tCCCT\t8.2\tPASS\t.";
  602. let variant: VcfVariant = row.parse()?;
  603. let var_string = variant.into_vcf_row();
  604. assert_eq!(row, &var_string);
  605. let row = "chr1\t2628434\t.\tC\tT\t17.973\tPASS\tH;FAU=0;FCU=7;FGU=0;FTU=7;RAU=0;RCU=2;RGU=0;RTU=2\tGT:GQ:DP:AF:AD:NAF:NDP:NAD:AU:CU:GU:TU:NAU:NCU:NGU:NTU\t0/1:17:18:0.5:0,9:0:11:0,0:0:9:0:9:0:11:0:0";
  606. let variant: VcfVariant = row.parse()?;
  607. let var_string = variant.into_vcf_row();
  608. assert_eq!(row, &var_string);
  609. let row = "chr1\t52232\t.\tC\tCT\t18\t.\t.\tGT:GQ:DP:AD:AF\t1/.:1:24:3,5:0.208333";
  610. let variant: VcfVariant = row.parse()?;
  611. let var_string = variant.into_vcf_row();
  612. assert_eq!(row, &var_string);
  613. let row = "chr1\t52232\t.\tC\tCT\t18\t.\t.\tGT:GQ:DP:AD:AF\t1/1:1:24:3,5:0.208333";
  614. let variant_b: VcfVariant = row.parse()?;
  615. assert_eq!(variant, variant_b);
  616. let row = "chr1\t475157\t.\tA\tG\t12.301\tPASS\tH;FAU=2;FCU=0;FGU=2;FTU=0;RAU=3;RCU=0;RGU=3;RTU=0\tGT:GQ:DP:AF:AD:NAF:NDP:NAD:AU:CU:GU:TU:NAU:NCU:NGU:NTU\t0/1:12:10:0.5:0,5:0.0769:13:0,1:5:0:5:0:12:0:1:0";
  617. let variant: VcfVariant = row.parse()?;
  618. let var_string = variant.into_vcf_row();
  619. assert_eq!(row, &var_string);
  620. let row = "chr1\t161417408\tr_10_0\tT\t[chr1:161417447[TTGGCAGGTTCC\t.\tPASS\tSVTYPE=BND;MATEID=r_10_1;SVINSLEN=11;SVINSSEQ=TTGGCAGGTTC\tTR:VR\t22:3\t12:0";
  621. let variant: VcfVariant = row.parse()?;
  622. println!("{variant:#?}");
  623. let u = variant.bnd_desc();
  624. println!("{u:#?}");
  625. // Severus mates are not in RC
  626. let vcf = "chr7\t27304522\tseverus_BND6747_1\tN\t[chr6:32688062[N\t60\tPASS\tPRECISE;SVTYPE=BND;MATE_ID=severus_BND6747_2;STRANDS=--;MAPQ=60;CLUSTERID=severus_2\tGT:VAF:hVAF:DR:DV\t0/1:0.29:0.29,0,0:12:5";
  627. let variant: VcfVariant = vcf.parse()?;
  628. let bnd_a = variant.bnd_desc()?;
  629. let vcf = "chr6\t32688062\tseverus_BND6747_2\tN\t[chr7:27304522[N\t60\tPASS\tPRECISE;SVTYPE=BND;MATE_ID=severus_BND6747_1;STRANDS=--;MAPQ=60;CLUSTERID=severus_2 GT:VAF:hVAF:DR:DV\t0/1:0.29:0.29,0,0:12:5";
  630. let variant: VcfVariant = vcf.parse()?;
  631. let bnd_b = variant.bnd_desc()?;
  632. assert_eq!(bnd_a, bnd_b.rc());
  633. println!("{bnd_a}\n{bnd_b}");
  634. // Savana here each mate are in RC
  635. let vcf = "chr10\t102039096\tID_35957_2\tG\t]chr10:101973386]G\t.\tPASS\tSVTYPE=BND;MATEID=ID_35957_1;TUMOUR_READ_SUPPORT=7;TUMOUR_ALN_SUPPORT=7;NORMAL_READ_SUPPORT=0;NORMAL_ALN_SUPPORT=0;SVLEN=65710;BP_NOTATION=+-;SOURCE=SUPPLEMENTARY;CLUSTERED_READS_TUMOUR=7;CLUSTERED_READS_NORMAL=0;ORIGIN_STARTS_STD_DEV=0.35;ORIGIN_MAPQ_MEAN=60;ORIGIN_EVENT_SIZE_STD_DEV=7.248;ORIGIN_EVENT_SIZE_MEDIAN=65710;ORIGIN_EVENT_SIZE_MEAN=65705.4;END_STARTS_STD_DEV=7.007;END_MAPQ_MEAN=60;END_EVENT_SIZE_STD_DEV=7.248;END_EVENT_SIZE_MEDIAN=65710;END_EVENT_SIZE_MEAN=65705.4;TUMOUR_DP_BEFORE=38,29;TUMOUR_DP_AT=44,21;TUMOUR_DP_AFTER=44,21;NORMAL_DP_BEFORE=13,15;NORMAL_DP_AT=13,15;NORMAL_DP_AFTER=13,15;TUMOUR_AF=0.159,0.333;NORMAL_AF=0,0;TUMOUR_TOTAL_HP_AT=20,16,8;NORMAL_TOTAL_HP_AT=6,7,0;TUMOUR_ALT_HP=0,1,6;TUMOUR_PS=101917152;NORMAL_ALT_HP=0,0,0;CLASS=PREDICTED_SOMATIC\tGT\t0/1";
  636. let variant: VcfVariant = vcf.parse()?;
  637. let bnd_a = variant.bnd_desc()?;
  638. let vcf = "chr10\t101973386\tID_35957_1\tA\tA[chr10:102039096[\t.\tPASS\tSVTYPE=BND;MATEID=ID_35957_2;TUMOUR_READ_SUPPORT=7;TUMOUR_ALN_SUPPORT=7;NORMAL_READ_SUPPORT=0;NORMAL_ALN_SUPPORT=0;SVLEN=65710;BP_NOTATION=+-;SOURCE=SUPPLEMENTARY;CLUSTERED_READS_TUMOUR=7;CLUSTERED_READS_NORMAL=0;ORIGIN_STARTS_STD_DEV=0.35;ORIGIN_MAPQ_MEAN=60;ORIGIN_EVENT_SIZE_STD_DEV=7.248;ORIGIN_EVENT_SIZE_MEDIAN=65710;ORIGIN_EVENT_SIZE_MEAN=65705.4;END_STARTS_STD_DEV=7.007;END_MAPQ_MEAN=60;END_EVENT_SIZE_STD_DEV=7.248;END_EVENT_SIZE_MEDIAN=65710;END_EVENT_SIZE_MEAN=65705.4;TUMOUR_DP_BEFORE=29,38;TUMOUR_DP_AT=21,44;TUMOUR_DP_AFTER=21,44;NORMAL_DP_BEFORE=15,13;NORMAL_DP_AT=15,13;NORMAL_DP_AFTER=15,13;TUMOUR_AF=0.333,0.159;NORMAL_AF=0,0;TUMOUR_TOTAL_HP_AT=17,0,4;NORMAL_TOTAL_HP_AT=5,7,3;TUMOUR_ALT_HP=0,6,1;TUMOUR_PS=101917152;NORMAL_ALT_HP=0,0,0;CLASS=PREDICTED_SOMATIC\tGT\t0/1";
  639. let variant: VcfVariant = vcf.parse()?;
  640. let bnd_b = variant.bnd_desc()?;
  641. assert_eq!(bnd_a, bnd_b);
  642. println!("{bnd_a}\n{bnd_b}");
  643. // Deletions
  644. // Severus
  645. let vcf = "chr7\t143674704\tseverus_DEL7318\tN\t<DEL>\t60\tPASS≥\tPRECISE;SVTYPE=DEL;SVLEN=3642;END=143678346;STRANDS=+-;MAPQ=60\tGT:GQ:VAF:hVAF:DR:DV\t0/1:114:0.39:0.39,0,0:14:9";
  646. let variant: VcfVariant = vcf.parse()?;
  647. println!("{:?}", variant.infos);
  648. println!("{:?}", variant.formats);
  649. let del = variant.deletion_desc().unwrap();
  650. println!("{:?}", del);
  651. println!("{:?} {:?}", del.len(), variant.formats.n_alt_depth());
  652. assert_eq!(
  653. "chr7:143674704_143678346_del",
  654. variant.deletion_desc().unwrap().to_string()
  655. );
  656. println!("--\n");
  657. let vcf="chr7\t144003249\tr_106\tC\t<DEL>\t.\tPASS\tEND=144142733;SVTYPE=DEL;SVLEN=-139484;SVINSLEN=4;SVINSSEQ=GCCA\tTR:VR\t12:10\t51:0";
  658. let variant: VcfVariant = vcf.parse()?;
  659. println!("{:?}", variant.infos);
  660. println!("{:?}", variant.formats);
  661. let del = variant.deletion_desc().unwrap();
  662. println!("{:?}", del);
  663. println!("{:?} {:?}", del.len(), variant.formats.n_alt_depth());
  664. let path = "/data/ref/hs1/chm13v2.0_RefSeq_Liftoff_v5.1_Genes.bed";
  665. let r = read_bed(path)?;
  666. let deleted_genes = bedrow_overlaps_par(
  667. &r,
  668. &[&GenomeRange {
  669. contig: variant.position.contig,
  670. range: del.start..del.end,
  671. }],
  672. )
  673. .into_iter()
  674. .filter_map(|e| e.name)
  675. .collect::<Vec<String>>()
  676. .join(", ");
  677. println!("{deleted_genes}");
  678. Ok(())
  679. }
  680. #[test]
  681. fn parse_del_vep() -> anyhow::Result<()> {
  682. init();
  683. let config = Config::default();
  684. let vcf = "chr14\t48907506\t.\tCCAG\tC\t25.40\tPASS\t.\tGT:GQ:DP:AD:VAF:MID:PL\t0/1:25:58:34,24:0.41379:small_model:25,0,33";
  685. let variant: VcfVariant = vcf.parse()?;
  686. let annotations = Annotations::default();
  687. let ext_annot = ExternalAnnotation::init("test", &config)?;
  688. ext_annot.annotate_vep(std::slice::from_ref(&variant), &annotations)?;
  689. {
  690. let annots = annotations.store
  691. .get(&variant.hash) // adapt if your API differs
  692. .expect("variant should have annotations");
  693. let vep = annots
  694. .iter()
  695. .find_map(|a| match a {
  696. Annotation::VEP(v) => Some(v),
  697. _ => None,
  698. })
  699. .expect("variant should have VEP annotation");
  700. let samd4a_nm = vep
  701. .iter()
  702. .find(|v| v.feature.as_deref() == Some("NM_001161576.2"))
  703. .expect("NM_001161576.2 transcript should be annotated");
  704. assert_eq!(samd4a_nm.gene.as_deref(), Some("SAMD4A"));
  705. assert_eq!(samd4a_nm.feature_type.as_deref(), Some("Transcript"));
  706. assert!(samd4a_nm
  707. .consequence
  708. .as_ref()
  709. .is_some_and(|c| c.contains(&annotation::vep::VepConsequence::InframeDeletion)));
  710. assert_eq!(samd4a_nm.cds_position.as_deref(), Some("468-470"));
  711. assert_eq!(samd4a_nm.protein_position.as_deref(), Some("156-157"));
  712. assert_eq!(samd4a_nm.amino_acids.as_deref(), Some("TS/T"));
  713. assert_eq!(samd4a_nm.codons.as_deref(), Some("acCAGc/acc"));
  714. assert_eq!(samd4a_nm.extra.impact, Some(annotation::vep::VepImpact::MODERATE));
  715. assert_eq!(samd4a_nm.extra.symbol.as_deref(), Some("SAMD4A"));
  716. assert_eq!(
  717. samd4a_nm.extra.hgvs_c.as_deref(),
  718. Some("NM_001161576.2:c.472_474del")
  719. );
  720. assert_eq!(
  721. samd4a_nm.extra.hgvs_p.as_deref(),
  722. Some("NP_001155048.2:p.Ser158del")
  723. );
  724. info!("VEP ok");
  725. }
  726. annotate_colorsdb(std::slice::from_ref(&variant), &annotations, &config.colors_db_smallvar)?;
  727. let annots = annotations.store
  728. .get(&variant.hash) // adapt if your API differs
  729. .expect("variant should have annotations");
  730. let colors = annots
  731. .iter()
  732. .find_map(|a| match a {
  733. Annotation::CoLoRSdb(v) => Some(v),
  734. _ => None,
  735. })
  736. .expect("variant should have CoLoRSdb annotation");
  737. assert_eq!(colors.ac, 2634);
  738. Ok(())
  739. }
  740. #[test]
  741. // fn variant_load_deepvariant() -> anyhow::Result<()> {
  742. // init();
  743. // let id = "ADJAGBA";
  744. // let time = "diag";
  745. // let dv = DeepVariant::initialize(id, time, Config::default())?;
  746. // let annotations = Annotations::default();
  747. // let variant_collection = dv.variants(&annotations)?;
  748. // println!(
  749. // "Deepvariant for {id} {time}: variants {} {}",
  750. // variant_collection.variants.len(),
  751. // variant_collection.vcf.n_variants
  752. // );
  753. // Ok(())
  754. // }
  755. // #[test]
  756. // fn variant_load_clairs() -> anyhow::Result<()> {
  757. // init();
  758. // let id = "ELKLIFI";
  759. // let clairs = NanomonSV::initialize(id, Config::default())?;
  760. // let u = clairs.should_run();
  761. // info!("should_run {u:?}");
  762. // // let annotations = Annotations::default();
  763. // // let variant_collection = clairs.variants(&annotations)?;
  764. // // println!("ClairS for {id}: variants {} {}", variant_collection.variants.len(), variant_collection.vcf.n_variants);
  765. // Ok(())
  766. // }
  767. //
  768. // #[test]
  769. // fn variant_load_nanomonsv() -> anyhow::Result<()> {
  770. // init();
  771. // let id = "ADJAGBA";
  772. // let nanomonsv = NanomonSV::initialize(id, Config::default())?;
  773. // let annotations = Annotations::default();
  774. // let variant_collection = nanomonsv.variants(&annotations)?;
  775. // println!(
  776. // "NanomonSV for {id}: variants {} {}",
  777. // variant_collection.variants.len(),
  778. // variant_collection.vcf.n_variants
  779. // );
  780. // println!("{:?}", variant_collection.variants.first());
  781. // Ok(())
  782. // }
  783. // #[test]
  784. // fn variant_load_clairs_germline() -> anyhow::Result<()> {
  785. // init();
  786. // let id = "ADJAGBA";
  787. // let clairs = ClairS::initialize(id, Config::default())?;
  788. // let annotations = Annotations::default();
  789. // let germline_variant_collection = clairs.germline(&annotations)?;
  790. // println!("ClairS for {id}: variants {} {}", germline_variant_collection.variants.len(), germline_variant_collection.vcf.n_variants);
  791. // Ok(())
  792. // }
  793. #[test]
  794. fn overlaps() {
  795. init();
  796. let positions = vec![
  797. &GenomePosition {
  798. contig: 1,
  799. position: 100,
  800. },
  801. &GenomePosition {
  802. contig: 1,
  803. position: 150,
  804. },
  805. &GenomePosition {
  806. contig: 1,
  807. position: 200,
  808. },
  809. &GenomePosition {
  810. contig: 2,
  811. position: 150,
  812. },
  813. ];
  814. let ranges = vec![
  815. &GenomeRange {
  816. contig: 1,
  817. range: 50..150,
  818. },
  819. &GenomeRange {
  820. contig: 2,
  821. range: 100..200,
  822. },
  823. ];
  824. let parallel_overlapping_indices = overlaps_par(&positions, &ranges);
  825. assert_eq!(parallel_overlapping_indices, vec![0, 3])
  826. }
  827. #[test]
  828. fn bed_read() -> anyhow::Result<()> {
  829. init();
  830. let path = &Config::default().mask_bed("ADJAGBA");
  831. let r = read_bed(path)?;
  832. println!("{}", r.len());
  833. Ok(())
  834. }
  835. #[test]
  836. fn test_read_dict() -> anyhow::Result<()> {
  837. init();
  838. let genome = read_dict(&Config::default().dict_file)?;
  839. let genome_length: usize = genome.into_iter().map(|(_, len)| len as usize).sum();
  840. println!("{genome_length}");
  841. Ok(())
  842. }
  843. #[test]
  844. fn bases_at() -> anyhow::Result<()> {
  845. init();
  846. let id = "ADJAGBA";
  847. let c = Config::default();
  848. let chr = "chr3";
  849. let position = 62416039; // 1-based
  850. let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "diag"))?;
  851. let p = nt_pileup(&mut bam, chr, position - 1, false)?
  852. .iter()
  853. .map(|e| String::from_utf8(vec![*e]).unwrap())
  854. .collect::<Vec<_>>();
  855. let mut counts = HashMap::new();
  856. for item in p.iter() {
  857. *counts.entry(item.as_str()).or_insert(0) += 1;
  858. }
  859. for (key, value) in &counts {
  860. println!("{}: {}", key, value);
  861. }
  862. assert_eq!(8, *counts.get("C").unwrap());
  863. assert_eq!(13, *counts.get("G").unwrap());
  864. assert_eq!(6, *counts.get("D").unwrap());
  865. let chr = "chr1";
  866. let position = 3220; // 1-based
  867. let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "mrd"))?;
  868. let p = counts_at(&mut bam, chr, position - 1)?;
  869. println!("{p:#?}");
  870. Ok(())
  871. }
  872. #[test]
  873. fn seq_at() -> anyhow::Result<()> {
  874. init();
  875. let c = Config::default();
  876. let chr = "chr1";
  877. let position = 16761;
  878. let mut fasta_reader =
  879. noodles_fasta::io::indexed_reader::Builder::default().build_from_path(c.reference)?;
  880. let r = io::fasta::sequence_at(&mut fasta_reader, chr, position, 3)?;
  881. println!(
  882. "{r} ({} {:.2})",
  883. r.len(),
  884. estimate_shannon_entropy(r.as_str())
  885. );
  886. Ok(())
  887. }
  888. #[test]
  889. fn ins_at() -> anyhow::Result<()> {
  890. init();
  891. let id = "PASSARD";
  892. let c = Config::default();
  893. let chr = "chr5";
  894. let position = 36122736; // 1-based like in vcf
  895. let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "mrd"))?;
  896. // let p = ins_pileup(&mut bam, chr, position - 1, true)?.iter().map(|e| String::from_utf8(vec![*e]).unwrap()).collect::<Vec<_>>();
  897. let counts = counts_ins_at(&mut bam, chr, position - 1)?;
  898. println!("{counts:?}");
  899. for (key, value) in &counts {
  900. println!("{}: {}", key, value);
  901. }
  902. Ok(())
  903. }
  904. // #[test]
  905. // fn del_at() -> anyhow::Result<()> {
  906. // let id = "PASSARD";
  907. // let c = Config::default();
  908. //
  909. // let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "mrd"))?;
  910. //
  911. // let pileup_start = crate::collection::bam::nt_pileup(
  912. // &mut bam,
  913. // "chr5",
  914. // 36122735,
  915. // false,
  916. // )?;
  917. //
  918. // let uu = crate::collection::bam::nt_pileup_new(
  919. // &mut bam,
  920. // "chr5",
  921. // 36122735,
  922. // false,
  923. // )?;
  924. //
  925. // let pileup_end = crate::collection::bam::nt_pileup_new(
  926. // &mut bam,
  927. // &var.position.contig(),
  928. // del_repr.end.saturating_sub(1),
  929. // false,
  930. // )?;
  931. //
  932. // println!("{pileup_start:?}");
  933. // println!("{uu:?}");
  934. // let depth = uu.len().max(pileup_end.len());
  935. //
  936. // Ok(())
  937. //
  938. // }
  939. #[test]
  940. fn vep_line() -> anyhow::Result<()> {
  941. init();
  942. let line = "chr2_1922358_-/T\tchr2:1922357-1922358\tT\tMYT1L\tNM_001303052.2\tTranscript\tintron_variant\t-\t-\t-\t-\t-\t-\tIMPACT=MODIFIER;STRAND=-1;SYMBOL=MYT1L;SOURCE=chm13v2.0_RefSeq_Liftoff_v5.1_sorted.gff3.gz;HGVSc=NM_001303052.2:c.1619-613dup";
  943. let vep_line: VepLine = line.parse()?;
  944. println!("{vep_line:#?}");
  945. let vep: VEP = VEP::try_from(&vep_line)?;
  946. println!("{vep:#?}");
  947. Ok(())
  948. }
  949. // #[test]
  950. // fn savana_cn() -> anyhow::Result<()> {
  951. // init();
  952. // let id = "CAMARA";
  953. // // let s = SavanaCopyNumber::load_id(id, Config::default())?;
  954. // let s = SavanaReadCounts::load_id(id, Config::default())?;
  955. // println!("tumoral reads: {}", s.n_tumoral_reads());
  956. // println!("normal reads: {}", s.n_normal_reads());
  957. // println!("tumoral:\n{:#?}", s.norm_chr_counts());
  958. // Ok(())
  959. // }
  960. #[test]
  961. fn load_bam() -> anyhow::Result<()> {
  962. init();
  963. let id = "ADJAGBA";
  964. let time = "diag";
  965. let bam_path = Config::default().solo_bam(id, time);
  966. WGSBam::new(Path::new(&bam_path).to_path_buf())?;
  967. Ok(())
  968. }
  969. #[test]
  970. fn tar() -> anyhow::Result<()> {
  971. init();
  972. scan_archive("/data/lto/20241030_CUNVI-DG-N20_SEBZA-DG-N21.tar")?;
  973. Ok(())
  974. }
  975. // #[test]
  976. // fn somatic_cases() -> anyhow::Result<()> {
  977. // init();
  978. // let id = "PASSARD";
  979. // let config = Config {
  980. // somatic_pipe_force: true,
  981. // ..Default::default()
  982. // };
  983. // match SomaticPipe::initialize(id, config)?.run() {
  984. // Ok(_) => (),
  985. // Err(e) => error!("{id} {e}"),
  986. // };
  987. // Ok(())
  988. // }
  989. #[test]
  990. fn load_variants() -> anyhow::Result<()> {
  991. init();
  992. let id = "ACHITE";
  993. let config = Config::default();
  994. let path = format!("{}/{id}/diag/{id}_somatic_variants.bit", config.result_dir);
  995. let variants = variant_collection::Variants::load_from_file(&path)?;
  996. println!("n variants {}", variants.data.len());
  997. let n_vep: usize = variants.data.iter().map(|v| v.vep().len()).sum();
  998. println!("VEP: {n_vep}");
  999. let translocations = variants.get_alteration_cat(AlterationCategory::TRL);
  1000. println!("{} translocations", translocations.len());
  1001. let threshold = 5;
  1002. let res = group_variants_by_bnd_desc(&translocations, 5);
  1003. let rres = group_variants_by_bnd_rc(&res, threshold);
  1004. rres.iter().for_each(|group| {
  1005. println!("{} {}", group.0.len(), group.1.len());
  1006. });
  1007. Ok(())
  1008. }
  1009. #[test]
  1010. fn load_fc() -> anyhow::Result<()> {
  1011. init();
  1012. // FlowCells::load_archive_from_scan("/data/lto", "/data/archives.json")?;
  1013. let r = FlowCells::load(
  1014. "/home/prom/mnt/store",
  1015. "/data/pandora_id_inputs.json",
  1016. "/data/archives.json.gz",
  1017. )?;
  1018. println!("{r:#?}");
  1019. Ok(())
  1020. }
  1021. #[test]
  1022. fn alt_cat() -> anyhow::Result<()> {
  1023. let id = "ADJAGBA";
  1024. let config = Config::default();
  1025. let path = format!("{}/{id}/diag/somatic_variants.json.gz", config.result_dir);
  1026. let variants = variant_collection::Variants::load_from_json(&path)?;
  1027. println!("n variants {}", variants.data.len());
  1028. variants
  1029. .data
  1030. .iter()
  1031. .filter(|v| v.alteration_category().contains(&AlterationCategory::TRL))
  1032. .for_each(|v| {
  1033. println!(
  1034. "{:?} {}",
  1035. v.vcf_variants
  1036. .iter()
  1037. .map(|v| v.bnd_desc())
  1038. .collect::<Vec<_>>(),
  1039. v.annotations
  1040. .iter()
  1041. .filter(|a| matches!(a, Annotation::Callers(..)))
  1042. .map(|a| a.to_string())
  1043. .collect::<Vec<String>>()
  1044. .join(";")
  1045. )
  1046. });
  1047. Ok(())
  1048. }
  1049. #[test]
  1050. fn variants_stats() -> anyhow::Result<()> {
  1051. init();
  1052. let config = Config::default();
  1053. let all_variants_bit = find_files(&format!(
  1054. "{}/*/diag/*_somatic_variants.bit",
  1055. config.result_dir
  1056. ))?;
  1057. for v in all_variants_bit.into_iter() {
  1058. let id = v
  1059. .file_name()
  1060. .unwrap()
  1061. .to_str()
  1062. .unwrap()
  1063. .split("_somatic")
  1064. .next()
  1065. .unwrap();
  1066. println!("{id}");
  1067. let config = Config::default();
  1068. let path = format!("{}/{id}/diag/{id}_somatic_variants.bit", config.result_dir);
  1069. match variant_collection::Variants::load_from_file(&path) {
  1070. Ok(mut variants) => {
  1071. let (mut high_depth_ranges, _) = somatic_depth_quality_ranges(id, &config)?;
  1072. high_depth_ranges.par_sort_by_key(|r| (r.contig, r.range.start));
  1073. let res = VariantsStats::new(&mut variants, id, &config, &high_depth_ranges)?
  1074. .save_to_json(&format!(
  1075. "{}/{id}/diag/{id}_somatic_variants_stats.json.gz",
  1076. config.result_dir
  1077. ));
  1078. if res.is_err() {
  1079. info!("{:#?}", res);
  1080. }
  1081. }
  1082. Err(e) => error!("{e}"),
  1083. }
  1084. }
  1085. Ok(())
  1086. }
  1087. // #[test]
  1088. // fn constit_stats() {
  1089. // init();
  1090. // let id = "ADJAGBA";
  1091. // let config = Config::default();
  1092. //
  1093. // let _ = const_stats(id.to_string(), config);
  1094. // }
  1095. // #[test]
  1096. // fn test_bnd() -> anyhow::Result<()> {
  1097. // init();
  1098. // let id = "COIFFET";
  1099. // let config = Config::default();
  1100. //
  1101. // let annotations = Annotations::default();
  1102. // let s = Savana::initialize(id, config)?.variants(&annotations)?;
  1103. // s.variants.iter().for_each(|e| {
  1104. // if let Ok(bnd) = e.bnd_desc() {
  1105. // println!("{}\t{}\t{}", e.position, e.reference, e.alternative);
  1106. // println!("{:#?}", bnd);
  1107. // }
  1108. // });
  1109. // Ok(())
  1110. // }
  1111. // #[test]
  1112. // fn parse_savana_seg() {
  1113. // init();
  1114. // let r = SavanaCN::parse_file("ADJAGBA", &config::Config::default())
  1115. // .unwrap()
  1116. // .segments;
  1117. // println!("{} lines", r.len());
  1118. // println!("{:#?}", r.first().unwrap());
  1119. // }
  1120. #[test]
  1121. fn whole_scan() -> anyhow::Result<()> {
  1122. init();
  1123. let id = "CHENU";
  1124. let mut config = Config::default();
  1125. let u = config.solo_bam(id, "mrd");
  1126. println!("{u}");
  1127. config.somatic_scan_force = true;
  1128. somatic_scan(id, &config)?;
  1129. Ok(())
  1130. }
  1131. #[test]
  1132. fn parse_gff() -> anyhow::Result<()> {
  1133. init();
  1134. let id = "ADJAGBA";
  1135. let config = Config::default();
  1136. let path = format!("{}/{id}/diag/somatic_variants.bit", config.result_dir);
  1137. let exon_ranges = features_ranges("exon", &config)?;
  1138. let exon_ranges = merge_overlapping_genome_ranges(&exon_ranges);
  1139. let variants = variant_collection::Variants::load_from_file(&path)?;
  1140. let full = variants_stats::somatic_rates(&variants.data, &exon_ranges, &config);
  1141. info!("{full:#?}");
  1142. // let restrained: Vec<variant_collection::Variant> = variants.data.iter().filter(|v| v.vcf_variants.len() >= 2)
  1143. // .cloned().collect();
  1144. // let min_2 = variants_stats::somatic_rates(&restrained, &exon_ranges, &config);
  1145. // info!("{min_2:#?}");
  1146. //
  1147. // let restrained: Vec<variant_collection::Variant> = restrained.iter().filter(|v| v.vcf_variants.len() >= 3)
  1148. // .cloned().collect();
  1149. // let min_3 = variants_stats::somatic_rates(&restrained, &exon_ranges, &config);
  1150. // info!("{min_3:#?}");
  1151. // let mut high_depth_ranges = variants_stats::high_depth_somatic(id, &config)?;
  1152. // high_depth_ranges.par_sort_by_key(|r| ( r.contig, r.range.start ));
  1153. //
  1154. // let exon_ranges_ref: Vec<&GenomeRange> = exon_ranges.iter().collect();
  1155. // let exons_high_depth = range_intersection_par(&high_depth_ranges.iter().collect::<Vec<&GenomeRange>>(), &exon_ranges_ref);
  1156. //
  1157. // let full = variants_stats::somatic_rates(&variants.data, &exons_high_depth, &config);
  1158. // info!("{full:#?}");
  1159. //
  1160. // info!("n variants loaded: {}", variants.data.len());
  1161. //
  1162. // let r = features_ranges("exon", &config::Config::default())?;
  1163. // info!("n exon: {}", r.len());
  1164. //
  1165. // let merged = merge_overlapping_genome_ranges(&r);
  1166. // info!("n merged exon: {}", merged.len());
  1167. //
  1168. // let ol = par_overlaps(&variants.data, &r);
  1169. // info!("variants in exon {}", ol.len());
  1170. //
  1171. // let n_coding = ol.iter().filter_map(|i| variants.data[*i].best_vep().ok() ).filter_map(|bv| bv.impact()).filter(|impact| *impact <= VepImpact::MODERATE).count();
  1172. // info!("coding variants {n_coding}");
  1173. //
  1174. // let n_bases_m = merged.par_iter().map(|gr| gr.length()).sum::<u32>();
  1175. // info!("{n_bases_m}nt");
  1176. //
  1177. // let mega_base_m = n_bases_m as f64 / 10.0e6;
  1178. //
  1179. // let wgs_len = read_dict(&config.dict_file)?.iter().map(|(_, l)| *l).sum::<u32>();
  1180. // info!("wgs len {wgs_len}");
  1181. // let rate_wgs = variants.data.len() as f64 / (wgs_len as f64 / 10.0e6);
  1182. // info!("somatic mutation rate {rate_wgs:.2}/mb");
  1183. //
  1184. // let n_exons_mb = ol.len() as f64 / mega_base_m;
  1185. // info!("somatic mutation rate in the coding region {n_exons_mb:.2}/mb");
  1186. //
  1187. // let n_exons_mb = n_coding as f64 / mega_base_m;
  1188. // info!("somatic non synonymous mutation rate in the coding region {n_exons_mb:.2}/mb");
  1189. Ok(())
  1190. }
  1191. fn gr(contig: u8, start: u32, end: u32) -> GenomeRange {
  1192. GenomeRange {
  1193. contig,
  1194. range: start..end,
  1195. }
  1196. }
  1197. #[test]
  1198. fn test_both_empty() {
  1199. let a: Vec<&GenomeRange> = vec![];
  1200. let b: Vec<&GenomeRange> = vec![];
  1201. let result = range_intersection_par(&a, &b);
  1202. assert!(result.is_empty());
  1203. }
  1204. #[test]
  1205. fn test_one_empty() {
  1206. let a = [gr(1, 0, 100)];
  1207. let b: Vec<&GenomeRange> = vec![];
  1208. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1209. let mut result = range_intersection_par(&a_refs, &b);
  1210. sort_ranges(&mut result);
  1211. assert!(result.is_empty());
  1212. }
  1213. #[test]
  1214. fn test_single_range_no_overlap() {
  1215. let a = [gr(1, 0, 100)];
  1216. let b = [gr(1, 100, 200)];
  1217. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1218. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1219. let mut result = range_intersection_par(&a_refs, &b_refs);
  1220. sort_ranges(&mut result);
  1221. assert!(result.is_empty());
  1222. }
  1223. #[test]
  1224. fn test_single_range_full_overlap() {
  1225. let a = [gr(1, 0, 100)];
  1226. let b = [gr(1, 0, 100)];
  1227. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1228. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1229. let mut result = range_intersection_par(&a_refs, &b_refs);
  1230. let mut expected = [gr(1, 0, 100)];
  1231. sort_ranges(&mut result);
  1232. sort_ranges(&mut expected);
  1233. assert_eq!(result, expected);
  1234. }
  1235. #[test]
  1236. fn test_different_contigs() {
  1237. let a = [gr(1, 0, 100)];
  1238. let b = [gr(2, 0, 100)];
  1239. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1240. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1241. let mut result = range_intersection_par(&a_refs, &b_refs);
  1242. sort_ranges(&mut result);
  1243. assert!(result.is_empty());
  1244. }
  1245. #[test]
  1246. fn test_touching_ranges() {
  1247. let a = [gr(1, 0, 100)];
  1248. let b = [gr(1, 100, 200)];
  1249. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1250. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1251. let mut result = range_intersection_par(&a_refs, &b_refs);
  1252. sort_ranges(&mut result);
  1253. assert!(result.is_empty());
  1254. }
  1255. #[test]
  1256. fn test_complete_subrange() {
  1257. let a = [gr(1, 0, 200)];
  1258. let b = [gr(1, 50, 150)];
  1259. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1260. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1261. let mut result = range_intersection_par(&a_refs, &b_refs);
  1262. let mut expected = [gr(1, 50, 150)];
  1263. sort_ranges(&mut result);
  1264. sort_ranges(&mut expected);
  1265. assert_eq!(result, expected);
  1266. }
  1267. #[test]
  1268. fn test_multiple_overlaps_same_contig() {
  1269. let a = [gr(1, 0, 50), gr(1, 75, 125)];
  1270. let b = [gr(1, 25, 100), gr(1, 150, 200)];
  1271. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1272. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1273. let mut result = range_intersection_par(&a_refs, &b_refs);
  1274. let mut expected = [gr(1, 25, 50), gr(1, 75, 100)];
  1275. sort_ranges(&mut result);
  1276. sort_ranges(&mut expected);
  1277. assert_eq!(result, expected);
  1278. }
  1279. #[test]
  1280. fn test_multiple_contigs() {
  1281. let a = [gr(1, 0, 100), gr(2, 50, 150), gr(3, 200, 300)];
  1282. let b = [gr(1, 50, 150), gr(2, 0, 100), gr(4, 0, 100)];
  1283. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1284. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1285. let mut result = range_intersection_par(&a_refs, &b_refs);
  1286. let mut expected = [gr(1, 50, 100), gr(2, 50, 100)];
  1287. sort_ranges(&mut result);
  1288. sort_ranges(&mut expected);
  1289. assert_eq!(result, expected);
  1290. }
  1291. #[test]
  1292. fn test_adjacent_ranges() {
  1293. let a = [gr(1, 0, 50), gr(1, 50, 100)];
  1294. let b = [gr(1, 25, 75)];
  1295. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1296. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1297. let mut result = range_intersection_par(&a_refs, &b_refs);
  1298. let mut expected = [gr(1, 25, 50), gr(1, 50, 75)];
  1299. sort_ranges(&mut result);
  1300. sort_ranges(&mut expected);
  1301. assert_eq!(result, expected);
  1302. }
  1303. #[test]
  1304. fn test_minimal_overlap() {
  1305. let a = [gr(1, 0, 100)];
  1306. let b = [gr(1, 99, 200)];
  1307. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1308. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1309. let mut result = range_intersection_par(&a_refs, &b_refs);
  1310. let mut expected = [gr(1, 99, 100)];
  1311. sort_ranges(&mut result);
  1312. sort_ranges(&mut expected);
  1313. assert_eq!(result, expected);
  1314. }
  1315. /// helper to build a forward‑strand BND (same contig) where
  1316. /// A = pos, B = pos + 5
  1317. fn fwd(contig: &str, pos: u32) -> BNDDesc {
  1318. BNDDesc {
  1319. a_contig: contig.into(),
  1320. a_position: pos,
  1321. a_sens: true,
  1322. b_contig: contig.into(),
  1323. b_position: pos + 5,
  1324. b_sens: true,
  1325. added_nt: String::new(),
  1326. }
  1327. }
  1328. /// Build a six‑node *forward* chain relying **only** on `auto_connect()`
  1329. /// (no manual edges) and assert the Hamiltonian path spans all nodes.
  1330. #[test]
  1331. fn hamiltonian_chain_auto() {
  1332. // positions 10,15 20,25 … 60,65 satisfy B(u) ≤ A(v)
  1333. let bnds: Vec<BNDDesc> = (1..=6).map(|i| fwd("chr1", i * 10)).collect();
  1334. let g: BNDGraph<()> = bnds.to_bnd_graph(); // trait uses auto_connect()
  1335. // ensure auto_connect produced 5 edges in a line
  1336. assert_eq!(g.inner().edge_count(), 5);
  1337. let path = g.hamiltonian_path().expect("chain should be Hamiltonian");
  1338. assert_eq!(path.len(), 6);
  1339. }
  1340. /// Two disconnected "V" shapes -> auto_connect creates 2 edges in each
  1341. /// component, no Hamiltonian path, components sorted by size.
  1342. #[test]
  1343. fn components_after_auto_connect() {
  1344. // comp1: a->b<-c on reverse strand of chrX
  1345. let a = BNDDesc {
  1346. a_contig: "chrX".into(),
  1347. a_position: 300,
  1348. a_sens: false,
  1349. b_contig: "chrX".into(),
  1350. b_position: 250,
  1351. b_sens: false,
  1352. added_nt: String::new(),
  1353. };
  1354. let b = BNDDesc {
  1355. a_contig: "chrX".into(),
  1356. a_position: 200,
  1357. a_sens: false,
  1358. b_contig: "chrX".into(),
  1359. b_position: 150,
  1360. b_sens: false,
  1361. added_nt: String::new(),
  1362. };
  1363. let c = BNDDesc {
  1364. a_contig: "chrX".into(),
  1365. a_position: 100,
  1366. a_sens: false,
  1367. b_contig: "chrX".into(),
  1368. b_position: 50,
  1369. b_sens: false,
  1370. added_nt: String::new(),
  1371. };
  1372. // comp2: three‑node forward chain on chrY
  1373. let d = fwd("chrY", 10);
  1374. let e = fwd("chrY", 20);
  1375. let f = fwd("chrY", 30);
  1376. let g: BNDGraph<()> = vec![a, b, c, d, e, f].to_bnd_graph();
  1377. assert!(g.hamiltonian_path().is_none());
  1378. let comps = g.components_by_size();
  1379. comps.iter().for_each(|a| println!("{}", g.fmt_path(a)));
  1380. assert_eq!(comps.len(), 2);
  1381. assert_eq!(comps[0].len(), 3);
  1382. assert_eq!(comps[1].len(), 3);
  1383. }
  1384. #[test]
  1385. fn bnd_connections() {
  1386. // comp1: a->b<-c on reverse strand of chrX
  1387. let a = BNDDesc {
  1388. a_contig: "chrX".into(),
  1389. a_position: 300,
  1390. a_sens: true,
  1391. b_contig: "chr14".into(),
  1392. b_position: 250,
  1393. b_sens: false,
  1394. added_nt: String::new(),
  1395. };
  1396. let b = BNDDesc {
  1397. a_contig: "chr14".into(),
  1398. a_position: 200,
  1399. a_sens: false,
  1400. b_contig: "chrX".into(),
  1401. b_position: 301,
  1402. b_sens: true,
  1403. added_nt: String::new(),
  1404. };
  1405. let c = BNDDesc {
  1406. a_contig: "chrX".into(),
  1407. a_position: 302,
  1408. a_sens: true,
  1409. b_contig: "chrZ".into(),
  1410. b_position: 50,
  1411. b_sens: false,
  1412. added_nt: String::new(),
  1413. };
  1414. // comp2: three‑node forward chain on chrY
  1415. let d = fwd("chrY", 10);
  1416. let e = fwd("chrY", 20);
  1417. let f = fwd("chrY", 30);
  1418. let g: BNDGraph<()> = vec![a, b, c, d, e, f].to_bnd_graph();
  1419. assert!(g.hamiltonian_path().is_none());
  1420. let comps = g.components_by_size();
  1421. comps.iter().for_each(|a| println!("{}", g.fmt_path(a)));
  1422. assert_eq!(comps.len(), 2);
  1423. assert_eq!(comps[0].len(), 3);
  1424. assert_eq!(comps[1].len(), 3);
  1425. }
  1426. #[test]
  1427. fn snv_hg38() -> anyhow::Result<()> {
  1428. init();
  1429. let id = "CHALO";
  1430. let config = Config::default();
  1431. let path = format!("{}/{id}/diag/{id}_somatic_variants.bit", config.result_dir);
  1432. let mut variants = variant_collection::Variants::load_from_file(&path)?;
  1433. info!("All: {}", variants.len());
  1434. variants.retain(|v| {
  1435. *v.alteration_category()
  1436. .first()
  1437. .unwrap_or(&AlterationCategory::Other)
  1438. == AlterationCategory::SNV
  1439. });
  1440. info!("SNV: {}", variants.len());
  1441. variants.in_place_merge();
  1442. info!("SNV: {}", variants.len());
  1443. // variants.write_vcf("/home/t_steimle/CHALO_hs1.vcf.gz", "/home/t_steimle/ref/hs1/chm13v2.0.dict", true)
  1444. variants.save_into_lifted(
  1445. "/home/t_steimle/CHALO_SNV_hg38.vcf",
  1446. "./unmapped",
  1447. "/home/t_steimle/ref/hs1/chm13v2-grch38.chain",
  1448. "/home/t_steimle/ref/hg38/hg38.fa",
  1449. 100,
  1450. )
  1451. }
  1452. }