diff --git a/.github/skip_nf_test.json b/.github/skip_nf_test.json index 633dc3e7..4fc5cdae 100644 --- a/.github/skip_nf_test.json +++ b/.github/skip_nf_test.json @@ -5,6 +5,11 @@ "modules/msk/facets", "modules/msk/fgbio/collectduplexseqmetrics", "modules/msk/gbcms", + "modules/msk/gbcmsrs/dna", + "modules/msk/gbcmsrs/rna", + "modules/msk/gbcmsrs/normalize", + "modules/msk/gbcmsrs/merge", + "modules/msk/gbcmsrs/buildgtfcache", "modules/msk/neoantigenediting/computefitness", "modules/msk/neoantigenediting/aligntoiedb", "modules/msk/neoantigenutils/neoantigeninput", diff --git a/modules/msk/gbcmsrs/buildgtfcache/environment.yml b/modules/msk/gbcmsrs/buildgtfcache/environment.yml new file mode 100644 index 00000000..4c59b932 --- /dev/null +++ b/modules/msk/gbcmsrs/buildgtfcache/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "YOUR-TOOL=HERE" diff --git a/modules/msk/gbcmsrs/buildgtfcache/main.nf b/modules/msk/gbcmsrs/buildgtfcache/main.nf new file mode 100644 index 00000000..031b9905 --- /dev/null +++ b/modules/msk/gbcmsrs/buildgtfcache/main.nf @@ -0,0 +1,48 @@ +process GBCMSRS_BUILDGTFCACHE { + tag "${variants.name}" + label 'process_single' + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1': + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1' }" + + input: + path variants + path gtf + + output: + path "gbcms_gtf_cache", emit: cache_dir + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "GBCMSRS_BUILDGTFCACHE module does not support Conda. Please use Docker / Singularity instead." + } + def args = task.ext.args ?: '' + """ + mkdir -p gbcms_gtf_cache + gbcms build-gtf-cache \\ + --gtf ${gtf} \\ + --variants ${variants} \\ + --gtf-cache-dir gbcms_gtf_cache \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(gbcms --version | sed 's/^gbcms //') + END_VERSIONS + """ + + stub: + """ + mkdir -p gbcms_gtf_cache + touch gbcms_gtf_cache/gbcms-gtf-stub.idx + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(echo "${task.container}" | sed 's/.*://') + END_VERSIONS + """ +} diff --git a/modules/msk/gbcmsrs/buildgtfcache/meta.yml b/modules/msk/gbcmsrs/buildgtfcache/meta.yml new file mode 100644 index 00000000..e126d31e --- /dev/null +++ b/modules/msk/gbcmsrs/buildgtfcache/meta.yml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "gbcmsrs_buildgtfcache" +description: + Pre-build the gbcms GTF index cache once for a cohort so that per-sample + `gbcms rna` runs skip re-parsing the GTF +keywords: + - cache + - gtf + - rna + - index +tools: + - "gbcms": + description: + "A high-performance, orientation-aware genotype counting system for + genomic variants (Rust rewrite of GetBaseCountsMultiSample)" + homepage: "https://github.com/msk-access/gbcms" + documentation: "https://msk-access.github.io/gbcms/" + tool_dev_url: "https://github.com/msk-access/gbcms" + licence: ["AGPL-3.0"] + identifier: "" + +input: + - variants: + type: file + description: + Variant file (VCF/MAF) for the cohort. Only its chromosome set is + used, and it must be the same variant file the per-sample `gbcms rna` runs use + so the cache key lines up. + pattern: "*.{vcf,maf}" + ontologies: [] + - gtf: + type: file + description: GTF annotation file (Ensembl/GENCODE) + pattern: "*.gtf" + ontologies: [] +output: + cache_dir: + - gbcms_gtf_cache: + type: directory + description: + Directory containing the serialized GTF index cache. Point every + per-sample `gbcms rna --gtf-cache-dir` at this same directory. + pattern: "gbcms_gtf_cache" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@shahr" +maintainers: + - "@shahr" diff --git a/modules/msk/gbcmsrs/buildgtfcache/tests/main.nf.test b/modules/msk/gbcmsrs/buildgtfcache/tests/main.nf.test new file mode 100644 index 00000000..e80d3432 --- /dev/null +++ b/modules/msk/gbcmsrs/buildgtfcache/tests/main.nf.test @@ -0,0 +1,35 @@ +// nf-core modules test gbcmsrs/buildgtfcache +nextflow_process { + + name "Test Process GBCMSRS_BUILDGTFCACHE" + script "../main.nf" + process "GBCMSRS_BUILDGTFCACHE" + + tag "modules" + tag "modules_msk" + tag "gbcmsrs" + tag "gbcmsrs/buildgtfcache" + + test("sarscov2 illumina - vcf gtf") { + + when { + process { + """ + input[0] = file(params.test_data['sarscov2']['illumina']['test_vcf'], checkIfExists: true) + input[1] = file(params.test_data['sarscov2']['genome']['genome_gtf'], checkIfExists: true) + """ + } + } + + then { + def idx_files = file(process.out.cache_dir[0]).listFiles().findAll { it.name.endsWith('.idx') } + assertAll( + { assert process.success }, + { assert idx_files.size() == 1 }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + +} diff --git a/modules/msk/gbcmsrs/buildgtfcache/tests/main.nf.test.snap b/modules/msk/gbcmsrs/buildgtfcache/tests/main.nf.test.snap new file mode 100644 index 00000000..6abdc6ed --- /dev/null +++ b/modules/msk/gbcmsrs/buildgtfcache/tests/main.nf.test.snap @@ -0,0 +1,14 @@ +{ + "sarscov2 illumina - vcf gtf": { + "content": [ + [ + "versions.yml:md5,9d75c19a947df6e008a3918a2e97ebc6" + ] + ], + "timestamp": "2026-08-27T10:14:42.910180254", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/msk/gbcmsrs/buildgtfcache/tests/tags.yml b/modules/msk/gbcmsrs/buildgtfcache/tests/tags.yml new file mode 100644 index 00000000..1d894761 --- /dev/null +++ b/modules/msk/gbcmsrs/buildgtfcache/tests/tags.yml @@ -0,0 +1,2 @@ +gbcmsrs/buildgtfcache: + - "modules/msk/gbcmsrs/buildgtfcache/**" diff --git a/modules/msk/gbcmsrs/dna/environment.yml b/modules/msk/gbcmsrs/dna/environment.yml new file mode 100644 index 00000000..4c59b932 --- /dev/null +++ b/modules/msk/gbcmsrs/dna/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "YOUR-TOOL=HERE" diff --git a/modules/msk/gbcmsrs/dna/main.nf b/modules/msk/gbcmsrs/dna/main.nf new file mode 100644 index 00000000..f28ae070 --- /dev/null +++ b/modules/msk/gbcmsrs/dna/main.nf @@ -0,0 +1,61 @@ +process GBCMSRS_DNA { + tag "$meta.id" + label 'process_medium' + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1': + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1' }" + + input: + tuple val(meta), path(variants), val(sample_names), path(bams), path(bais) + path fasta + path fasta_fai + + output: + tuple val(meta), path("gbcms_out/*.{vcf,maf}"), emit: variant_file + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "GBCMSRS_DNA module does not support Conda. Please use Docker / Singularity instead." + } + // gbcms's read filters (--filter-duplicates/-secondary/-supplementary/-qc-failed) + // default to ON. A pipeline mapping its own boolean params into ext.args must emit + // the explicit --no-filter-x form when off; omitting the flag silently keeps it on. + // On Nextflow >=26.04 (strict parser), CLI param overrides arrive as Strings, so + // `params.x ? 'a' : 'b'` sees "false" as truthy — compare with `.toString() == 'true'`. + def args = task.ext.args ?: '' + // Bare `--bam path` labels the sample using the staged file's stem, which is not + // meaningful for real BAM naming conventions. Pairing each bam with an explicit + // name keeps the output filename and Tumor_Sample_Barcode/VCF sample column + // predictable and equal to what the caller intends. + def bam_args = [sample_names, bams].transpose().collect { name, bam -> "--bam ${name}:${bam}" }.join(' ') + """ + gbcms dna \\ + --variants ${variants} \\ + ${bam_args} \\ + --fasta ${fasta} \\ + --output-dir gbcms_out \\ + --threads ${task.cpus} \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(gbcms --version | sed 's/^gbcms //') + END_VERSIONS + """ + + stub: + def sample_name = sample_names[0] + """ + mkdir -p gbcms_out + touch gbcms_out/${sample_name}.vcf + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(echo "${task.container}" | sed 's/.*://') + END_VERSIONS + """ +} diff --git a/modules/msk/gbcmsrs/dna/meta.yml b/modules/msk/gbcmsrs/dna/meta.yml new file mode 100644 index 00000000..3299b130 --- /dev/null +++ b/modules/msk/gbcmsrs/dna/meta.yml @@ -0,0 +1,94 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "gbcmsrs_dna" +description: | + Count alleles in cfDNA/somatic DNA BAMs at known variant sites using gbcms. + + Note: gbcms's read filters (--filter-duplicates/-secondary/-supplementary/-qc-failed) + default to ON. A pipeline that maps its own boolean params into ext.args must emit the + explicit --no-filter-x form when off, or the filter silently stays on since omitting + the flag just falls back to the CLI default. On Nextflow >=26.04 (strict parser), CLI + param overrides arrive as Strings, so `params.x ? 'a' : 'b'` sees "false" as truthy. + Example ext.args mapping: + + ext.args = { [ + params.filter_qc_failed.toString() == 'true' ? '--filter-qc-failed' : '--no-filter-qc-failed', + ].join(' ') } +keywords: + - basecount + - genotyping + - dna + - variants +tools: + - "gbcms": + description: + "A high-performance, orientation-aware genotype counting system for + genomic variants (Rust rewrite of GetBaseCountsMultiSample)" + homepage: "https://github.com/msk-access/gbcms" + documentation: "https://msk-access.github.io/gbcms/" + tool_dev_url: "https://github.com/msk-access/gbcms" + licence: ["AGPL-3.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - variants: + type: file + description: Input variant file in VCF or MAF format + pattern: "*.{vcf,maf}" + ontologies: [] + - sample_names: + type: string + description: + List of sample names, parallel to `bams`, used to label each BAM + via `--bam :`. Without an explicit name, gbcms falls + back to the staged file's stem, which is usually not the desired + sample id. + - bams: + type: file + description: One or more indexed BAM/CRAM files to count alleles in + pattern: "*.{bam,cram}" + ontologies: [] + - bais: + type: file + description: Index file(s) for the input BAM/CRAM file(s) + pattern: "*.{bai,crai}" + ontologies: [] + - fasta: + type: file + description: Reference genome FASTA file + pattern: "*.{fasta,fa}" + ontologies: [] + - fasta_fai: + type: file + description: Index of the reference genome FASTA file + pattern: "*.fai" + ontologies: [] +output: + variant_file: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "gbcms_out/*.{vcf,maf}": + type: file + description: + Variant file annotated with allele counts, strand information, + and statistical annotations + pattern: "gbcms_out/*.{vcf,maf}" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@shahr" +maintainers: + - "@buehlere" + - "@shahr" diff --git a/modules/msk/gbcmsrs/dna/tests/main.nf.test b/modules/msk/gbcmsrs/dna/tests/main.nf.test new file mode 100644 index 00000000..97b481df --- /dev/null +++ b/modules/msk/gbcmsrs/dna/tests/main.nf.test @@ -0,0 +1,40 @@ +// nf-core modules test gbcmsrs/dna +nextflow_process { + + name "Test Process GBCMSRS_DNA" + script "../main.nf" + process "GBCMSRS_DNA" + + tag "modules" + tag "modules_msk" + tag "gbcmsrs" + tag "gbcmsrs/dna" + + test("sarscov2 illumina - vcf") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.test_data['sarscov2']['illumina']['test_vcf'], checkIfExists: true), + [ 'test' ], + [ file(params.test_data['sarscov2']['illumina']['test_single_end_sorted_bam'], checkIfExists: true) ], + [ file(params.test_data['sarscov2']['illumina']['test_single_end_sorted_bam_bai'], checkIfExists: true) ] + ] + input[1] = file(params.test_data['sarscov2']['genome']['genome_fasta'], checkIfExists: true) + input[2] = file(params.test_data['sarscov2']['genome']['genome_fasta_fai'], checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/msk/gbcmsrs/dna/tests/main.nf.test.snap b/modules/msk/gbcmsrs/dna/tests/main.nf.test.snap new file mode 100644 index 00000000..fc85fa09 --- /dev/null +++ b/modules/msk/gbcmsrs/dna/tests/main.nf.test.snap @@ -0,0 +1,35 @@ +{ + "sarscov2 illumina - vcf": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.vcf:md5,6263b41090085c08a45c28b2c1010836" + ] + ], + "1": [ + "versions.yml:md5,4bf00939eda0ee2c812c22755a0ebb19" + ], + "variant_file": [ + [ + { + "id": "test" + }, + "test.vcf:md5,6263b41090085c08a45c28b2c1010836" + ] + ], + "versions": [ + "versions.yml:md5,4bf00939eda0ee2c812c22755a0ebb19" + ] + } + ], + "timestamp": "2026-08-27T10:14:53.601840015", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/msk/gbcmsrs/dna/tests/tags.yml b/modules/msk/gbcmsrs/dna/tests/tags.yml new file mode 100644 index 00000000..67dab5fb --- /dev/null +++ b/modules/msk/gbcmsrs/dna/tests/tags.yml @@ -0,0 +1,2 @@ +gbcmsrs/dna: + - "modules/msk/gbcmsrs/dna/**" diff --git a/modules/msk/gbcmsrs/merge/environment.yml b/modules/msk/gbcmsrs/merge/environment.yml new file mode 100644 index 00000000..4c59b932 --- /dev/null +++ b/modules/msk/gbcmsrs/merge/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "YOUR-TOOL=HERE" diff --git a/modules/msk/gbcmsrs/merge/main.nf b/modules/msk/gbcmsrs/merge/main.nf new file mode 100644 index 00000000..4ca2a82d --- /dev/null +++ b/modules/msk/gbcmsrs/merge/main.nf @@ -0,0 +1,47 @@ +process GBCMSRS_MERGE { + tag "$meta.id" + label 'process_single' + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1': + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1' }" + + input: + tuple val(meta), val(types), path(mafs) + + output: + tuple val(meta), path("*.merged.maf"), emit: merged + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "GBCMSRS_MERGE module does not support Conda. Please use Docker / Singularity instead." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def input_args = [types, mafs].transpose().collect { type, maf -> "--input ${type}:${maf}" }.join(' ') + """ + gbcms merge \\ + ${input_args} \\ + --output ${prefix}.merged.maf \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(gbcms --version | sed 's/^gbcms //') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.merged.maf + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(echo "${task.container}" | sed 's/.*://') + END_VERSIONS + """ +} diff --git a/modules/msk/gbcmsrs/merge/meta.yml b/modules/msk/gbcmsrs/merge/meta.yml new file mode 100644 index 00000000..6e01e997 --- /dev/null +++ b/modules/msk/gbcmsrs/merge/meta.yml @@ -0,0 +1,66 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "gbcmsrs_merge" +description: + Merge per-BAM-type genotyped MAFs (e.g. duplex/simplex) produced by gbcms + into a single type-prefixed output +keywords: + - merge + - genotyping + - maf + - duplex +tools: + - "gbcms": + description: + "A high-performance, orientation-aware genotype counting system for + genomic variants (Rust rewrite of GetBaseCountsMultiSample)" + homepage: "https://github.com/msk-access/gbcms" + documentation: "https://msk-access.github.io/gbcms/" + tool_dev_url: "https://github.com/msk-access/gbcms" + licence: ["AGPL-3.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - types: + type: string + description: + List of BAM-type labels (e.g. `['duplex', 'simplex']`), parallel + to `mafs`, used to prefix the count columns of each input MAF. At least 2 + required. + - mafs: + type: file + description: + List of genotyped MAF files to merge, parallel to `types`. At least + 2 required. + pattern: "*.maf" + ontologies: [] +output: + merged: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*.merged.maf": + type: file + description: + Outer-joined MAF with type-prefixed count columns from all input + MAFs + pattern: "*.merged.maf" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@shahr" +maintainers: + - "@buehlere" + - "@shahr" diff --git a/modules/msk/gbcmsrs/merge/tests/main.nf.test b/modules/msk/gbcmsrs/merge/tests/main.nf.test new file mode 100644 index 00000000..d936b6aa --- /dev/null +++ b/modules/msk/gbcmsrs/merge/tests/main.nf.test @@ -0,0 +1,39 @@ +// nf-core modules test gbcmsrs/merge +nextflow_process { + + name "Test Process GBCMSRS_MERGE" + script "../main.nf" + process "GBCMSRS_MERGE" + + tag "modules" + tag "modules_msk" + tag "gbcmsrs" + tag "gbcmsrs/merge" + + test("duplex simplex maf") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + [ 'duplex', 'simplex' ], + [ + file(params.test_data_mskcc['gbcmsrs']['mfsd_single_maf'], checkIfExists: true), + file(params.test_data_mskcc['gbcmsrs']['mfsd_multi_maf'], checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/msk/gbcmsrs/merge/tests/main.nf.test.snap b/modules/msk/gbcmsrs/merge/tests/main.nf.test.snap new file mode 100644 index 00000000..4d05a992 --- /dev/null +++ b/modules/msk/gbcmsrs/merge/tests/main.nf.test.snap @@ -0,0 +1,35 @@ +{ + "duplex simplex maf": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.merged.maf:md5,81bbe12fb5511ce9c66d3375b9ed71dc" + ] + ], + "1": [ + "versions.yml:md5,d11192a5fe8053ad55df36e0d666e768" + ], + "merged": [ + [ + { + "id": "test" + }, + "test.merged.maf:md5,81bbe12fb5511ce9c66d3375b9ed71dc" + ] + ], + "versions": [ + "versions.yml:md5,d11192a5fe8053ad55df36e0d666e768" + ] + } + ], + "timestamp": "2026-08-27T10:15:04.577509879", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/msk/gbcmsrs/merge/tests/tags.yml b/modules/msk/gbcmsrs/merge/tests/tags.yml new file mode 100644 index 00000000..431bbc82 --- /dev/null +++ b/modules/msk/gbcmsrs/merge/tests/tags.yml @@ -0,0 +1,2 @@ +gbcmsrs/merge: + - "modules/msk/gbcmsrs/merge/**" diff --git a/modules/msk/gbcmsrs/normalize/environment.yml b/modules/msk/gbcmsrs/normalize/environment.yml new file mode 100644 index 00000000..4c59b932 --- /dev/null +++ b/modules/msk/gbcmsrs/normalize/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "YOUR-TOOL=HERE" diff --git a/modules/msk/gbcmsrs/normalize/main.nf b/modules/msk/gbcmsrs/normalize/main.nf new file mode 100644 index 00000000..b074613c --- /dev/null +++ b/modules/msk/gbcmsrs/normalize/main.nf @@ -0,0 +1,50 @@ +process GBCMSRS_NORMALIZE { + tag "$meta.id" + label 'process_single' + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1': + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1' }" + + input: + tuple val(meta), path(variants) + path fasta + path fasta_fai + + output: + tuple val(meta), path("*.normalized.tsv"), emit: normalized + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "GBCMSRS_NORMALIZE module does not support Conda. Please use Docker / Singularity instead." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + gbcms normalize \\ + --variants ${variants} \\ + --fasta ${fasta} \\ + --output ${prefix}.normalized.tsv \\ + --threads ${task.cpus} \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(gbcms --version | sed 's/^gbcms //') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.normalized.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(echo "${task.container}" | sed 's/.*://') + END_VERSIONS + """ +} diff --git a/modules/msk/gbcmsrs/normalize/meta.yml b/modules/msk/gbcmsrs/normalize/meta.yml new file mode 100644 index 00000000..c13a0c22 --- /dev/null +++ b/modules/msk/gbcmsrs/normalize/meta.yml @@ -0,0 +1,67 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "gbcmsrs_normalize" +description: + Normalize variants (left-align and validate REF against the reference + genome) without counting, using gbcms +keywords: + - normalize + - variants + - vcf + - maf +tools: + - "gbcms": + description: + "A high-performance, orientation-aware genotype counting system for + genomic variants (Rust rewrite of GetBaseCountsMultiSample)" + homepage: "https://github.com/msk-access/gbcms" + documentation: "https://msk-access.github.io/gbcms/" + tool_dev_url: "https://github.com/msk-access/gbcms" + licence: ["AGPL-3.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - variants: + type: file + description: Input variant file in VCF or MAF format + pattern: "*.{vcf,maf}" + ontologies: [] + - fasta: + type: file + description: Reference genome FASTA file + pattern: "*.{fasta,fa}" + ontologies: [] + - fasta_fai: + type: file + description: Index of the reference genome FASTA file + pattern: "*.fai" + ontologies: [] +output: + normalized: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*.normalized.tsv": + type: file + description: TSV file with original and left-aligned/normalized variant coordinates + pattern: "*.normalized.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@shahr" +maintainers: + - "@buehlere" + - "@shahr" diff --git a/modules/msk/gbcmsrs/normalize/tests/main.nf.test b/modules/msk/gbcmsrs/normalize/tests/main.nf.test new file mode 100644 index 00000000..4afcef91 --- /dev/null +++ b/modules/msk/gbcmsrs/normalize/tests/main.nf.test @@ -0,0 +1,37 @@ +// nf-core modules test gbcmsrs/normalize +nextflow_process { + + name "Test Process GBCMSRS_NORMALIZE" + script "../main.nf" + process "GBCMSRS_NORMALIZE" + + tag "modules" + tag "modules_msk" + tag "gbcmsrs" + tag "gbcmsrs/normalize" + + test("sarscov2 illumina - vcf") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.test_data['sarscov2']['illumina']['test_vcf'], checkIfExists: true) + ] + input[1] = file(params.test_data['sarscov2']['genome']['genome_fasta'], checkIfExists: true) + input[2] = file(params.test_data['sarscov2']['genome']['genome_fasta_fai'], checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/msk/gbcmsrs/normalize/tests/main.nf.test.snap b/modules/msk/gbcmsrs/normalize/tests/main.nf.test.snap new file mode 100644 index 00000000..2568fcc7 --- /dev/null +++ b/modules/msk/gbcmsrs/normalize/tests/main.nf.test.snap @@ -0,0 +1,35 @@ +{ + "sarscov2 illumina - vcf": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.normalized.tsv:md5,292c3a20d32eadc93f4402a99b8d409b" + ] + ], + "1": [ + "versions.yml:md5,1b32163099847e6066c17372a3a1ede5" + ], + "normalized": [ + [ + { + "id": "test" + }, + "test.normalized.tsv:md5,292c3a20d32eadc93f4402a99b8d409b" + ] + ], + "versions": [ + "versions.yml:md5,1b32163099847e6066c17372a3a1ede5" + ] + } + ], + "timestamp": "2026-08-27T10:15:15.124502571", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/msk/gbcmsrs/normalize/tests/tags.yml b/modules/msk/gbcmsrs/normalize/tests/tags.yml new file mode 100644 index 00000000..fe68d270 --- /dev/null +++ b/modules/msk/gbcmsrs/normalize/tests/tags.yml @@ -0,0 +1,2 @@ +gbcmsrs/normalize: + - "modules/msk/gbcmsrs/normalize/**" diff --git a/modules/msk/gbcmsrs/rna/environment.yml b/modules/msk/gbcmsrs/rna/environment.yml new file mode 100644 index 00000000..4c59b932 --- /dev/null +++ b/modules/msk/gbcmsrs/rna/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "YOUR-TOOL=HERE" diff --git a/modules/msk/gbcmsrs/rna/main.nf b/modules/msk/gbcmsrs/rna/main.nf new file mode 100644 index 00000000..8c8b30ed --- /dev/null +++ b/modules/msk/gbcmsrs/rna/main.nf @@ -0,0 +1,72 @@ +process GBCMSRS_RNA { + tag "$meta.id" + label 'process_medium' + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1': + 'ghcr.io/mskcc-omics-workflows/gbcms:6.3.1' }" + + input: + tuple val(meta), path(variants), val(sample_names), path(bams), path(bais) + path fasta + path fasta_fai + path gtf + path rna_editing_db + path gtf_cache + + output: + tuple val(meta), path("gbcms_out/*.{vcf,maf}"), emit: variant_file + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "GBCMSRS_RNA module does not support Conda. Please use Docker / Singularity instead." + } + // gbcms's read filters (--filter-duplicates/-secondary/-supplementary/-qc-failed) + // default to ON. A pipeline mapping its own boolean params into ext.args must emit + // the explicit --no-filter-x form when off; omitting the flag silently keeps it on. + // On Nextflow >=26.04 (strict parser), CLI param overrides arrive as Strings, so + // `params.x ? 'a' : 'b'` sees "false" as truthy — compare with `.toString() == 'true'`. + def args = task.ext.args ?: '' + // Bare `--bam path` labels the sample using the staged file's stem, which is not + // meaningful for real BAM naming conventions. Pairing each bam with an explicit + // name keeps the output filename and Tumor_Sample_Barcode/VCF sample column + // predictable and equal to what the caller intends. + def bam_args = [sample_names, bams].transpose().collect { name, bam -> "--bam ${name}:${bam}" }.join(' ') + def gtf_arg = gtf ? "--gtf ${gtf}" : '' + def editing_db_arg = rna_editing_db ? "--rna-editing-db ${rna_editing_db}" : '' + // gtf_cache is produced by GBCMSRS_BUILDGTFCACHE.out.cache_dir; wiring that module's + // output into this input is a subworkflow-level concern, not this module's. + def gtf_cache_arg = gtf_cache ? "--gtf-cache-dir ${gtf_cache}" : '' + """ + gbcms rna \\ + --variants ${variants} \\ + ${bam_args} \\ + --fasta ${fasta} \\ + --output-dir gbcms_out \\ + --threads ${task.cpus} \\ + ${gtf_arg} \\ + ${editing_db_arg} \\ + ${gtf_cache_arg} \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(gbcms --version | sed 's/^gbcms //') + END_VERSIONS + """ + + stub: + def sample_name = sample_names[0] + """ + mkdir -p gbcms_out + touch gbcms_out/${sample_name}.vcf + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gbcms: \$(echo "${task.container}" | sed 's/.*://') + END_VERSIONS + """ +} diff --git a/modules/msk/gbcmsrs/rna/meta.yml b/modules/msk/gbcmsrs/rna/meta.yml new file mode 100644 index 00000000..d2f422cd --- /dev/null +++ b/modules/msk/gbcmsrs/rna/meta.yml @@ -0,0 +1,117 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "gbcmsrs_rna" +description: | + Count alleles in RNA-seq BAMs at known variant sites with transcriptome-aware + filtering using gbcms. + + Note: gbcms's read filters (--filter-duplicates/-secondary/-supplementary/-qc-failed) + default to ON. A pipeline that maps its own boolean params into ext.args must emit the + explicit --no-filter-x form when off, or the filter silently stays on since omitting + the flag just falls back to the CLI default. On Nextflow >=26.04 (strict parser), CLI + param overrides arrive as Strings, so `params.x ? 'a' : 'b'` sees "false" as truthy. + Example ext.args mapping: + + ext.args = { [ + params.filter_qc_failed.toString() == 'true' ? '--filter-qc-failed' : '--no-filter-qc-failed', + ].join(' ') } +keywords: + - basecount + - genotyping + - rna + - variants +tools: + - "gbcms": + description: + "A high-performance, orientation-aware genotype counting system for + genomic variants (Rust rewrite of GetBaseCountsMultiSample)" + homepage: "https://github.com/msk-access/gbcms" + documentation: "https://msk-access.github.io/gbcms/" + tool_dev_url: "https://github.com/msk-access/gbcms" + licence: ["AGPL-3.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - variants: + type: file + description: Input variant file in VCF or MAF format + pattern: "*.{vcf,maf}" + ontologies: [] + - sample_names: + type: string + description: + List of sample names, parallel to `bams`, used to label each BAM + via `--bam :`. Without an explicit name, gbcms falls + back to the staged file's stem, which is usually not the desired + sample id. + - bams: + type: file + description: One or more indexed RNA-seq BAM/CRAM files to count alleles in + pattern: "*.{bam,cram}" + ontologies: [] + - bais: + type: file + description: Index file(s) for the input BAM/CRAM file(s) + pattern: "*.{bai,crai}" + ontologies: [] + - fasta: + type: file + description: Reference genome FASTA file + pattern: "*.{fasta,fa}" + ontologies: [] + - fasta_fai: + type: file + description: Index of the reference genome FASTA file + pattern: "*.fai" + ontologies: [] + - gtf: + type: file + description: + Optional GTF annotation file (Ensembl/GENCODE) enabling exon boundary + and splice junction awareness. Pass an empty list (`[]`) to omit. + pattern: "*.gtf" + ontologies: [] + - rna_editing_db: + type: file + description: + Optional REDIportal TABLE1 file of known A-to-I RNA editing sites. + Pass an empty list (`[]`) to omit. + pattern: "*.{txt,txt.gz}" + ontologies: [] + - gtf_cache: + type: directory + description: + Optional prebuilt GTF index cache directory, as produced by + GBCMSRS_BUILDGTFCACHE.out.cache_dir, avoiding a re-parse of the GTF + for this sample. Pass an empty list (`[]`) to omit. Wiring the two + modules together is a subworkflow-level concern. + pattern: "gbcms_gtf_cache" +output: + variant_file: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "gbcms_out/*.{vcf,maf}": + type: file + description: + Variant file annotated with transcript-aware allele counts and + RNA editing flags + pattern: "gbcms_out/*.{vcf,maf}" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@shahr" +maintainers: + - "@buehlere" + - "@shahr" diff --git a/modules/msk/gbcmsrs/rna/tests/main.nf.test b/modules/msk/gbcmsrs/rna/tests/main.nf.test new file mode 100644 index 00000000..e4ee9088 --- /dev/null +++ b/modules/msk/gbcmsrs/rna/tests/main.nf.test @@ -0,0 +1,43 @@ +// nf-core modules test gbcmsrs/rna +nextflow_process { + + name "Test Process GBCMSRS_RNA" + script "../main.nf" + process "GBCMSRS_RNA" + + tag "modules" + tag "modules_msk" + tag "gbcmsrs" + tag "gbcmsrs/rna" + + test("sarscov2 illumina - vcf") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.test_data['sarscov2']['illumina']['test_vcf'], checkIfExists: true), + [ 'test' ], + [ file(params.test_data['sarscov2']['illumina']['test_single_end_sorted_bam'], checkIfExists: true) ], + [ file(params.test_data['sarscov2']['illumina']['test_single_end_sorted_bam_bai'], checkIfExists: true) ] + ] + input[1] = file(params.test_data['sarscov2']['genome']['genome_fasta'], checkIfExists: true) + input[2] = file(params.test_data['sarscov2']['genome']['genome_fasta_fai'], checkIfExists: true) + input[3] = file(params.test_data['sarscov2']['genome']['genome_gtf'], checkIfExists: true) + input[4] = [] + input[5] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/msk/gbcmsrs/rna/tests/main.nf.test.snap b/modules/msk/gbcmsrs/rna/tests/main.nf.test.snap new file mode 100644 index 00000000..a7bebcdc --- /dev/null +++ b/modules/msk/gbcmsrs/rna/tests/main.nf.test.snap @@ -0,0 +1,35 @@ +{ + "sarscov2 illumina - vcf": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.vcf:md5,a769b29c5747dfabde1802306cd2233b" + ] + ], + "1": [ + "versions.yml:md5,48db84911219f83e8870c5c493b21114" + ], + "variant_file": [ + [ + { + "id": "test" + }, + "test.vcf:md5,a769b29c5747dfabde1802306cd2233b" + ] + ], + "versions": [ + "versions.yml:md5,48db84911219f83e8870c5c493b21114" + ] + } + ], + "timestamp": "2026-08-27T10:15:27.562904488", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/msk/gbcmsrs/rna/tests/tags.yml b/modules/msk/gbcmsrs/rna/tests/tags.yml new file mode 100644 index 00000000..c0b5f0c9 --- /dev/null +++ b/modules/msk/gbcmsrs/rna/tests/tags.yml @@ -0,0 +1,2 @@ +gbcmsrs/rna: + - "modules/msk/gbcmsrs/rna/**" diff --git a/tests/config/test_data.config b/tests/config/test_data.config index 347b83d6..0e952f37 100644 --- a/tests/config/test_data.config +++ b/tests/config/test_data.config @@ -816,5 +816,9 @@ params { test_chr22_collapsed_grouped_bam = "${params.test_data_base_msk}/feature/fgbio_collectduplexseqmetrics/testdata/chr22_collapsed_grouped.bam" test_chr22_collapsed_grouped_bam_bai = "${params.test_data_base_msk}/feature/fgbio_collectduplexseqmetrics/testdata/chr22_collapsed_grouped.bam.bai" } + 'gbcmsrs' { + mfsd_single_maf = "${params.test_data_base_msk}/feature/getbase/getbasecount/merge/mfsd_single.maf" + mfsd_multi_maf = "${params.test_data_base_msk}/feature/getbase/getbasecount/merge/mfsd_multi.maf" + } } }