| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290 |
- use log::{info, warn};
- use serde::{Deserialize, Serialize};
- use std::fs;
- use std::path::{Path, PathBuf};
- const CONFIG_TEMPLATE: &str = include_str!("../pandora-config.example.toml");
- #[derive(Debug, Clone, Serialize, Deserialize)]
- /// Global configuration for the Pandora somatic pipeline.
- ///
- /// Loaded from `~/.local/share/pandora/pandora-config.toml` (see [`Config::config_path`]).
- /// Most fields are path templates that can contain placeholders such as:
- /// `{result_dir}`, `{id}`, `{time}`, `{reference_name}`, `{haplotagged_bam_tag_name}`, `{output_dir}`.
- pub struct Config {
- // === General filesystem layout / I/O ===
- /// Root directory where all results will be written.
- pub result_dir: String,
- /// Temporary directory.
- pub tmp_dir: String,
- /// Run cache directory.
- pub run_cache_dir: String,
- /// Runner can slurm
- pub slurm_runner: bool,
- /// Slurm max parallel jobs
- pub slurm_max_par: u8,
- /// Software threads
- pub threads: u8,
- /// Singularity/Apptainer bin
- pub singularity_bin: String,
- /// Path to the `conda.sh` activation script (used to activate envs before running tools).
- pub conda_sh: String,
- // === Alignment / BAM handling ===
- /// Configuration for Dorado + samtools alignment pipeline.
- pub align: AlignConfig,
- /// Minimum MAPQ for reads to be kept during BAM filtering.
- pub bam_min_mapq: u8,
- /// Number of threads for hts BAM reader
- pub bam_n_threads: u8,
- /// Number of reads sampled when estimating BAM composition (e.g. tumor contamination).
- pub bam_composition_sample_size: u32,
- // === Reference genome and annotations ===
- /// Path to the reference FASTA used throughout the pipeline.
- pub reference: String,
- /// Short name for the reference (e.g. "hs1"), used in filenames.
- pub reference_name: String,
- /// Pseudoautosomal regions (PARs) BED file.
- pub pseudoautosomal_regions_bed: String,
- /// Path to the sequence dictionary (`.dict`) for the reference.
- pub dict_file: String,
- /// Path to the RefSeq GFF3 annotation, sorted and indexed.
- pub refseq_gff: String,
- pub refseq_gtf: String,
- /// dbSNP vcf.gz file (should be indexed)
- pub db_snp: String,
- /// CoLoRSdb jasmine (should be indexed, tbi)
- pub colors_db: String,
- pub colors_db_smallvar: String,
- /// BED with genes on the 4th column
- pub genes_bed: String,
- /// Cytobands BED file
- pub cytobands_bed: String,
- /// Chromosome alias file
- pub chromosomes_alias: String,
- /// BED template used to mask low-quality or filtered regions.
- ///
- /// Placeholders:
- /// - `{result_dir}`: global result directory
- /// - `{id}`: case identifier
- pub mask_bed: String,
- /// Panels of interest (name, BED path).
- pub panels: Vec<(String, String)>,
- /// Repeats bed file
- pub repeats_bed: String,
- // === Sample naming conventions ===
- /// Label used for the tumor sample in directory and file names (e.g. "diag").
- pub tumoral_name: String,
- /// Label used for the normal sample (e.g. "mrd").
- pub normal_name: String,
- /// BAM tag name used for haplotagged reads (e.g. "HP").
- pub haplotagged_bam_tag_name: String,
- // === Coverage counting (somatic-scan) ===
- /// Name of the subdirectory (under each sample dir) where count files are stored.
- pub count_dir_name: String,
- /// Bin size (bp) for count files.
- pub count_bin_size: u32,
- /// Number of chunks used to split chromosomes for counting.
- pub count_n_chunks: u32,
- /// Whether to force recomputation of coverage / counting even if outputs already exist.
- pub somatic_scan_force: bool,
- // === Somatic pipeline global options ===
- /// Whether to force recomputation of the whole somatic pipeline.
- pub somatic_pipe_force: bool,
- /// Default number of threads for most heavy tools (DeepVariant, Savana, etc.).
- pub somatic_pipe_threads: u8,
- /// Path template to the per-case somatic pipeline statistics directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`.
- pub somatic_pipe_stats: String,
- // === Basic somatic filtering / QC thresholds ===
- /// Minimum depth in the constitutional sample to consider a site evaluable.
- pub somatic_min_constit_depth: u16,
- /// Maximum allowed ALT count in the constitutional sample for a somatic call.
- pub somatic_max_alt_constit: u16,
- /// Window size (bp) used when computing sequence entropy around variants.
- pub entropy_seq_len: usize,
- /// Minimum Shannon entropy threshold for keeping a variant.
- pub min_shannon_entropy: f64,
- /// Minimum dbSNP population frequency (max across studies) to consider a variant a known
- /// polymorphism for the dbSNP + constitutional-alt filter.
- pub db_snp_min_freq: f32,
- /// Maximum depth considered "low quality" for certain filters.
- pub max_depth_low_quality: u32,
- /// Minimum depth considered "high quality" for certain filters.
- pub min_high_quality_depth: u32,
- /// Minimum number of callers supporting a variant for it to be kept.
- pub min_n_callers: u8,
- // === DeepVariant configuration ===
- /// Template for the DeepVariant output directory (solo and normal/tumor runs).
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub deepvariant_output_dir: String,
- /// Number of threads to use for DeepVariant.
- pub deepvariant_threads: u8,
- /// DeepVariant singularity image path
- pub deepvariant_image: String,
- /// DeepVariant model type (e.g. "ONT_R104").
- pub deepvariant_model_type: String,
- /// Force DeepVariant recomputation even if outputs already exist.
- pub deepvariant_force: bool,
- // === DeepSomatic configuration ===
- /// Template for the DeepSomatic output directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub deepsomatic_output_dir: String,
- /// Number of threads for DeepSomatic.
- pub deepsomatic_threads: u8,
- /// DeepSomatic singularity image path
- pub deepsomatic_image: String,
- /// DeepSomatic model type (e.g. "ONT").
- pub deepsomatic_model_type: String,
- /// Force DeepSomatic recomputation.
- pub deepsomatic_force: bool,
- // === ClairS configuration ===
- /// Number of threads for ClairS.
- pub clairs_threads: u8,
- /// Path to ClairS singularity image.
- pub clairs_image: String,
- /// Force ClairS recomputation.
- pub clairs_force: bool,
- /// Keep per-part directories after chunked ClairS merging (default: false).
- pub clairs_keep_parts: bool,
- /// Platform preset for ClairS (e.g. "ont_r10_dorado_sup_5khz_ssrs").
- pub clairs_platform: String,
- /// Template for ClairS output directory (`{result_dir}`, `{id}`).
- pub clairs_output_dir: String,
- // === GATK configuration ===
- /// Path to the GATK container image (Singularity/Apptainer .sif, or a docker:// URI
- /// if you pull at runtime).
- ///
- /// Examples:
- /// - "/containers/gatk_4.6.0.0.sif"
- /// - "docker://broadinstitute/gatk:latest"
- pub gatk_image: String,
- /// Path to a BED file restricting analysis to target regions (0-based, half-open).
- /// Must match contig naming of the reference/BAMs (e.g. "chr9" vs "9").
- ///
- /// Used for targeted calling (e.g. Mutect2 `-L` or region chunking).
- pub gatk_bed_path: String,
- /// Local single-run CPU threads (non-Slurm execution).
- /// Used for full-run Mutect2 or other GATK tools.
- /// Typically forwarded to:
- /// - `--native-pair-hmm-threads`
- /// - `--reader-threads`
- /// Should match available cores on the node.
- pub gatk_threads: usize, // e.g. 32
- /// Local single-run memory limit in GB.
- /// Used to size Java heap:
- /// `--java-options "-Xmx{mem}g"`
- /// Should leave headroom for native memory (PairHMM, buffers).
- pub gatk_mem_gb: u32, // e.g. 120
- /// Per-chunk CPU threads when running chunked under Slurm.
- /// Applies to each parallel job independently.
- pub gatk_slurm_threads: usize, // e.g. 8
- /// Per-chunk memory (GB) when running under Slurm.
- /// Used both for scheduler request and Java heap sizing per chunk.
- /// Must be sufficient for interval-restricted Mutect2.
- pub gatk_slurm_mem_gb: u32, // e.g. 32
- /// If true, force re-run of GATK steps by removing or ignoring existing outputs.
- pub gatk_force: bool,
- /// Template for GATK output directory (`{result_dir}`, `{id}`).
- pub gatk_output_dir: String,
- /// GATK passed VCF
- pub gatk_passed_vcf: String,
- // === Savana configuration ===
- /// Savana binary name or full path.
- pub savana_bin: String,
- /// Number of threads for Savana.
- pub savana_threads: u8,
- /// RAM capacity used for running Savana with slurm.
- pub savana_mem: u8,
- /// Template for Savana output directory (`{result_dir}`, `{id}`).
- pub savana_output_dir: String,
- /// Template for Savana copy number file.
- ///
- /// Placeholders: `{output_dir}`, `{id}`, `{reference_name}`, `{haplotagged_bam_tag_name}`.
- pub savana_copy_number: String,
- /// Template for Savana raw read counts file.
- ///
- /// Same placeholders as [`Config::savana_copy_number`].
- pub savana_read_counts: String,
- /// Template for Savana passed VCF output (`{output_dir}`, `{id}`).
- pub savana_passed_vcf: String,
- /// Force Savana recomputation.
- pub savana_force: bool,
- /// Template for constitutional phased VCF (`{result_dir}`, `{id}`).
- pub germline_phased_vcf: String,
- // === CoRAL ===
- /// Number of CPU threads for the CoRAL reconstruction job.
- ///
- /// CoRAL is CPU-bound during breakpoint graph construction and quadratic
- /// programming cycle extraction. 8–16 threads is sufficient for most
- /// focal amplification cases; increase for highly complex ecDNA with
- /// many amplicons.
- ///
- /// # TOML
- /// ```toml
- /// coral_threads = 16
- /// ```
- pub coral_threads: u8,
- pub coral_dir: String,
- /// Memory allocation for the CoRAL SLURM job (e.g. `"32G"`).
- ///
- /// Memory usage scales with amplicon complexity and BAM depth.
- /// 32G is sufficient for typical WGS at 30–60×; increase to 64G
- /// for highly rearranged genomes (chromothripsis, high ecDNA copy number).
- ///
- /// # TOML
- /// ```toml
- /// coral_slurm_mem = "32G"
- /// ```
- pub coral_slurm_mem: String,
- /// SLURM partition to use for CoRAL jobs.
- ///
- /// CoRAL requires only CPU — do not submit to a GPU partition.
- /// Use the same partition as other CPU-bound callers (e.g. Severus, NanomonSV).
- ///
- /// # TOML
- /// ```toml
- /// coral_slurm_partition = "cpuq"
- /// ```
- pub coral_slurm_partition: String,
- /// Minimum copy number gain threshold for a segment to be considered
- /// a focal amplification seed (CoRAL `--gain`).
- ///
- /// CoRAL applies this threshold to the raw absolute CN values from the
- /// cn_segs BED — do NOT pre-correct for purity or ploidy, as this may
- /// cause entire chromosome arms to exceed the threshold in aneuploid tumours.
- ///
- /// Default in CoRAL is 6.0 (diploid assumption). For hyperdiploid tumours
- /// (e.g. hyperploid ALL, CML blast crisis) consider lowering to 4.0–5.0.
- ///
- /// # TOML
- /// ```toml
- /// coral_seed_gain = 6.0
- /// ```
- pub coral_seed_gain: f64,
- /// Minimum size in base pairs for a CN segment to qualify as a seed
- /// (CoRAL `--min-seed-size`).
- ///
- /// Segments below this size are discarded even if they exceed `coral_seed_gain`.
- /// Two merged proximal segments (see `coral_max_seg_gap`) are evaluated
- /// against this threshold as a single combined interval.
- ///
- /// Default in CoRAL is 100000 (100 kb). Reducing this risks including
- /// artefactual short high-copy segments; increasing it misses small focal
- /// amplifications (e.g. narrow EGFR or MYC peaks).
- ///
- /// # TOML
- /// ```toml
- /// coral_min_seed_size = 100000
- /// ```
- pub coral_min_seed_size: u32,
- /// Maximum gap in base pairs between two proximal CN segments to allow
- /// merging into a single seed candidate (CoRAL `--max-seg-gap`).
- ///
- /// If two amplified segments are separated by a gap smaller than this value,
- /// they are merged before the `coral_min_seed_size` filter is applied.
- /// This handles cases where a single focal amplicon is split by a low-coverage
- /// or diploid bin.
- ///
- /// Default in CoRAL is 300000 (300 kb). For haematological cancers with
- /// compact focal amplifications (e.g. NUP214::ABL1, ABL1 amplification in
- /// CML blast crisis) a tighter value such as 100000 reduces spurious merging
- /// of adjacent independent amplicons.
- ///
- /// # TOML
- /// ```toml
- /// coral_max_seg_gap = 100000
- /// ```
- pub coral_max_seg_gap: u32,
- // === Severus configuration ===
- /// Path to Severus main script (`severus.py`).
- pub severus_bin: String,
- /// Force Severus recomputation.
- pub severus_force: bool,
- /// Number of threads for Severus.
- pub severus_threads: u8,
- /// VNTRs BED file for Severus.
- pub vntrs_bed: String,
- /// Path to Severus PoN file (TSV or VCF).
- pub severus_pon: String,
- /// Template for Severus tumor/normal (paired) output directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`.
- pub severus_output_dir: String,
- /// Template for Severus solo output directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub severus_solo_output_dir: String,
- // === MARLIN ===
- pub marlin_bed: String,
- // === Echtvar ===
- pub echtvar_bin: String,
- pub echtvar_sources: Vec<String>,
- // === Bcftools configuration ===
- /// Path to Bcftools binary.
- pub bcftools_bin: String,
- /// Number of threads for Bcftools.
- pub bcftools_threads: u8,
- // === Longphase configuration ===
- /// Path to longphase binary.
- pub longphase_bin: String,
- /// Number of threads for longphase.
- pub longphase_threads: u8,
- /// Number of threads for longphase modcall step.
- pub longphase_modcall_threads: u8,
- /// Force longphase recomputation (haplotagging/phasing).
- pub longphase_force: bool,
- /// Template for longphase modcall VCF.
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub longphase_modcall_vcf: String,
- // === Modkit configuration ===
- /// Path to modkit binary.
- pub modkit_bin: String,
- /// Number of threads for `modkit summary`.
- pub modkit_summary_threads: u8,
- /// Template for modkit summary output file.
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub modkit_summary_file: String,
- // === Nanomonsv configuration ===
- /// Path to nanomonsv binary.
- pub nanomonsv_bin: String,
- /// Template for paired nanomonsv output directory (`{result_dir}`, `{id}`, `{time}`).
- pub nanomonsv_output_dir: String,
- /// Force nanomonsv recomputation.
- pub nanomonsv_force: bool,
- /// Number of threads for nanomonsv.
- pub nanomonsv_threads: u8,
- /// Template for paired nanomonsv passed VCF (`{output_dir}`, `{id}`).
- pub nanomonsv_passed_vcf: String,
- /// Template for solo nanomonsv output directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub nanomonsv_solo_output_dir: String,
- /// Template for solo nanomonsv passed VCF (`{output_dir}`, `{id}`, `{time}`).
- pub nanomonsv_solo_passed_vcf: String,
- pub nanomonsv_simple_repeat_bed: String,
- pub nanomonsv_line1_bed: String,
- // === Straglr configuration ===
- /// Path to Straglr executable.
- pub straglr_bin: String,
- /// Path to STR loci BED file for Straglr.
- pub straglr_loci_bed: String,
- /// Size of reported difference between normal and tumoral
- pub straglr_min_size_diff: u32,
- /// Minimum CN of reported difference between normal and tumoral
- pub straglr_min_support_diff: u32,
- /// Minimum read support for STR genotyping.
- pub straglr_min_support: u32,
- /// Minimum cluster size for STR detection.
- pub straglr_min_cluster_size: u32,
- /// Whether to genotype in size mode.
- pub straglr_genotype_in_size: bool,
- /// Template for paired Straglr output directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`.
- pub straglr_output_dir: String,
- /// Template for solo Straglr output directory.
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub straglr_solo_output_dir: String,
- /// Force Straglr recomputation.
- pub straglr_force: bool,
- // === PromethION runs / metadata ===
- /// Directory containing metadata about PromethION runs.
- pub promethion_runs_metadata_dir: String,
- /// JSON file describing PromethION runs and flowcell IDs.
- pub promethion_runs_input: String,
- // === VEP ===
- /// Path to VEP singularity image
- pub vep_image: String,
- /// Path to the VEP cache directory
- pub vep_cache_dir: String,
- /// Path to VEP sorted GFF
- pub vep_gff: String,
- // === Flye ===
- /// "python_bin_path flye_bin_path" ex.: "/usr/bin/python /home/t_steimle/somatic_pipe_tools/Flye/bin/flye"
- pub flye_bin: String,
- /// Flye threads
- pub flye_threads: u8,
- /// Slurm max mem
- pub flye_slurm_mem: String,
- pub minimap2_bin: String,
- pub minimap2_threads: u8,
- pub minimap2_slurm_mem: String,
- pub medaka_env: String,
- pub medaka_consensus_bin: String,
- pub medaka_threads: u8,
- pub medaka_slurm_mem: String,
- pub medaka_model: String,
- pub wtdbg2_bin: String,
- pub wtdbg2_threads: u8,
- pub wtdbg2_slurm_mem: String,
- // === longcallD configuration ===
- /// Template for the longcallD output directory (solo and normal/tumor runs).
- ///
- /// Placeholders: `{result_dir}`, `{id}`, `{time}`.
- pub longcalld_output_dir: String,
- pub longcalld_bin: String,
- pub longcalld_threads: u8,
- pub longcalld_slurm_mem: String,
- }
- #[derive(Debug, Clone, Serialize, Deserialize)]
- /// Configuration for basecalling and alignment using Dorado and samtools.
- pub struct AlignConfig {
- /// Path to Dorado binary.
- pub dorado_bin: String,
- /// Arguments passed to `dorado basecaller` (e.g. devices and model name).
- pub dorado_basecall_arg: String,
- /// Should dorado re-align after demux ?
- pub dorado_should_realign: bool,
- /// Dorado aligner threads number
- pub dorado_aligner_threads: u8,
- /// Reference FASTA used for alignment.
- pub ref_fa: String,
- /// Minimap2 index (`.mmi`) used by Dorado or downstream tools.
- pub ref_mmi: String,
- /// Path to Samtools binary.
- pub samtools_bin: String,
- /// Number of threads given to `samtools view`.
- pub samtools_view_threads: u8,
- /// Number of threads given to `samtools sort`.
- pub samtools_sort_threads: u8,
- /// Number of threads given to `samtools merge`.
- pub samtools_merge_threads: u8,
- /// Number of threads given to `samtools split`.
- pub samtools_split_threads: u8,
- }
- // Here comes names that can't be changed from output of tools
- lazy_static! {
- /// Template name for DeepVariant VCF outputs.
- static ref DEEPVARIANT_OUTPUT_NAME: &'static str = "{id}_{time}_DeepVariant.vcf.gz";
- /// ClairS main SNP/indel VCF name.
- static ref CLAIRS_OUTPUT_NAME: &'static str = "output.vcf.gz";
- /// ClairS indel-only VCF name.
- static ref CLAIRS_OUTPUT_INDELS_NAME: &'static str = "indel.vcf.gz";
- /// ClairS germline normal VCF name.
- static ref CLAIRS_GERMLINE_NORMAL: &'static str = "clair3_normal_germline_output.vcf.gz";
- /// ClairS germline tumor VCF name.
- static ref CLAIRS_GERMLINE_TUMOR: &'static str = "clair3_tumor_germline_output.vcf.gz";
- }
- // impl Default for AlignConfig {
- // fn default() -> Self {
- // Self {
- // dorado_bin: "/data/tools/dorado-1.1.1-linux-x64/bin/dorado".to_string(),
- // dorado_basecall_arg: "-x 'cuda:0,1,2,3' sup,5mC_5hmC".to_string(),
- // ref_fa: "/data/ref/hs1/chm13v2.0.fa".to_string(),
- // ref_mmi: "/data/ref/chm13v2.0.mmi".to_string(),
- // samtools_view_threads: 20,
- // samtools_sort_threads: 50,
- // }
- // }
- // }
- //
- impl Config {
- /// Returns the config file path, e.g.:
- /// `~/.local/share/pandora/pandora-config.toml`.
- pub fn config_path() -> PathBuf {
- let mut path = directories::ProjectDirs::from("", "", "pandora")
- .expect("Could not determine project directory")
- .config_dir()
- .to_path_buf();
- path.push("pandora-config.toml");
- path
- }
- /// Install the commented template config on disk **if it does not exist yet**.
- ///
- /// This writes `CONFIG_TEMPLATE` verbatim so comments are preserved.
- fn write_template_if_missing() -> Result<(), Box<dyn std::error::Error>> {
- let path = Self::config_path();
- if path.exists() {
- // Do not touch an existing user config.
- return Ok(());
- }
- if let Some(parent) = path.parent() {
- fs::create_dir_all(parent)?;
- }
- fs::write(&path, CONFIG_TEMPLATE)?;
- info!("Config template written to: {}", path.display());
- Ok(())
- }
- /// “Save” configuration.
- ///
- /// In this model, we do **not** overwrite the user config (to preserve comments).
- /// `save()` only ensures the template exists on disk on first run.
- pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
- Self::write_template_if_missing()
- }
- pub fn from_path(path: impl AsRef<Path>) -> Self {
- let path = path.as_ref().to_path_buf();
- // First, ensure there is at least a file on disk (template on first run).
- if let Err(e) = Self::write_template_if_missing() {
- warn!(
- "Warning: failed to ensure config template at {}: {}",
- path.display(),
- e
- );
- }
- // Try to load and parse the user config file.
- match fs::read_to_string(&path) {
- Ok(content) => match toml::from_str::<Config>(&content) {
- Ok(cfg) => cfg,
- Err(e) => {
- warn!(
- "Warning: failed to parse user config {}: {}. Falling back to embedded template.",
- path.display(),
- e
- );
- // Fallback: parse the embedded template.
- toml::from_str::<Config>(CONFIG_TEMPLATE)
- .expect("embedded config template is invalid")
- }
- },
- Err(e) => {
- warn!(
- "Warning: failed to read user config {}: {}. Falling back to embedded template.",
- path.display(),
- e
- );
- toml::from_str::<Config>(CONFIG_TEMPLATE)
- .expect("embedded config template is invalid")
- }
- }
- }
- /// Returns `<result_dir>/<id>/<tumoral_name>`.
- #[inline]
- pub fn tumoral_dir(&self, id: &str) -> String {
- format!("{}/{}/{}", self.result_dir, id, self.tumoral_name)
- }
- /// Returns `<result_dir>/<id>/<normal_name>`.
- #[inline]
- pub fn normal_dir(&self, id: &str) -> String {
- format!("{}/{}/{}", self.result_dir, id, self.normal_name)
- }
- /// Returns the directory for a "solo" run (timepoint or tag), i.e. `<result_dir>/<id>/<time>`.
- #[inline]
- pub fn solo_dir(&self, id: &str, time: &str) -> String {
- format!("{}/{}/{}", self.result_dir, id, time)
- }
- /// BAM for a solo run: `<solo_dir>/<id>_<time>_<reference_name>.bam`.
- pub fn solo_bam(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_{}.bam",
- self.solo_dir(id, time),
- id,
- time,
- self.reference_name,
- )
- }
- /// JSON sidecar for the solo BAM.
- pub fn solo_bam_info_json(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_{}_info.json",
- self.solo_dir(id, time),
- id,
- time,
- self.reference_name,
- )
- }
- /// Tumor BAM path: `<tumoral_dir>/<id>_<tumoral_name>_<reference_name>.bam`.
- pub fn tumoral_bam(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_{}.bam",
- self.tumoral_dir(id),
- id,
- self.tumoral_name,
- self.reference_name,
- )
- }
- /// Normal BAM path: `<normal_dir>/<id>_<normal_name>_<reference_name>.bam`.
- pub fn normal_bam(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_{}.bam",
- self.normal_dir(id),
- id,
- self.normal_name,
- self.reference_name,
- )
- }
- /// Tumor haplotagged BAM.
- pub fn solo_haplotagged_bam(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_{}_{}.bam",
- self.solo_dir(id, time),
- id,
- time,
- self.reference_name,
- self.haplotagged_bam_tag_name
- )
- }
- /// Tumor haplotagged BAM.
- pub fn tumoral_haplotagged_bam(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_{}_{}.bam",
- self.tumoral_dir(id),
- id,
- self.tumoral_name,
- self.reference_name,
- self.haplotagged_bam_tag_name
- )
- }
- /// Normal haplotagged BAM.
- pub fn normal_haplotagged_bam(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_{}_{}.bam",
- self.normal_dir(id),
- id,
- self.normal_name,
- self.reference_name,
- self.haplotagged_bam_tag_name
- )
- }
- /// Normal haplotagged BAM.
- pub fn panel_bam(&self, id: &str) -> String {
- format!(
- "{}/panel/{}_panel_{}.bam",
- self.tumoral_dir(id),
- id,
- self.reference_name,
- )
- }
- pub fn dir_count(&self, id: &str, time: &str) -> String {
- format!("{}/{}", self.solo_dir(id, time), self.count_dir_name)
- }
- /// Normal count directory: `<normal_dir>/<count_dir_name>`.
- pub fn normal_dir_count(&self, id: &str) -> String {
- format!("{}/{}", self.normal_dir(id), self.count_dir_name)
- }
- /// Tumor count directory: `<tumoral_dir>/<count_dir_name>`.
- pub fn tumoral_dir_count(&self, id: &str) -> String {
- format!("{}/{}", self.tumoral_dir(id), self.count_dir_name)
- }
- /// Mask BED path with `{result_dir}` and `{id}` expanded.
- pub fn mask_bed(&self, id: &str) -> String {
- self.mask_bed
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// Germline phased VCF with `{result_dir}` and `{id}` expanded.
- pub fn germline_phased_vcf(&self, id: &str) -> String {
- self.germline_phased_vcf
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// Somatic pipeline stats directory with `{result_dir}` and `{id}` expanded.
- pub fn somatic_pipe_stats(&self, id: &str) -> String {
- self.somatic_pipe_stats
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// DeepVariant output directory for a given run (`{result_dir}`, `{id}`, `{time}`).
- pub fn deepvariant_output_dir(&self, id: &str, time: &str) -> String {
- self.deepvariant_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// DeepVariant solo VCF (raw) for `<id>, <time>`.
- pub fn deepvariant_solo_output_vcf(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}",
- self.deepvariant_output_dir(id, time),
- *DEEPVARIANT_OUTPUT_NAME
- )
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// DeepVariant output directory for the normal sample.
- pub fn deepvariant_normal_output_dir(&self, id: &str) -> String {
- self.deepvariant_output_dir(id, &self.normal_name)
- }
- /// DeepVariant "tumoral output dir" (as in your original code – note: this actually returns the *PASSED VCF* path).
- pub fn deepvariant_tumoral_output_dir(&self, id: &str) -> String {
- self.deepvariant_solo_passed_vcf(id, &self.tumoral_name)
- }
- /// DeepVariant solo *PASSED* VCF for `<id>, <time>`.
- pub fn deepvariant_solo_passed_vcf(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_DeepVariant_PASSED.vcf.gz",
- self.deepvariant_output_dir(id, time),
- id,
- time
- )
- }
- /// DeepVariant *PASSED* VCF for the normal sample.
- pub fn deepvariant_normal_passed_vcf(&self, id: &str) -> String {
- self.deepvariant_solo_passed_vcf(id, &self.normal_name)
- }
- /// DeepVariant *PASSED* VCF for the tumor sample.
- pub fn deepvariant_tumoral_passed_vcf(&self, id: &str) -> String {
- self.deepvariant_solo_passed_vcf(id, &self.tumoral_name)
- }
- /// DeepSomatic output directory (uses `{time} = tumoral_name`).
- pub fn deepsomatic_output_dir(&self, id: &str) -> String {
- self.deepsomatic_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", &self.tumoral_name)
- }
- /// DeepSomatic raw VCF.
- pub fn deepsomatic_output_vcf(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_DeepSomatic.vcf.gz",
- self.deepsomatic_output_dir(id),
- id,
- self.tumoral_name
- )
- }
- /// DeepSomatic *PASSED* VCF.
- pub fn deepsomatic_passed_vcf(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_DeepSomatic_PASSED.vcf.gz",
- self.deepsomatic_output_dir(id),
- id,
- self.tumoral_name
- )
- }
- /// ClairS output directory (`{result_dir}`, `{id}`).
- pub fn clairs_output_dir(&self, id: &str) -> String {
- self.clairs_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// ClairS main SNP/indel VCFs (standard + indel-only).
- pub fn clairs_output_vcfs(&self, id: &str) -> (String, String) {
- let dir = self.clairs_output_dir(id);
- (
- format!("{dir}/{}", *CLAIRS_OUTPUT_NAME),
- format!("{dir}/{}", *CLAIRS_OUTPUT_INDELS_NAME),
- )
- }
- /// ClairS somatic *PASSED* VCF.
- pub fn clairs_passed_vcf(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_clairs_PASSED.vcf.gz",
- self.clairs_output_dir(id),
- id,
- self.tumoral_name
- )
- }
- /// ClairS germline normal VCF.
- pub fn clairs_germline_normal_vcf(&self, id: &str) -> String {
- let dir = self.clairs_output_dir(id);
- format!("{dir}/{}", *CLAIRS_GERMLINE_NORMAL)
- }
- /// ClairS germline tumor VCF.
- pub fn clairs_germline_tumor_vcf(&self, id: &str) -> String {
- let dir = self.clairs_output_dir(id);
- format!("{dir}/{}", *CLAIRS_GERMLINE_TUMOR)
- }
- /// Consolidated germline *PASSED* VCF from ClairS.
- pub fn clairs_germline_passed_vcf(&self, id: &str) -> String {
- let dir = self.clairs_output_dir(id);
- format!("{dir}/{id}_diag_clair3-germline_PASSED.vcf.gz")
- }
- /// gatk_output_dir = "{result_dir}/{id}/{tumoral_name}/GATK"
- pub fn gatk_output_dir(&self, id: &str) -> String {
- self.gatk_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{tumoral_name}", &self.tumoral_name)
- }
- /// gatk_passed_vcf = "{output_dir}/{id}_{tumoral_name}_{reference_name}_GATK_PASSED.vcf.gz"
- pub fn gatk_passed_vcf(&self, id: &str) -> String {
- self.gatk_passed_vcf
- .replace("{output_dir}", &self.gatk_output_dir(id))
- .replace("{id}", id)
- .replace("{tumoral_name}", &self.tumoral_name)
- .replace("{reference_name}", &self.reference_name)
- }
- /// Paired nanomonsv output directory.
- pub fn nanomonsv_output_dir(&self, id: &str, time: &str) -> String {
- self.nanomonsv_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// Paired nanomonsv *PASSED* VCF.
- pub fn nanomonsv_passed_vcf(&self, id: &str) -> String {
- self.nanomonsv_passed_vcf
- .replace("{output_dir}", &self.nanomonsv_output_dir(id, "diag"))
- .replace("{id}", id)
- }
- /// Solo nanomonsv output directory.
- pub fn nanomonsv_solo_output_dir(&self, id: &str, time: &str) -> String {
- self.nanomonsv_solo_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// Solo nanomonsv *PASSED* VCF.
- pub fn nanomonsv_solo_passed_vcf(&self, id: &str, time: &str) -> String {
- self.nanomonsv_solo_passed_vcf
- .replace("{output_dir}", &self.nanomonsv_solo_output_dir(id, time))
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// Savana output directory (`{result_dir}`, `{id}`).
- pub fn savana_output_dir(&self, id: &str) -> String {
- self.savana_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// Savana main somatic VCF (classified).
- pub fn savana_output_vcf(&self, id: &str) -> String {
- let output_dir = self.savana_output_dir(id);
- format!(
- "{output_dir}/{id}_{}_{}_{}.classified.somatic.vcf",
- self.tumoral_name, self.reference_name, self.haplotagged_bam_tag_name
- )
- }
- /// Savana *PASSED* VCF.
- pub fn savana_passed_vcf(&self, id: &str) -> String {
- self.savana_passed_vcf
- .replace("{output_dir}", &self.savana_output_dir(id))
- .replace("{id}", id)
- }
- /// Savana read counts file.
- pub fn savana_read_counts(&self, id: &str) -> String {
- self.savana_read_counts
- .replace("{output_dir}", &self.savana_output_dir(id))
- .replace("{id}", id)
- .replace("{reference_name}", &self.reference_name)
- .replace("{haplotagged_bam_tag_name}", &self.haplotagged_bam_tag_name)
- }
- /// Savana copy-number file.
- pub fn savana_copy_number(&self, id: &str) -> String {
- self.savana_copy_number
- .replace("{output_dir}", &self.savana_output_dir(id))
- .replace("{id}", id)
- .replace("{reference_name}", &self.reference_name)
- .replace("{haplotagged_bam_tag_name}", &self.haplotagged_bam_tag_name)
- }
- /// CoRAl
- pub fn coral_output_dir(&self, id: &str) -> String {
- format!(
- "{}/{}/{}/coral/output",
- self.result_dir, id, self.tumoral_name
- )
- }
- pub fn coral_work_dir(&self, id: &str) -> String {
- format!(
- "{}/{}/{}/coral/work",
- self.result_dir, id, self.tumoral_name
- )
- }
- pub fn savana_fitted_purity_ploidy(&self, id: &str) -> String {
- // adjust the glob pattern to match your actual SAVANA output naming
- format!(
- "{}/{}/{}/savana/{}_*_fitted_purity_ploidy.tsv",
- self.result_dir, id, self.tumoral_name, id
- )
- }
- pub fn savana_segmented_absolute_cn(&self, id: &str) -> String {
- format!(
- "{}/{}/{}/savana/{}_*_segmented_absolute_copy_number.tsv",
- self.result_dir, id, self.tumoral_name, id
- )
- }
- /// Severus paired output directory.
- pub fn severus_output_dir(&self, id: &str) -> String {
- self.severus_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// Severus somatic SV VCF (paired).
- pub fn severus_output_vcf(&self, id: &str) -> String {
- let output_dir = self.severus_output_dir(id);
- format!("{output_dir}/somatic_SVs/severus_somatic.vcf")
- }
- /// Severus *PASSED* VCF (paired).
- pub fn severus_passed_vcf(&self, id: &str) -> String {
- format!(
- "{}/{}_diag_severus_PASSED.vcf.gz",
- &self.severus_output_dir(id),
- id
- )
- }
- /// Severus solo output directory.
- pub fn severus_solo_output_dir(&self, id: &str, time: &str) -> String {
- self.severus_solo_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// Severus solo SV VCF.
- pub fn severus_solo_output_vcf(&self, id: &str, time: &str) -> String {
- let output_dir = self.severus_solo_output_dir(id, time);
- format!("{output_dir}/all_SVs/severus_all.vcf")
- }
- /// Severus solo *PASSED* VCF.
- pub fn severus_solo_passed_vcf(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_severus-solo_PASSED.vcf.gz",
- &self.severus_solo_output_dir(id, time),
- id,
- time
- )
- }
- /// Straglr paired output directory.
- pub fn straglr_output_dir(&self, id: &str) -> String {
- self.straglr_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- }
- /// Straglr normal sample TSV output.
- pub fn straglr_normal_tsv(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_straglr.tsv",
- self.straglr_solo_output_dir(id, &self.normal_name),
- id,
- self.normal_name
- )
- }
- /// Straglr tumor sample TSV output.
- pub fn straglr_tumor_tsv(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_straglr.tsv",
- self.straglr_output_dir(id),
- id,
- self.tumoral_name
- )
- }
- /// Straglr tumor sample TSV output.
- pub fn straglr_tumor_normal_diff_tsv(&self, id: &str) -> String {
- format!(
- "{}/{}_{}_straglr_diff.tsv",
- self.straglr_output_dir(id),
- id,
- self.tumoral_name
- )
- }
- /// Straglr solo output directory.
- pub fn straglr_solo_output_dir(&self, id: &str, time: &str) -> String {
- self.straglr_solo_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// Straglr solo TSV output.
- pub fn straglr_solo_tsv(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_straglr.tsv",
- self.straglr_solo_output_dir(id, time),
- id,
- time
- )
- }
- /// Alias for the constitutional germline VCF.
- pub fn constit_vcf(&self, id: &str) -> String {
- self.clairs_germline_passed_vcf(id)
- }
- /// Somatic-scan output directory for a solo run (counts subdir).
- pub fn somatic_scan_solo_output_dir(&self, id: &str, time: &str) -> String {
- format!("{}/counts", self.solo_dir(id, time))
- }
- /// Somatic-scan output dir for the normal sample.
- pub fn somatic_scan_normal_output_dir(&self, id: &str) -> String {
- self.somatic_scan_solo_output_dir(id, &self.normal_name)
- }
- /// Somatic-scan output dir for the tumor sample.
- pub fn somatic_scan_tumoral_output_dir(&self, id: &str) -> String {
- self.somatic_scan_solo_output_dir(id, &self.tumoral_name)
- }
- /// Somatic-scan count file for a given contig in a solo run.
- pub fn somatic_scan_solo_count_file(&self, id: &str, time: &str, contig: &str) -> String {
- format!(
- "{}/{}_count.tsv.gz",
- self.somatic_scan_solo_output_dir(id, time),
- contig
- )
- }
- /// Somatic-scan count file (normal) for a given contig.
- pub fn somatic_scan_normal_count_file(&self, id: &str, contig: &str) -> String {
- self.somatic_scan_solo_count_file(id, &self.normal_name, contig)
- }
- /// Somatic-scan count file (tumor) for a given contig.
- pub fn somatic_scan_tumoral_count_file(&self, id: &str, contig: &str) -> String {
- self.somatic_scan_solo_count_file(id, &self.tumoral_name, contig)
- }
- /// Modkit summary file (`{result_dir}`, `{id}`, `{time}`).
- pub fn modkit_summary_file(&self, id: &str, time: &str) -> String {
- self.modkit_summary_file
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// Longphase modcall VCF (`{result_dir}`, `{id}`, `{time}`).
- pub fn longphase_modcall_vcf(&self, id: &str, time: &str) -> String {
- self.longphase_modcall_vcf
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// longcallD output directory (`{result_dir}`, `{id}`, `{time}`).
- pub fn longcalld_output_dir(&self, id: &str, time: &str) -> String {
- self.longcalld_output_dir
- .replace("{result_dir}", &self.result_dir)
- .replace("{id}", id)
- .replace("{time}", time)
- }
- /// longcallD solo *PASSED* VCF for `<id>, <time>`.
- pub fn longcalld_solo_passed_vcf(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_longcallD_PASSED.vcf.gz",
- self.longcalld_output_dir(id, time),
- id,
- time
- )
- }
- pub fn longcalld_solo_bam(&self, id: &str, time: &str) -> String {
- format!(
- "{}/{}_{}_{}_{}_realn.bam",
- self.solo_dir(id, time),
- id,
- time,
- self.reference_name,
- self.haplotagged_bam_tag_name
- )
- }
- }
- impl Default for Config {
- fn default() -> Self {
- let path = Self::config_path();
- Self::from_path(path)
- }
- }
|