lib.rs 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368
  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 commands;
  134. pub mod config;
  135. pub mod modkit;
  136. pub mod callers;
  137. pub mod runners;
  138. pub mod collection;
  139. pub mod functions;
  140. pub mod helpers;
  141. pub mod variant;
  142. pub mod io;
  143. pub mod pipes;
  144. pub mod positions;
  145. pub mod annotation;
  146. pub mod cases;
  147. pub mod scan;
  148. pub mod math;
  149. #[macro_use]
  150. extern crate lazy_static;
  151. // Define DOCKER_ID lock for handling Docker kill when ctrlc is pressed
  152. lazy_static! {
  153. static ref DOCKER_ID: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
  154. }
  155. #[cfg(test)]
  156. mod tests {
  157. use std::{collections::HashMap, fs, path::Path};
  158. use annotation::{vep::{VepLine, VEP}, Annotations};
  159. use callers::{nanomonsv::nanomonsv_create_pon, savana::{Savana, SavanaReadCounts}, severus::{Severus, SeverusSolo}};
  160. use collection::{bam::{counts_at, counts_ins_at, nt_pileup, WGSBam, WGSBamStats}, Initialize, InitializeSolo, Version};
  161. use commands::{longphase::{LongphaseConfig, LongphaseHap, LongphaseModcallSolo, LongphasePhase}, modkit::{bed_methyl, ModkitConfig}};
  162. use functions::assembler::{Assembler, AssemblerConfig};
  163. use helpers::estimate_shannon_entropy;
  164. use io::bed::read_bed;
  165. use itertools::Itertools;
  166. use log::{error, info, warn};
  167. use pipes::somatic::SomaticPipe;
  168. use positions::{overlaps_par, GenomePosition, GenomeRange};
  169. use rayon::prelude::*;
  170. use runners::Run;
  171. use variant::{variant::{Variants, VcfVariant}, variant_collection};
  172. use self::{collection::pod5::{FlowCellCase, Pod5Collection}, commands::dorado, config::Config};
  173. use super::*;
  174. use crate::{annotation::Annotation, callers::{clairs::ClairS, deep_variant::DeepVariant, nanomonsv::{NanomonSV, NanomonSVSolo}, savana::SavanaCN}, collection::{bam::{self, nt_pileup_new}, flowcells::{scan_archive, FlowCells}, run_tasks, vcf::VcfCollection, Collections, CollectionsConfig, ShouldRun}, commands::dorado::Dorado, helpers::find_files, io::{bed::bedrow_overlaps_par, dict::read_dict, gff::features_ranges}, pipes::somatic::const_stats, positions::{merge_overlapping_genome_ranges, range_intersection_par, sort_ranges}, scan::scan::somatic_scan, variant::{variant::{AlterationCategory, BNDDesc, BNDGraph, GroupByThreshold, ToBNDGraph}, variant_collection::{group_variants_by_bnd_desc, group_variants_by_bnd_rc, Variant}, variants_stats::{self, somatic_depth_quality_ranges, VariantsStats}}};
  175. // export RUST_LOG="debug"
  176. fn init() {
  177. let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
  178. .is_test(true)
  179. .try_init();
  180. }
  181. #[test]
  182. fn it_works() {
  183. let bam_path = "/data/longreads_basic_pipe/PARACHINI/diag/PARACHINI_diag_hs1.bam";
  184. modkit::modkit(bam_path);
  185. }
  186. #[test]
  187. fn run_dorado() -> anyhow::Result<()> {
  188. let case = FlowCellCase {
  189. id: "CONSIGNY".to_string(),
  190. time_point: "mrd".to_string(), barcode: "07".to_string(), pod_dir: "/data/run_data/20240326-CL/CONSIGNY-MRD-NB07_RICCO-DIAG-NB08/20240326_1355_1E_PAU78333_bc25da25/pod5_pass/barcode07".into()
  191. };
  192. dorado::Dorado::init(case, Config::default())?.run_pipe()
  193. }
  194. #[test]
  195. fn pod5() -> anyhow::Result<()> {
  196. let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
  197. .build();
  198. let coll = Pod5Collection::new(
  199. "/data/run_data",
  200. "/data/flow_cells.tsv",
  201. "/data/longreads_basic_pipe",
  202. )?;
  203. println!("{coll:#?}");
  204. // let runs = Runs::import_dir("/home/prom/store/banana-pool/run_data", "/data/flow_cells.tsv")?;
  205. Ok(())
  206. }
  207. #[test]
  208. fn bam() -> anyhow::Result<()> {
  209. init();
  210. let bam_collection = bam::load_bam_collection("/data/longreads_basic_pipe");
  211. bam_collection
  212. .bams
  213. .iter()
  214. // .filter(|b| matches!(b.bam_type, BamType::Panel(_)))
  215. .for_each(|b| println!("{b:#?}"));
  216. let u = bam_collection.get("PARACHINI", "mrd");
  217. println!("{u:#?}");
  218. Ok(())
  219. }
  220. #[test]
  221. fn vcf() -> anyhow::Result<()> {
  222. init();
  223. let mut vcf_collection = VcfCollection::new("/data/longreads_basic_pipe");
  224. vcf_collection.sort_by_id();
  225. vcf_collection
  226. .vcfs
  227. .iter()
  228. .for_each(|v| v.println().unwrap());
  229. Ok(())
  230. }
  231. // 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
  232. // 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/
  233. #[test]
  234. fn mux() -> anyhow::Result<()> {
  235. init();
  236. let result_dir = "/data/test_suite/results".to_string();
  237. let cases = vec![
  238. FlowCellCase { id: "test_02".to_string(), time_point: "diag".to_string(), barcode: "02".to_string(), pod_dir: "/data/test_suite/pod5/muxed".into() },
  239. FlowCellCase { id: "test_03".to_string(), time_point: "diag".to_string(), barcode: "03".to_string(), pod_dir: "/data/test_suite/pod5/muxed".into() },
  240. ];
  241. cases.iter().for_each(|c| {
  242. let dir = format!("{result_dir}/{}", c.id);
  243. if Path::new(&dir).exists() {
  244. fs::remove_dir_all(dir).unwrap();
  245. }
  246. });
  247. let config = Config { result_dir, ..Default::default() };
  248. Dorado::from_mux(cases, config)
  249. }
  250. // #[test_log::test]
  251. // fn clairs() -> anyhow::Result<()> {
  252. // let config = ClairSConfig {
  253. // result_dir: "/data/test".to_string(),
  254. // ..ClairSConfig::default()
  255. // };
  256. // ClairS::new("test_a", "/data/test_data/subset.bam", "/data/test_data/subset_mrd.bam", config).run()
  257. // }
  258. #[test]
  259. fn nanomonsv() -> anyhow::Result<()> {
  260. init();
  261. let id = "HAMROUNE";
  262. NanomonSV::initialize(id, Config::default())?.run()
  263. }
  264. #[test]
  265. fn nanomddonsv_solo() -> anyhow::Result<()> {
  266. init();
  267. NanomonSVSolo::initialize("BRETON", "diag", Config::default())?.run()
  268. }
  269. // cargo test run -- --nocapture; ~/run_scripts/notify_finish.sh &
  270. #[test]
  271. fn todo_all() -> anyhow::Result<()> {
  272. init();
  273. // let config = CollectionsConfig::default();
  274. let config = CollectionsConfig { pod_dir: "/data/run_data".to_string(), ..Default::default() };
  275. info!("Runing todo with config: {:#?}", config);
  276. let mut collections = Collections::new(config)?;
  277. collections.todo()?;
  278. collections.tasks.iter().for_each(|t| println!("{t}"));
  279. println!("{}", collections.tasks.len());
  280. Ok(())
  281. }
  282. // #[test]
  283. // fn todo_agg() -> anyhow::Result<()> {
  284. // init();
  285. // let config = CollectionsConfig::default();
  286. // info!("Runing todo with config: {:#?}", config);
  287. // let collections = Collections::new(config)?;
  288. // let agg_tasks = collections.todo_variants_agg()?;
  289. // println!("{:#?}", agg_tasks);
  290. // println!("{}", agg_tasks.len());
  291. // Ok(())
  292. // }
  293. // #[test]
  294. // fn run_agg() -> anyhow::Result<()> {
  295. // init();
  296. // let config = CollectionsConfig {
  297. // id_black_list: vec!["MANCUSO".to_string(),"HAMROUNE".to_string()],
  298. // ..Default::default()
  299. // };
  300. // info!("Runing todo with config: {:#?}", config);
  301. // let mut collections = Collections::new(config)?;
  302. // collections.tasks = collections.todo_variants_agg()?;
  303. // collections.run()?;
  304. //
  305. // Ok(())
  306. // }
  307. // export RUST_LOG="debug"
  308. #[test]
  309. fn run_t() -> anyhow::Result<()> {
  310. init();
  311. // let config = CollectionsConfig::default();
  312. let config = CollectionsConfig { pod_dir: "/data/run_data".to_string(), ..Default::default() };
  313. run_tasks(config)
  314. }
  315. // #[test_log::test]
  316. // fn bcftools_pass() {
  317. // let config = BcftoolsConfig::default();
  318. // let id = "RICCO";
  319. // let time = "diag";
  320. // let caller = "DeepVariant";
  321. //
  322. // Config::default();
  323. //
  324. // // let (i, o) =
  325. // // let i = format!("/data/longreads_basic_pipe/{id}/{time}/nanomonsv/{id}_diag.nanomonsv.result.vcf");
  326. // // let o = format!("/data/longreads_basic_pipe/{id}/{time}/nanomonsv/{id}_diag_nanomonsv_PASSED.vcf.gz");
  327. // bcftools_keep_pass(&i, &o, config).unwrap();
  328. // }
  329. #[test]
  330. fn bam_ok() -> anyhow::Result<()> {
  331. init();
  332. let collections = Collections::new(
  333. CollectionsConfig::default()
  334. )?;
  335. let mut res: Vec<_> = collections.bam.by_id_completed(15.0, 10.0).iter().map(|b| {
  336. (b.id.to_string(), b.time_point.to_string(), b.path.to_str().unwrap().to_string())
  337. }).collect();
  338. res.sort_by_key(|b| b.1.clone());
  339. res.sort_by_key(|b| b.0.clone());
  340. res.iter().for_each(|(id, tp, path)| println!("{id}\t{tp}\t{path}"));
  341. Ok(())
  342. }
  343. #[test]
  344. fn todo_assembler() -> anyhow::Result<()> {
  345. init();
  346. let collections = Collections::new(
  347. CollectionsConfig::default()
  348. )?;
  349. collections.todo_assembler()?;
  350. Ok(())
  351. }
  352. #[test]
  353. fn sv_pon() -> anyhow::Result<()> {
  354. init();
  355. nanomonsv_create_pon(&Config::default(), "/data/ref/hs1/nanomonsv_pon.vcf.gz")
  356. }
  357. #[test]
  358. fn todo_mod() -> anyhow::Result<()> {
  359. init();
  360. let collections = Collections::new(
  361. CollectionsConfig::default()
  362. )?;
  363. collections.todo_mod_pileup();
  364. Ok(())
  365. }
  366. #[test]
  367. fn todo_deepv() -> anyhow::Result<()> {
  368. init();
  369. let collections = Collections::new(
  370. CollectionsConfig::default()
  371. )?;
  372. let tasks = collections.todo_deepvariants();
  373. tasks.iter().for_each(|t| info!("{t}"));
  374. info!("n tasks {}", tasks.len());
  375. Ok(())
  376. }
  377. #[test]
  378. fn todo_clairs() -> anyhow::Result<()> {
  379. init();
  380. let collections = Collections::new(
  381. CollectionsConfig::default()
  382. )?;
  383. collections.todo_clairs().iter().for_each(|t| info!("{t}"));
  384. Ok(())
  385. }
  386. #[test]
  387. fn run_assemblers() -> anyhow::Result<()> {
  388. Assembler::new("CAMEL".to_string(), "diag".to_string(), AssemblerConfig::default()).run()
  389. }
  390. // #[test]
  391. // fn run_dmr_par() -> anyhow::Result<()> {
  392. // init();
  393. // let collections = Collections::new(
  394. // CollectionsConfig::default()
  395. // )?;
  396. // let tasks = collections.todo_dmr_c_diag_mrd();
  397. // tasks.iter().for_each(|t| info!("{t}"));
  398. // let len = tasks.len();
  399. // // let pool = ThreadPoolBuilder::new().num_threads(10).build().unwrap();
  400. // // pool.install(|| {
  401. // // tasks.par_iter().enumerate().for_each(|(i, t)| {
  402. // // let config = ModkitConfig {threads: 2, ..Default::default() };
  403. // // if let collection::CollectionsTasks::DMRCDiagMrd { id, .. } = t { let _ = dmr_c_mrd_diag(id, &config); }
  404. // // println!("⚡ {i}/{len}");
  405. // // });
  406. // // });
  407. // Ok(())
  408. // }
  409. #[test]
  410. fn run_mod_par() -> anyhow::Result<()> {
  411. init();
  412. let collections = Collections::new(
  413. CollectionsConfig::default()
  414. )?;
  415. let tasks = collections.todo_mod_pileup();
  416. let len = tasks.len();
  417. tasks.par_iter().enumerate().for_each(|(i, t)| {
  418. let config = ModkitConfig {threads: 2, ..Default::default() };
  419. if let collection::CollectionsTasks::ModPileup { bam, .. } = t { let _ = bed_methyl(bam.to_owned(), &config); }
  420. println!("⚡ {i}/{len}");
  421. });
  422. Ok(())
  423. }
  424. #[test]
  425. fn run_severus() -> anyhow::Result<()> {
  426. init();
  427. Severus::initialize("CAMEL", Config::default())?.run()
  428. }
  429. #[test]
  430. fn run_severus_solo() -> anyhow::Result<()> {
  431. init();
  432. SeverusSolo::initialize("CAMEL","diag", Config::default())?.run()
  433. }
  434. #[test]
  435. fn run_savana() -> anyhow::Result<()> {
  436. init();
  437. let collections = Collections::new(
  438. CollectionsConfig::default()
  439. )?;
  440. for bam in collections.bam.by_id_completed(15.0, 10.0).iter() {
  441. let id = &bam.id;
  442. match ClairS::initialize(id, Config::default())?.run() {
  443. Ok(_) => match Savana::initialize(id, Config::default())?.run() {
  444. Ok(_) => (),
  445. Err(e) => error!("{e}"),
  446. },
  447. Err(e) => error!("{e}"),
  448. }
  449. ;
  450. }
  451. Ok(())
  452. }
  453. #[test]
  454. fn check_versions() -> anyhow::Result<()> {
  455. init();
  456. let config = Config::default();
  457. let v = Savana::version(&config)?;
  458. info!("Savanna version {v}");
  459. let v = Severus::version(&config)?;
  460. info!("Severus version {v}");
  461. Ok(())
  462. }
  463. #[test]
  464. fn run_multi_deepvariant() -> anyhow::Result<()> {
  465. init();
  466. let mut collections = Collections::new(
  467. CollectionsConfig::default()
  468. )?;
  469. collections.run_deepvariant()
  470. }
  471. #[test]
  472. fn run_deepvariant() -> anyhow::Result<()> {
  473. init();
  474. DeepVariant::initialize("HAMROUNE", "diag", Config::default())?.run()
  475. }
  476. #[test]
  477. fn run_clairs() -> anyhow::Result<()> {
  478. init();
  479. ClairS::initialize("ADJAGBA", Config::default())?.run()
  480. }
  481. #[test]
  482. fn run_longphase() -> anyhow::Result<()> {
  483. init();
  484. let id = "BECERRA";
  485. let diag_bam = format!("/data/longreads_basic_pipe/{id}/diag/{id}_diag_hs1.bam");
  486. let vcf = format!("/data/longreads_basic_pipe/{id}/diag/ClairS/clair3_normal_tumoral_germline_output.vcf.gz");
  487. let mrd_bam = format!("/data/longreads_basic_pipe/{id}/mrd/{id}_mrd_hs1.bam");
  488. LongphaseHap::new(id, &diag_bam, &vcf, LongphaseConfig::default()).run()?;
  489. LongphaseHap::new(id, &mrd_bam, &vcf, LongphaseConfig::default()).run()
  490. }
  491. #[test]
  492. fn run_longphase_modcall() -> anyhow::Result<()> {
  493. init();
  494. let id = "ADJAGBA";
  495. let time = "diag";
  496. LongphaseModcallSolo::initialize(id, time, Config::default())?.run()
  497. }
  498. #[test]
  499. fn run_longphase_phase() -> anyhow::Result<()> {
  500. init();
  501. let id = "ADJAGBA";
  502. LongphasePhase::initialize(id, Config::default())?.run()
  503. }
  504. #[test]
  505. fn variant_parse() -> anyhow::Result<()> {
  506. let row = "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";
  507. let variant: VcfVariant = row.parse()?;
  508. let var_string = variant.into_vcf_row();
  509. assert_eq!(row, &var_string);
  510. let row = "chr1\t1366\t.\tC\tCCCT\t8.2\tPASS\t.";
  511. let variant: VcfVariant = row.parse()?;
  512. let var_string = variant.into_vcf_row();
  513. assert_eq!(row, &var_string);
  514. 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";
  515. let variant: VcfVariant = row.parse()?;
  516. let var_string = variant.into_vcf_row();
  517. assert_eq!(row, &var_string);
  518. let row = "chr1\t52232\t.\tC\tCT\t18\t.\t.\tGT:GQ:DP:AD:AF\t1/.:1:24:3,5:0.208333";
  519. let variant: VcfVariant = row.parse()?;
  520. let var_string = variant.into_vcf_row();
  521. assert_eq!(row, &var_string);
  522. let row = "chr1\t52232\t.\tC\tCT\t18\t.\t.\tGT:GQ:DP:AD:AF\t1/1:1:24:3,5:0.208333";
  523. let variant_b: VcfVariant = row.parse()?;
  524. assert_eq!(variant, variant_b);
  525. 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";
  526. let variant: VcfVariant = row.parse()?;
  527. let var_string = variant.into_vcf_row();
  528. assert_eq!(row, &var_string);
  529. 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";
  530. let variant: VcfVariant = row.parse()?;
  531. println!("{variant:#?}");
  532. let u = variant.bnd_desc();
  533. println!("{u:#?}");
  534. // Severus mates are not in RC
  535. 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";
  536. let variant: VcfVariant = vcf.parse()?;
  537. let bnd_a = variant.bnd_desc()?;
  538. 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";
  539. let variant: VcfVariant = vcf.parse()?;
  540. let bnd_b = variant.bnd_desc()?;
  541. assert_eq!(bnd_a, bnd_b.rc());
  542. println!("{bnd_a}\n{bnd_b}");
  543. // Savana here each mate are in RC
  544. 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";
  545. let variant: VcfVariant = vcf.parse()?;
  546. let bnd_a = variant.bnd_desc()?;
  547. 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";
  548. let variant: VcfVariant = vcf.parse()?;
  549. let bnd_b = variant.bnd_desc()?;
  550. assert_eq!(bnd_a, bnd_b);
  551. println!("{bnd_a}\n{bnd_b}");
  552. // Deletions
  553. // Severus
  554. 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";
  555. let variant: VcfVariant = vcf.parse()?;
  556. println!("{:?}", variant.infos);
  557. println!("{:?}", variant.formats);
  558. let del = variant.deletion_desc().unwrap();
  559. println!("{:?}", del);
  560. println!("{:?} {:?}", del.len(), variant.formats.n_alt_depth());
  561. assert_eq!("chr7:143674704_143678346_del", variant.deletion_desc().unwrap().to_string());
  562. println!("--\n");
  563. 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";
  564. let variant: VcfVariant = vcf.parse()?;
  565. println!("{:?}", variant.infos);
  566. println!("{:?}", variant.formats);
  567. let del = variant.deletion_desc().unwrap();
  568. println!("{:?}", del);
  569. println!("{:?} {:?}", del.len(), variant.formats.n_alt_depth());
  570. let path = "/data/ref/hs1/chm13v2.0_RefSeq_Liftoff_v5.1_Genes.bed";
  571. let r = read_bed(path)?;
  572. let deleted_genes = bedrow_overlaps_par(&r, &vec![&GenomeRange { contig: variant.position.contig, range: del.start..del.end }]).into_iter().filter_map(|e| {
  573. e.name
  574. }).collect::<Vec<String>>().join(", ");
  575. println!("{deleted_genes}");
  576. Ok(())
  577. }
  578. #[test]
  579. fn variant_load_deepvariant() -> anyhow::Result<()> {
  580. init();
  581. let id = "ADJAGBA";
  582. let time = "diag";
  583. let dv = DeepVariant::initialize(id, time, Config::default())?;
  584. let annotations = Annotations::default();
  585. let variant_collection = dv.variants(&annotations)?;
  586. println!("Deepvariant for {id} {time}: variants {} {}", variant_collection.variants.len(), variant_collection.vcf.n_variants);
  587. Ok(())
  588. }
  589. #[test]
  590. fn variant_load_clairs() -> anyhow::Result<()> {
  591. init();
  592. let id = "ADJAGBA";
  593. let clairs = ClairS::initialize(id, Config::default())?;
  594. let annotations = Annotations::default();
  595. let variant_collection = clairs.variants(&annotations)?;
  596. println!("ClairS for {id}: variants {} {}", variant_collection.variants.len(), variant_collection.vcf.n_variants);
  597. Ok(())
  598. }
  599. #[test]
  600. fn variant_load_nanomonsv() -> anyhow::Result<()> {
  601. init();
  602. let id = "ADJAGBA";
  603. let nanomonsv = NanomonSV::initialize(id, Config::default())?;
  604. let annotations = Annotations::default();
  605. let variant_collection = nanomonsv.variants(&annotations)?;
  606. println!("NanomonSV for {id}: variants {} {}", variant_collection.variants.len(), variant_collection.vcf.n_variants);
  607. println!("{:?}", variant_collection.variants.first());
  608. Ok(())
  609. }
  610. #[test]
  611. fn variant_load_clairs_germline() -> anyhow::Result<()> {
  612. init();
  613. let id = "ADJAGBA";
  614. let clairs = ClairS::initialize(id, Config::default())?;
  615. let annotations = Annotations::default();
  616. let germline_variant_collection = clairs.germline(&annotations)?;
  617. println!("ClairS for {id}: variants {} {}", germline_variant_collection.variants.len(), germline_variant_collection.vcf.n_variants);
  618. Ok(())
  619. }
  620. #[test]
  621. fn pipe_somatic() -> anyhow::Result<()> {
  622. init();
  623. let collections = Collections::new(
  624. CollectionsConfig::default()
  625. )?;
  626. for (a, _) in collections.bam_pairs().iter() {
  627. if ["AUBERT", "BAFFREAU", "BAILLEUL"].contains(&a.id.as_str()) {
  628. continue;
  629. }
  630. if let Err(e) = SomaticPipe::initialize(&a.id, Config::default()).map(|mut p| if p.should_run() {
  631. if let Err(e) = p.run() {
  632. error!("{e}");
  633. }
  634. }) {
  635. error!("{e}");
  636. }
  637. }
  638. Ok(())
  639. // let id = "VILI";
  640. // SomaticPipe::initialize(id, Config::default())?.run()
  641. }
  642. #[test]
  643. fn overlaps() {
  644. init();
  645. let positions = vec![
  646. &GenomePosition { contig: 1, position: 100 },
  647. &GenomePosition { contig: 1, position: 150 },
  648. &GenomePosition { contig: 1, position: 200 },
  649. &GenomePosition { contig: 2, position: 150 },
  650. ];
  651. let ranges = vec![
  652. &GenomeRange { contig: 1, range: 50..150 },
  653. &GenomeRange { contig: 2, range: 100..200 },
  654. ];
  655. let parallel_overlapping_indices = overlaps_par(&positions, &ranges);
  656. assert_eq!(parallel_overlapping_indices, vec![0, 3])
  657. }
  658. #[test]
  659. fn bed_read() -> anyhow::Result<()> {
  660. init();
  661. let path = &Config::default().mask_bed("ADJAGBA");
  662. let r = read_bed(path)?;
  663. println!("{}", r.len());
  664. Ok(())
  665. }
  666. #[test]
  667. fn test_read_dict() -> anyhow::Result<()> {
  668. init();
  669. let genome = read_dict(&Config::default().dict_file)?;
  670. let genome_length: usize = genome.into_iter().map(|(_, len)| len as usize).sum();
  671. println!("{genome_length}");
  672. Ok(())
  673. }
  674. #[test]
  675. fn bases_at() -> anyhow::Result<()> {
  676. init();
  677. let id = "ADJAGBA";
  678. let c = Config::default();
  679. let chr = "chr3";
  680. let position = 62416039; // 1-based
  681. let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "diag"))?;
  682. let p = nt_pileup(&mut bam, chr, position - 1, false)?.iter().map(|e| String::from_utf8(vec![*e]).unwrap()).collect::<Vec<_>>();
  683. let mut counts = HashMap::new();
  684. for item in p.iter() {
  685. *counts.entry(item.as_str()).or_insert(0) += 1;
  686. }
  687. for (key, value) in &counts {
  688. println!("{}: {}", key, value);
  689. }
  690. assert_eq!(8, *counts.get("C").unwrap());
  691. assert_eq!(13, *counts.get("G").unwrap());
  692. assert_eq!(6, *counts.get("D").unwrap());
  693. let chr = "chr1";
  694. let position = 3220; // 1-based
  695. let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "mrd"))?;
  696. let p = counts_at(&mut bam, chr, position - 1)?;
  697. println!("{p:#?}");
  698. Ok(())
  699. }
  700. #[test]
  701. fn seq_at() -> anyhow::Result<()> {
  702. init();
  703. let c = Config::default();
  704. let chr = "chr3";
  705. let position = 716766;
  706. let mut fasta_reader = noodles_fasta::indexed_reader::Builder::default().build_from_path(c.reference)?;
  707. let r = io::fasta::sequence_at(&mut fasta_reader, chr, position, 10)?;
  708. println!("{r} ({} {:.2})", r.len(), estimate_shannon_entropy(r.as_str()));
  709. Ok(())
  710. }
  711. #[test]
  712. fn ins_at() -> anyhow::Result<()> {
  713. init();
  714. let id = "ADJAGBA";
  715. let c = Config::default();
  716. let chr = "chr1";
  717. let position = 52232; // 1-based like in vcf
  718. let mut bam = rust_htslib::bam::IndexedReader::from_path(c.solo_bam(id, "mrd"))?;
  719. // let p = ins_pileup(&mut bam, chr, position - 1, true)?.iter().map(|e| String::from_utf8(vec![*e]).unwrap()).collect::<Vec<_>>();
  720. let counts = counts_ins_at(&mut bam, chr, position -1)?;
  721. for (key, value) in &counts {
  722. println!("{}: {}", key, value);
  723. }
  724. Ok(())
  725. }
  726. #[test]
  727. fn vep_line() -> anyhow::Result<()> {
  728. init();
  729. 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";
  730. let vep_line: VepLine = line.parse()?;
  731. println!("{vep_line:#?}");
  732. let vep: VEP = VEP::try_from(&vep_line)?;
  733. println!("{vep:#?}");
  734. Ok(())
  735. }
  736. #[test]
  737. fn savana_cn() -> anyhow::Result<()> {
  738. init();
  739. let id = "CAMARA";
  740. // let s = SavanaCopyNumber::load_id(id, Config::default())?;
  741. let s = SavanaReadCounts::load_id(id, Config::default())?;
  742. println!("tumoral reads: {}", s.n_tumoral_reads());
  743. println!("normal reads: {}", s.n_normal_reads());
  744. println!("tumoral:\n{:#?}", s.norm_chr_counts());
  745. Ok(())
  746. }
  747. #[test]
  748. fn coverage() -> anyhow::Result<()> {
  749. init();
  750. let id = "CAMARA";
  751. let time = "diag";
  752. WGSBamStats::new(id, time, Config::default())?.print();
  753. Ok(())
  754. }
  755. #[test]
  756. fn load_bam() -> anyhow::Result<()> {
  757. init();
  758. let id = "ADJAGBA";
  759. let time = "diag";
  760. let bam_path = Config::default().solo_bam(id, time);
  761. WGSBam::new(Path::new(&bam_path).to_path_buf())?;
  762. Ok(())
  763. }
  764. #[test]
  765. fn tar() -> anyhow::Result<()> {
  766. init();
  767. scan_archive("/data/lto/20241030_CUNVI-DG-N20_SEBZA-DG-N21.tar")?;
  768. Ok(())
  769. }
  770. #[test]
  771. fn run_somatic() -> anyhow::Result<()> {
  772. init();
  773. let collections = Collections::new(
  774. CollectionsConfig::default()
  775. )?;
  776. let bams = collections.bam.by_id_completed(15.0, 10.0);
  777. let n = bams.len();
  778. let mut config = Config::default();
  779. // config.somatic_scan_force = true;
  780. warn!("{n} cases");
  781. for (i, bam) in bams.iter().enumerate() {
  782. let id = &bam.id;
  783. warn!("{i}/{n} {id}");
  784. if id == "BANGA" {
  785. continue;
  786. }
  787. if id == "ARM" {
  788. continue;
  789. }
  790. match SomaticPipe::initialize(id, config.clone())?.run() {
  791. Ok(_) => (),
  792. Err(e) => error!("{id} {e}"),
  793. };
  794. }
  795. Ok(())
  796. }
  797. #[test]
  798. fn somatic_cases() -> anyhow::Result<()> {
  799. init();
  800. let id = "ACHITE";
  801. let config = Config { somatic_pipe_force: true, ..Default::default() };
  802. match SomaticPipe::initialize(id, config)?.run() {
  803. Ok(_) => (),
  804. Err(e) => error!("{id} {e}"),
  805. };
  806. Ok(())
  807. }
  808. #[test]
  809. fn load_variants() -> anyhow::Result<()> {
  810. init();
  811. let id = "ACHITE";
  812. let config = Config::default();
  813. let path = format!("{}/{id}/diag/{id}_somatic_variants.bit", config.result_dir);
  814. let variants = variant_collection::Variants::load_from_file(&path)?;
  815. println!("n variants {}", variants.data.len());
  816. let n_vep: usize = variants.data.iter().map(|v| v.vep().len()).sum();
  817. println!("VEP: {n_vep}");
  818. let translocations = variants.get_alteration_cat(AlterationCategory::TRL);
  819. println!("{} translocations", translocations.len());
  820. let threshold = 5;
  821. let res = group_variants_by_bnd_desc(&translocations, 5);
  822. let rres = group_variants_by_bnd_rc(&res, threshold);
  823. rres.iter().for_each(|group| {
  824. println!("{} {}", group.0.len(), group.1.len());
  825. });
  826. Ok(())
  827. }
  828. #[test]
  829. fn load_fc() -> anyhow::Result<()> {
  830. init();
  831. // FlowCells::load_archive_from_scan("/data/lto", "/data/archives.json")?;
  832. let r = FlowCells::load("/home/prom/mnt/store",
  833. "/data/pandora_id_inputs.json", "/data/archives.json.gz")?;
  834. println!("{r:#?}");
  835. Ok(())
  836. }
  837. #[test]
  838. fn alt_cat() -> anyhow::Result<()> {
  839. let id = "ADJAGBA";
  840. let config = Config::default();
  841. let path = format!("{}/{id}/diag/somatic_variants.json.gz", config.result_dir);
  842. let variants = variant_collection::Variants::load_from_json(&path)?;
  843. println!("n variants {}", variants.data.len());
  844. variants.data.iter()
  845. .filter(|v| v.alteration_category().contains(&AlterationCategory::TRL))
  846. .for_each(|v| {
  847. println!("{:?} {}",
  848. v.vcf_variants.iter().map(|v| v.bnd_desc()).collect::<Vec<_>>(),
  849. v.annotations.iter().filter(|a| matches!(a, Annotation::Callers(..))).map(|a| a.to_string()).collect::<Vec<String>>().join(";")
  850. )
  851. });
  852. Ok(())
  853. }
  854. #[test]
  855. fn variants_stats() -> anyhow::Result<()> {
  856. init();
  857. let config = Config::default();
  858. let all_variants_bit = find_files(&format!("{}/*/diag/*_somatic_variants.bit", config.result_dir))?;
  859. for v in all_variants_bit.into_iter() {
  860. let id = v.file_name().unwrap().to_str().unwrap().split("_somatic").next().unwrap();
  861. println!("{id}");
  862. let config = Config::default();
  863. let path = format!("{}/{id}/diag/{id}_somatic_variants.bit", config.result_dir);
  864. match variant_collection::Variants::load_from_file(&path) {
  865. Ok(mut variants) => {
  866. let (mut high_depth_ranges, _) =
  867. somatic_depth_quality_ranges(&id, &config)?;
  868. high_depth_ranges.par_sort_by_key(|r| (r.contig, r.range.start));
  869. let res = VariantsStats::new(&mut variants, id, &config, &high_depth_ranges)?.save_to_json(&format!(
  870. "{}/{id}/diag/{id}_somatic_variants_stats.json.gz", config.result_dir));
  871. if res.is_err() {
  872. info!("{:#?}", res);
  873. }
  874. },
  875. Err(e) => error!("{e}"),
  876. }
  877. }
  878. Ok(())
  879. }
  880. #[test]
  881. fn constit_stats() {
  882. init();
  883. let id = "ADJAGBA";
  884. let config = Config::default();
  885. let _ = const_stats(id.to_string(), config);
  886. }
  887. #[test]
  888. fn test_bnd() -> anyhow::Result<()> {
  889. init();
  890. let id = "COIFFET";
  891. let config = Config::default();
  892. let annotations = Annotations::default();
  893. let s = Savana::initialize(id, config)?.variants(&annotations)?;
  894. s.variants.iter().for_each(|e| {
  895. if let Ok(bnd) = e.bnd_desc() {
  896. println!("{}\t{}\t{}", e.position , e.reference, e.alternative);
  897. println!("{:#?}", bnd);
  898. }
  899. });
  900. Ok(())
  901. }
  902. #[test]
  903. fn parse_savana_seg() {
  904. init();
  905. let r = SavanaCN::parse_file("ADJAGBA", &config::Config::default()).unwrap().segments;
  906. println!("{} lines", r.len());
  907. println!("{:#?}", r.first().unwrap());
  908. }
  909. #[test]
  910. fn whole_scan() -> anyhow::Result<()> {
  911. init();
  912. let id = "CHENU";
  913. let mut config = Config::default();
  914. let u = config.solo_bam(id, "mrd");
  915. println!("{u}");
  916. config.somatic_scan_force = true;
  917. somatic_scan(id, &config)?;
  918. Ok(())
  919. }
  920. #[test]
  921. fn parse_gff() -> anyhow::Result<()> {
  922. init();
  923. let id = "ADJAGBA";
  924. let config = Config::default();
  925. let path = format!("{}/{id}/diag/somatic_variants.bit", config.result_dir);
  926. let exon_ranges = features_ranges("exon", &config)?;
  927. let exon_ranges = merge_overlapping_genome_ranges(&exon_ranges);
  928. let variants = variant_collection::Variants::load_from_file(&path)?;
  929. let full = variants_stats::somatic_rates(&variants.data, &exon_ranges, &config);
  930. info!("{full:#?}");
  931. // let restrained: Vec<variant_collection::Variant> = variants.data.iter().filter(|v| v.vcf_variants.len() >= 2)
  932. // .cloned().collect();
  933. // let min_2 = variants_stats::somatic_rates(&restrained, &exon_ranges, &config);
  934. // info!("{min_2:#?}");
  935. //
  936. // let restrained: Vec<variant_collection::Variant> = restrained.iter().filter(|v| v.vcf_variants.len() >= 3)
  937. // .cloned().collect();
  938. // let min_3 = variants_stats::somatic_rates(&restrained, &exon_ranges, &config);
  939. // info!("{min_3:#?}");
  940. // let mut high_depth_ranges = variants_stats::high_depth_somatic(id, &config)?;
  941. // high_depth_ranges.par_sort_by_key(|r| ( r.contig, r.range.start ));
  942. //
  943. // let exon_ranges_ref: Vec<&GenomeRange> = exon_ranges.iter().collect();
  944. // let exons_high_depth = range_intersection_par(&high_depth_ranges.iter().collect::<Vec<&GenomeRange>>(), &exon_ranges_ref);
  945. //
  946. // let full = variants_stats::somatic_rates(&variants.data, &exons_high_depth, &config);
  947. // info!("{full:#?}");
  948. //
  949. // info!("n variants loaded: {}", variants.data.len());
  950. //
  951. // let r = features_ranges("exon", &config::Config::default())?;
  952. // info!("n exon: {}", r.len());
  953. //
  954. // let merged = merge_overlapping_genome_ranges(&r);
  955. // info!("n merged exon: {}", merged.len());
  956. //
  957. // let ol = par_overlaps(&variants.data, &r);
  958. // info!("variants in exon {}", ol.len());
  959. //
  960. // 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();
  961. // info!("coding variants {n_coding}");
  962. //
  963. // let n_bases_m = merged.par_iter().map(|gr| gr.length()).sum::<u32>();
  964. // info!("{n_bases_m}nt");
  965. //
  966. // let mega_base_m = n_bases_m as f64 / 10.0e6;
  967. //
  968. // let wgs_len = read_dict(&config.dict_file)?.iter().map(|(_, l)| *l).sum::<u32>();
  969. // info!("wgs len {wgs_len}");
  970. // let rate_wgs = variants.data.len() as f64 / (wgs_len as f64 / 10.0e6);
  971. // info!("somatic mutation rate {rate_wgs:.2}/mb");
  972. //
  973. // let n_exons_mb = ol.len() as f64 / mega_base_m;
  974. // info!("somatic mutation rate in the coding region {n_exons_mb:.2}/mb");
  975. //
  976. // let n_exons_mb = n_coding as f64 / mega_base_m;
  977. // info!("somatic non synonymous mutation rate in the coding region {n_exons_mb:.2}/mb");
  978. Ok(())
  979. }
  980. fn gr(contig: u8, start: u32, end: u32) -> GenomeRange {
  981. GenomeRange {
  982. contig,
  983. range: start..end,
  984. }
  985. }
  986. #[test]
  987. fn test_both_empty() {
  988. let a: Vec<&GenomeRange> = vec![];
  989. let b: Vec<&GenomeRange> = vec![];
  990. let result = range_intersection_par(&a, &b);
  991. assert!(result.is_empty());
  992. }
  993. #[test]
  994. fn test_one_empty() {
  995. let a = [gr(1, 0, 100)];
  996. let b: Vec<&GenomeRange> = vec![];
  997. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  998. let mut result = range_intersection_par(&a_refs, &b);
  999. sort_ranges(&mut result);
  1000. assert!(result.is_empty());
  1001. }
  1002. #[test]
  1003. fn test_single_range_no_overlap() {
  1004. let a = [gr(1, 0, 100)];
  1005. let b = [gr(1, 100, 200)];
  1006. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1007. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1008. let mut result = range_intersection_par(&a_refs, &b_refs);
  1009. sort_ranges(&mut result);
  1010. assert!(result.is_empty());
  1011. }
  1012. #[test]
  1013. fn test_single_range_full_overlap() {
  1014. let a = [gr(1, 0, 100)];
  1015. let b = [gr(1, 0, 100)];
  1016. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1017. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1018. let mut result = range_intersection_par(&a_refs, &b_refs);
  1019. let mut expected = [gr(1, 0, 100)];
  1020. sort_ranges(&mut result);
  1021. sort_ranges(&mut expected);
  1022. assert_eq!(result, expected);
  1023. }
  1024. #[test]
  1025. fn test_different_contigs() {
  1026. let a = [gr(1, 0, 100)];
  1027. let b = [gr(2, 0, 100)];
  1028. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1029. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1030. let mut result = range_intersection_par(&a_refs, &b_refs);
  1031. sort_ranges(&mut result);
  1032. assert!(result.is_empty());
  1033. }
  1034. #[test]
  1035. fn test_touching_ranges() {
  1036. let a = [gr(1, 0, 100)];
  1037. let b = [gr(1, 100, 200)];
  1038. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1039. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1040. let mut result = range_intersection_par(&a_refs, &b_refs);
  1041. sort_ranges(&mut result);
  1042. assert!(result.is_empty());
  1043. }
  1044. #[test]
  1045. fn test_complete_subrange() {
  1046. let a = [gr(1, 0, 200)];
  1047. let b = [gr(1, 50, 150)];
  1048. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1049. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1050. let mut result = range_intersection_par(&a_refs, &b_refs);
  1051. let mut expected = [gr(1, 50, 150)];
  1052. sort_ranges(&mut result);
  1053. sort_ranges(&mut expected);
  1054. assert_eq!(result, expected);
  1055. }
  1056. #[test]
  1057. fn test_multiple_overlaps_same_contig() {
  1058. let a = [gr(1, 0, 50), gr(1, 75, 125)];
  1059. let b = [gr(1, 25, 100), gr(1, 150, 200)];
  1060. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1061. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1062. let mut result = range_intersection_par(&a_refs, &b_refs);
  1063. let mut expected = [gr(1, 25, 50), gr(1, 75, 100)];
  1064. sort_ranges(&mut result);
  1065. sort_ranges(&mut expected);
  1066. assert_eq!(result, expected);
  1067. }
  1068. #[test]
  1069. fn test_multiple_contigs() {
  1070. let a = [gr(1, 0, 100), gr(2, 50, 150), gr(3, 200, 300)];
  1071. let b = [gr(1, 50, 150), gr(2, 0, 100), gr(4, 0, 100)];
  1072. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1073. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1074. let mut result = range_intersection_par(&a_refs, &b_refs);
  1075. let mut expected = [gr(1, 50, 100), gr(2, 50, 100)];
  1076. sort_ranges(&mut result);
  1077. sort_ranges(&mut expected);
  1078. assert_eq!(result, expected);
  1079. }
  1080. #[test]
  1081. fn test_adjacent_ranges() {
  1082. let a = [gr(1, 0, 50), gr(1, 50, 100)];
  1083. let b = [gr(1, 25, 75)];
  1084. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1085. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1086. let mut result = range_intersection_par(&a_refs, &b_refs);
  1087. let mut expected = [gr(1, 25, 50), gr(1, 50, 75)];
  1088. sort_ranges(&mut result);
  1089. sort_ranges(&mut expected);
  1090. assert_eq!(result, expected);
  1091. }
  1092. #[test]
  1093. fn test_minimal_overlap() {
  1094. let a = [gr(1, 0, 100)];
  1095. let b = [gr(1, 99, 200)];
  1096. let a_refs: Vec<&GenomeRange> = a.iter().collect();
  1097. let b_refs: Vec<&GenomeRange> = b.iter().collect();
  1098. let mut result = range_intersection_par(&a_refs, &b_refs);
  1099. let mut expected = [gr(1, 99, 100)];
  1100. sort_ranges(&mut result);
  1101. sort_ranges(&mut expected);
  1102. assert_eq!(result, expected);
  1103. }
  1104. #[test]
  1105. fn todo_scan() -> anyhow::Result<()> {
  1106. init();
  1107. let mut collections = Collections::new(
  1108. CollectionsConfig::default()
  1109. )?;
  1110. collections.todo_bam_count(&Config::default())?;
  1111. collections.tasks.iter().for_each(|t| info!("{t}"));
  1112. let pool = rayon::ThreadPoolBuilder::new()
  1113. .num_threads(100)
  1114. .build()
  1115. .unwrap();
  1116. pool.install(move || {
  1117. collections.tasks.into_iter().for_each(|t| {
  1118. // info!("{t}");
  1119. if let Err(e) = t.run() {
  1120. error!("{e}");
  1121. }
  1122. });
  1123. });
  1124. Ok(())
  1125. }
  1126. /// helper to build a forward‑strand BND (same contig) where
  1127. /// A = pos, B = pos + 5
  1128. fn fwd(contig: &str, pos: u32) -> BNDDesc {
  1129. BNDDesc {
  1130. a_contig: contig.into(),
  1131. a_position: pos,
  1132. a_sens: true,
  1133. b_contig: contig.into(),
  1134. b_position: pos + 5,
  1135. b_sens: true,
  1136. added_nt: String::new(),
  1137. }
  1138. }
  1139. /// Build a six‑node *forward* chain relying **only** on `auto_connect()`
  1140. /// (no manual edges) and assert the Hamiltonian path spans all nodes.
  1141. #[test]
  1142. fn hamiltonian_chain_auto() {
  1143. // positions 10,15 20,25 … 60,65 satisfy B(u) ≤ A(v)
  1144. let bnds: Vec<BNDDesc> = (1..=6).map(|i| fwd("chr1", i * 10)).collect();
  1145. let g: BNDGraph<()> = bnds.to_bnd_graph(); // trait uses auto_connect()
  1146. // ensure auto_connect produced 5 edges in a line
  1147. assert_eq!(g.inner().edge_count(), 5);
  1148. let path = g.hamiltonian_path().expect("chain should be Hamiltonian");
  1149. assert_eq!(path.len(), 6);
  1150. }
  1151. /// Two disconnected "V" shapes -> auto_connect creates 2 edges in each
  1152. /// component, no Hamiltonian path, components sorted by size.
  1153. #[test]
  1154. fn components_after_auto_connect() {
  1155. // comp1: a->b<-c on reverse strand of chrX
  1156. let a = BNDDesc {
  1157. a_contig: "chrX".into(), a_position: 300, a_sens: false,
  1158. b_contig: "chrX".into(), b_position: 250, b_sens: false,
  1159. added_nt: String::new()
  1160. };
  1161. let b = BNDDesc {
  1162. a_contig: "chrX".into(), a_position: 200, a_sens: false,
  1163. b_contig: "chrX".into(), b_position: 150, b_sens: false,
  1164. added_nt: String::new()
  1165. };
  1166. let c = BNDDesc {
  1167. a_contig: "chrX".into(), a_position: 100, a_sens: false,
  1168. b_contig: "chrX".into(), b_position: 50, b_sens: false,
  1169. added_nt: String::new()
  1170. };
  1171. // comp2: three‑node forward chain on chrY
  1172. let d = fwd("chrY", 10);
  1173. let e = fwd("chrY", 20);
  1174. let f = fwd("chrY", 30);
  1175. let g: BNDGraph<()> = vec![a,b,c,d,e,f].to_bnd_graph();
  1176. assert!(g.hamiltonian_path().is_none());
  1177. let comps = g.components_by_size();
  1178. comps.iter().for_each(|a| println!("{}", g.fmt_path(a) ));
  1179. assert_eq!(comps.len(), 2);
  1180. assert_eq!(comps[0].len(), 3);
  1181. assert_eq!(comps[1].len(), 3);
  1182. }
  1183. #[test]
  1184. fn bnd_connections() {
  1185. // comp1: a->b<-c on reverse strand of chrX
  1186. let a = BNDDesc {
  1187. a_contig: "chrX".into(), a_position: 300, a_sens: true,
  1188. b_contig: "chr14".into(), b_position: 250, b_sens: false,
  1189. added_nt: String::new()
  1190. };
  1191. let b = BNDDesc {
  1192. a_contig: "chr14".into(), a_position: 200, a_sens: false,
  1193. b_contig: "chrX".into(), b_position: 301, b_sens: true,
  1194. added_nt: String::new()
  1195. };
  1196. let c = BNDDesc {
  1197. a_contig: "chrX".into(), a_position: 302, a_sens: true,
  1198. b_contig: "chrZ".into(), b_position: 50, b_sens: false,
  1199. added_nt: String::new()
  1200. };
  1201. // comp2: three‑node forward chain on chrY
  1202. let d = fwd("chrY", 10);
  1203. let e = fwd("chrY", 20);
  1204. let f = fwd("chrY", 30);
  1205. let g: BNDGraph<()> = vec![a,b,c,d,e,f].to_bnd_graph();
  1206. assert!(g.hamiltonian_path().is_none());
  1207. let comps = g.components_by_size();
  1208. comps.iter().for_each(|a| println!("{}", g.fmt_path(a) ));
  1209. assert_eq!(comps.len(), 2);
  1210. assert_eq!(comps[0].len(), 3);
  1211. assert_eq!(comps[1].len(), 3);
  1212. }
  1213. }