diff --git a/subworkflows/local/paired/paired_smallvar_germline.nf b/subworkflows/local/paired/paired_smallvar_germline.nf index f1dc9cb7..453f08ff 100644 --- a/subworkflows/local/paired/paired_smallvar_germline.nf +++ b/subworkflows/local/paired/paired_smallvar_germline.nf @@ -44,8 +44,7 @@ workflow PAIRED_SMALLVAR_GERMLINE { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, meta.clair3_model, bam, bai ] } .set { normal_bams_model } @@ -112,8 +111,7 @@ workflow PAIRED_SMALLVAR_GERMLINE { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') def intervals = [] return [new_meta, bam, bai, intervals] } @@ -188,8 +186,7 @@ workflow PAIRED_SMALLVAR_GERMLINE { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return[new_meta, vcf, tbi] } .set{germline_vcf} diff --git a/subworkflows/local/paired/paired_smallvar_somatic.nf b/subworkflows/local/paired/paired_smallvar_somatic.nf index 7bcf5258..8a74033e 100644 --- a/subworkflows/local/paired/paired_smallvar_somatic.nf +++ b/subworkflows/local/paired/paired_smallvar_somatic.nf @@ -176,8 +176,7 @@ workflow PAIRED_SMALLVAR_SOMATIC { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return[new_meta, vcf, tbi] } .set{somatic_vcf} diff --git a/subworkflows/local/phasing_haplotyping/main.nf b/subworkflows/local/phasing_haplotyping/main.nf index f974e510..5a85d7ad 100644 --- a/subworkflows/local/phasing_haplotyping/main.nf +++ b/subworkflows/local/phasing_haplotyping/main.nf @@ -53,8 +53,7 @@ workflow PHASING_HAPLOTYPING { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, bam, bai ] } .set{ tumor_only_ch } @@ -80,8 +79,7 @@ workflow PHASING_HAPLOTYPING { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, bam, bai ] } .set{ paired_normal_ch } @@ -96,8 +94,7 @@ workflow PHASING_HAPLOTYPING { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, bam, bai ] } .set{ paired_tumor_ch } @@ -353,8 +350,7 @@ workflow PHASING_HAPLOTYPING { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, mods ] } .set{modcall_vcf_ch} diff --git a/subworkflows/local/small_variant_consensus/main.nf b/subworkflows/local/small_variant_consensus/main.nf index 83b7a712..bdf265f7 100644 --- a/subworkflows/local/small_variant_consensus/main.nf +++ b/subworkflows/local/small_variant_consensus/main.nf @@ -135,8 +135,7 @@ workflow SMALL_VARIANT_CONSENSUS { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, vcfs, tbi] } .set{clair_ch} @@ -152,8 +151,7 @@ workflow SMALL_VARIANT_CONSENSUS { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return [ new_meta, vcfs, tbi] } .set{deepvariant_ch} diff --git a/subworkflows/local/tumor_only/tumoronly_smallvar.nf b/subworkflows/local/tumor_only/tumoronly_smallvar.nf index b69d5a40..8f1b9931 100644 --- a/subworkflows/local/tumor_only/tumoronly_smallvar.nf +++ b/subworkflows/local/tumor_only/tumoronly_smallvar.nf @@ -360,8 +360,7 @@ workflow TUMORONLY_SMALLVAR { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return[new_meta, vcf, tbi] } .set{somatic_vcf} @@ -375,8 +374,7 @@ workflow TUMORONLY_SMALLVAR { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return[new_meta, vcf, tbi] } .set{germline_vcf} diff --git a/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf b/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf index 95744073..a36a509b 100644 --- a/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf @@ -464,6 +464,21 @@ def validateInputSamplesheet(input) { return [ metas[0], bams ] } + +// +// Caller models key the tumor/normal and replicate joins, so all BAMs of a sample must agree on them +// +def validateSampleModels(metas) { + def model_keys = ['clair3_model', 'clairS_model', 'clairSTO_model'] + metas.groupBy { meta -> meta.id }.each { id, rows -> + def differing = model_keys.findAll { key -> rows.collect { meta -> meta[key] }.unique().size() > 1 } + if (differing) { + def detail = differing.collect { key -> "${key}: ${rows.collect { meta -> "${meta.type}=${meta[key]}" }.join(', ')}" }.join('; ') + error("Sample '${id}': its BAMs resolve to different caller models (${detail}). Set ${differing.join(', ')} explicitly in the samplesheet for every row of this sample.") + } + } + return true +} // // Get attribute from genome config file e.g. fasta // diff --git a/subworkflows/local/utils_nfcore_lrsomatic_pipeline/tests/main.function.nf.test b/subworkflows/local/utils_nfcore_lrsomatic_pipeline/tests/main.function.nf.test index 0f21d9b5..126d7228 100644 --- a/subworkflows/local/utils_nfcore_lrsomatic_pipeline/tests/main.function.nf.test +++ b/subworkflows/local/utils_nfcore_lrsomatic_pipeline/tests/main.function.nf.test @@ -985,6 +985,55 @@ nextflow_function { } } + // --- validateSampleModels: a sample's BAMs must agree on caller models, or the pairing joins drop it + + test("validateSampleModels accepts a pair that differs only in kinetics") { + + function "validateSampleModels" + + when { + function { + """ + input[0] = [ + [id: 's1', type: 'tumor', clair3_model: 'hifi', clairS_model: 'hifi_revio_ssrs', clairSTO_model: 'hifi_revio_ssrs', kinetics: 'false'], + [id: 's1', type: 'normal', clair3_model: 'hifi', clairS_model: 'hifi_revio_ssrs', clairSTO_model: 'hifi_revio_ssrs', kinetics: 'true'] + ] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result == true } + ) + } + } + + test("validateSampleModels stops on a tumor/normal clair3_model mismatch") { + + function "validateSampleModels" + + when { + function { + """ + input[0] = [ + [id: 's1', type: 'tumor', clair3_model: 'r1041_e82_400bps_sup_v500', clairS_model: 'ont_r10_dorado_sup_5khz', clairSTO_model: 'ont_r10_dorado_sup_5khz'], + [id: 's1', type: 'normal', clair3_model: 'r1041_e82_400bps_sup_v430', clairS_model: 'ont_r10_dorado_sup_5khz', clairSTO_model: 'ont_r10_dorado_sup_5khz'], + [id: 's2', type: 'tumor', clair3_model: 'hifi', clairS_model: 'hifi_revio_ssrs', clairSTO_model: 'hifi_revio_ssrs'] + ] + """ + } + } + + then { + assertAll( + { assert function.failed }, + { assert function.stdout.any { line -> line.contains("Sample 's1'") && line.contains('clair3_model') } } + ) + } + } + // --- validateCondaSupport: container-only steps fail at launch under conda test("validateCondaSupport names every container-only step left on under conda") { diff --git a/workflows/lrsomatic.nf b/workflows/lrsomatic.nf index e6f56c9e..fd2eb573 100644 --- a/workflows/lrsomatic.nf +++ b/workflows/lrsomatic.nf @@ -14,7 +14,7 @@ include { reportGenePanelTokens } from '../subworkflows/local/utils_nfcore_lrso include { reportGenePanelIsFile } from '../subworkflows/local/utils_nfcore_lrsomatic_pipeline' include { resolveVepPlugins; validateVepPluginParams } from '../subworkflows/local/utils_nfcore_lrsomatic_pipeline' include { padfootGenome; padfootAnnotationOk } from '../subworkflows/local/utils_nfcore_lrsomatic_pipeline' -include { validateClairstoCnaResources } from '../subworkflows/local/utils_nfcore_lrsomatic_pipeline' +include { validateClairstoCnaResources; validateSampleModels } from '../subworkflows/local/utils_nfcore_lrsomatic_pipeline' include { PREPARE_VEP_PLUGINS } from '../subworkflows/local/prepare_vep_plugins' // @@ -295,6 +295,12 @@ workflow LRSOMATIC { // clair3_model, clairS_model, clairSTO_model, kinetics // bams are grouped per sample (multiple runs merged into a list) + // Fail fast if a sample's BAMs resolve to different caller models; they key the pairing joins + ch_samplesheet + .map { meta, _bam -> meta } + .collect() + .map { metas -> validateSampleModels(metas) } + // // SUBWORKFLOW: PREPARE_REFERENCE_FILES -- decompress and index the FASTA, fetch Clair3 models, unpack ASCAT references // Input: params.fasta, ASCAT file paths, basecall_meta, clair3_modelMap @@ -442,7 +448,6 @@ workflow LRSOMATIC { 'clair3_model', 'clairS_model', 'clairSTO_model', - 'kinetics', 'n_replicates') // groupKey: release each sample as soon as its own replicates arrive, not when all samples have return [groupKey(new_meta, new_meta.n_replicates), bam, bai] @@ -526,8 +531,7 @@ workflow LRSOMATIC { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return[new_meta, bam, bai] } .set{paired_normal_bams} @@ -543,11 +547,11 @@ workflow LRSOMATIC { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') return[new_meta, bam, bai] } - .join(paired_normal_bams) + // failOnMismatch: a tumor/normal meta drift must stop the run, not drop the pair + .join(paired_normal_bams, failOnMismatch: true) .set { somatic_smallvar_input } // somatic_smallvar_input: [meta, tumor_bam, tumor_bai, normal_bam, normal_bai] @@ -574,8 +578,7 @@ workflow LRSOMATIC { 'fiber', 'clair3_model', 'clairS_model', - 'clairSTO_model', - 'kinetics') + 'clairSTO_model') def normal_bam = [] def normal_bai = [] return [new_meta, normal_bam, normal_bai, bam, bai] @@ -900,7 +903,7 @@ workflow LRSOMATIC { // Severus, Wakhan and SAVANA read the HP (haplotype) tag per read, so build it from // PHASING_HAPLOTYPING's haplotagged BAMs rather than the unphased minimap2 BAMs. def sv_meta_keys = ['id', 'paired_data', 'platform', 'sex', 'fiber', - 'clair3_model', 'clairS_model', 'clairSTO_model', 'kinetics'] + 'clair3_model', 'clairS_model', 'clairSTO_model'] PHASING_HAPLOTYPING.out.tumor_normal_hapbams_ch .branch { meta, _bam, _bai ->